Skip to content

fix(pipeline): reject partially-consumed input in JSONWithVarExprs#22265

Open
ozpool wants to merge 1 commit intosmartcontractkit:developfrom
ozpool:fix/ethtx-from-literal-address-21768
Open

fix(pipeline): reject partially-consumed input in JSONWithVarExprs#22265
ozpool wants to merge 1 commit intosmartcontractkit:developfrom
ozpool:fix/ethtx-from-literal-address-21768

Conversation

@ozpool
Copy link
Copy Markdown

@ozpool ozpool commented Apr 30, 2026

Description

Closes #21768.

The pipeline JSONWithVarExprs getter (core/services/pipeline/getters.go) uses a streaming json.Decoder that reads only one top-level token. For a literal Ethereum address in an ethtx task's from field the decoder reads the leading 0 as a complete JSON number, returns int64(0), and discards the rest of the address:

perform [type="ethtx"
         from="0xAaAa...AAAA"
         ...]
fatal=true
run.FatalErrors=["from: AddressSliceParam: cannot convert int64: bad input for task"]

Because JSONWithVarExprs successfully returns int64(0), the next getter in the From chain (NonemptyString) is never tried, and AddressSliceParam.UnmarshalPipelineParam hits its default branch and errors out.

Issue author Beam already traced this in the report; root cause is that the decoder doesn't validate that it consumed the entire input.

Fix

Tighten JSONWithVarExprs to require the decoder to consume the full input. After jd.Decode(...) returns, call jd.Token() once and require io.EOF; anything else means there's trailing data, which the getter rejects with ErrBadInput so the next getter in the From chain (e.g. NonemptyString) gets its turn.

if _, err := jd.Token(); !stderrors.Is(err, io.EOF) {
    return nil, errors.Wrapf(ErrBadInput, "trailing data after JSON value; js: %s", string(replaced))
}

Wire-observable behavior:

  • Valid JSON (objects, arrays, complete numbers, strings, null) — unchanged. All existing TestGetters_JSONWithVarExprs cases still pass.
  • Literal 0xABCD... and similar partially-consumable inputs — now correctly fall through to the next getter, so from = "0x..." resolves via NonemptyString and the ethtx task succeeds.

Tests

Added regression cases to the existing TestGetters_JSONWithVarExprs table:

  • 0x1234567890abcdef1234567890abcdef12345678 (full 20-byte address)
  • 0xdeadbeef
  • 123abc (number prefix + garbage suffix)
  • {"a":1} extra (valid JSON + trailing junk)

All four assert pipeline.ErrBadInput, matching the next-getter-falls-through contract.

$ go test -count=1 -run "TestGetters_JSONWithVarExprs" ./core/services/pipeline/
ok   github.com/smartcontractkit/chainlink/v2/core/services/pipeline   2.142s

DB-backed tests in this package (TestETHTxTask, TestETHCallTask) require CL_DATABASE_URL and a Postgres test database, which I don't have set up locally; happy to add a focused TestETHTxTask_FromLiteralAddress if a maintainer can either point me at the right setup or run those checks in CI.

Diff

 .changeset/fix-ethtx-from-literal-address.md    | +9
 core/services/pipeline/getters.go               | +11
 core/services/pipeline/getters_test.go          | +8

The pipeline JSONWithVarExprs getter used a streaming json.Decoder that
read just one top-level token and returned. For a literal Ethereum
address in an ethtx task, the decoder happily read the leading 0 as a
complete JSON number, returned int64(0), and discarded the rest:

    perform [type="ethtx"
             from="0xAaAa...AAAA"
             ...]

    fatal=true
    run.FatalErrors=["from: AddressSliceParam: cannot convert int64: bad input for task"]

Because JSONWithVarExprs successfully returned int64(0), the next getter
in the From chain (NonemptyString) was never tried, and
AddressSliceParam.UnmarshalPipelineParam hit its default branch and
errored out.

Tighten JSONWithVarExprs to require the decoder to consume the full
input. After Decode returns, call jd.Token() once and require io.EOF;
anything else means trailing data, which the getter rejects with
ErrBadInput so the next getter in the chain gets a turn.

Adds table cases in getters_test.go covering the original bug shape
(literal Ethereum address, mixed alphanumeric, JSON value with
trailing junk).

Closes smartcontractkit#21768.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[NODE] ethtx task with from (field) address always fails with "cannot convert int64"

1 participant