From 1e0855e5aeec2a62bfba976776f9f14efb61e452 Mon Sep 17 00:00:00 2001 From: ManuelArto Date: Mon, 8 Jun 2026 19:21:36 +0200 Subject: [PATCH 1/3] internal/ethapi: return InvalidParams for malformed sendRawTransaction RLP SendRawTransaction and SendRawTransactionSync currently return the bare UnmarshalBinary error to the JSON-RPC layer. Because the underlying decode errors don't implement rpc.Error.ErrorCode(), rpc/json.go:errorMessage falls back to errcodeDefault = -32000. Per JSON-RPC 2.0, malformed method parameters should use -32602 (InvalidParams), which is what Reth and Besu emit for the same input. internal/ethapi/errors.go already defines invalidParamsError with ErrorCode() = errCodeInvalidParams. Wire it into the two decode paths and add a parameterised test covering 5 distinct RLP-decode failure shapes against both entry points. --- internal/ethapi/api.go | 4 +-- internal/ethapi/api_test.go | 49 +++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 22a59aab58..45a4be3b0c 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1735,7 +1735,7 @@ func (api *TransactionAPI) currentBlobSidecarVersion() byte { func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) { tx := new(types.Transaction) if err := tx.UnmarshalBinary(input); err != nil { - return common.Hash{}, err + return common.Hash{}, &invalidParamsError{message: err.Error()} } // Convert legacy blob transaction proofs. @@ -1758,7 +1758,7 @@ func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil func (api *TransactionAPI) SendRawTransactionSync(ctx context.Context, input hexutil.Bytes, timeoutMs *uint64) (map[string]interface{}, error) { tx := new(types.Transaction) if err := tx.UnmarshalBinary(input); err != nil { - return nil, err + return nil, &invalidParamsError{message: err.Error()} } // Convert legacy blob transaction proofs. diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 80a9036ecc..9b821a2bdc 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -4175,6 +4175,55 @@ func TestSendRawTransactionSync_Timeout(t *testing.T) { } } +// TestSendRawTransaction_InvalidParams_OnMalformedRLP verifies that malformed-RLP +// inputs are reported with JSON-RPC error code -32602 (InvalidParams), not the +// default -32000. Aligns with JSON-RPC 2.0 spec and the Reth/Besu implementations. +func TestSendRawTransaction_InvalidParams_OnMalformedRLP(t *testing.T) { + t.Parallel() + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{}, + } + b := newTestBackend(t, 0, genesis, ethash.NewFaker(), nil) + api := NewTransactionAPI(b, new(AddrLocker)) + + cases := []struct { + name string + raw string + }{ + {"empty-list", "0xc0"}, + {"truncated-short-list", "0xd4"}, + {"unknown-tx-type", "0x09c0"}, + {"eip4844-truncated-list", "0x03c0"}, + {"eip7702-truncated-list", "0x04c0"}, + } + + for _, tc := range cases { + t.Run(tc.name+"/SendRawTransaction", func(t *testing.T) { + _, err := api.SendRawTransaction(context.Background(), hexutil.MustDecode(tc.raw)) + assertInvalidParams(t, err) + }) + t.Run(tc.name+"/SendRawTransactionSync", func(t *testing.T) { + _, err := api.SendRawTransactionSync(context.Background(), hexutil.MustDecode(tc.raw), nil) + assertInvalidParams(t, err) + }) + } +} + +func assertInvalidParams(t *testing.T, err error) { + t.Helper() + if err == nil { + t.Fatal("expected error, got nil") + } + var ec interface{ ErrorCode() int } + if !errors.As(err, &ec) { + t.Fatalf("expected error implementing ErrorCode(), got %T: %v", err, err) + } + if got, want := ec.ErrorCode(), -32602; got != want { + t.Fatalf("expected ErrorCode=%d (InvalidParams), got %d (%v)", want, got, err) + } +} + func TestGetStorageValues(t *testing.T) { t.Parallel() From d20e601ece95244d3895d66f3ae1231499291acb Mon Sep 17 00:00:00 2001 From: ManuelArto Date: Tue, 9 Jun 2026 15:58:07 +0200 Subject: [PATCH 2/3] remove redundant test description for malformed RLP in TestSendRawTransaction_InvalidParams --- internal/ethapi/api_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 9b821a2bdc..ee24fe96ee 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -4175,9 +4175,6 @@ func TestSendRawTransactionSync_Timeout(t *testing.T) { } } -// TestSendRawTransaction_InvalidParams_OnMalformedRLP verifies that malformed-RLP -// inputs are reported with JSON-RPC error code -32602 (InvalidParams), not the -// default -32000. Aligns with JSON-RPC 2.0 spec and the Reth/Besu implementations. func TestSendRawTransaction_InvalidParams_OnMalformedRLP(t *testing.T) { t.Parallel() genesis := &core.Genesis{ From 506108d038fe1a456471901d155666708d794f44 Mon Sep 17 00:00:00 2001 From: ManuelArto Date: Sun, 14 Jun 2026 17:49:37 +0200 Subject: [PATCH 3/3] internal/ethapi: remove malformed-RLP tests --- internal/ethapi/api_test.go | 46 ------------------------------------- 1 file changed, 46 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index ee24fe96ee..80a9036ecc 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -4175,52 +4175,6 @@ func TestSendRawTransactionSync_Timeout(t *testing.T) { } } -func TestSendRawTransaction_InvalidParams_OnMalformedRLP(t *testing.T) { - t.Parallel() - genesis := &core.Genesis{ - Config: params.TestChainConfig, - Alloc: types.GenesisAlloc{}, - } - b := newTestBackend(t, 0, genesis, ethash.NewFaker(), nil) - api := NewTransactionAPI(b, new(AddrLocker)) - - cases := []struct { - name string - raw string - }{ - {"empty-list", "0xc0"}, - {"truncated-short-list", "0xd4"}, - {"unknown-tx-type", "0x09c0"}, - {"eip4844-truncated-list", "0x03c0"}, - {"eip7702-truncated-list", "0x04c0"}, - } - - for _, tc := range cases { - t.Run(tc.name+"/SendRawTransaction", func(t *testing.T) { - _, err := api.SendRawTransaction(context.Background(), hexutil.MustDecode(tc.raw)) - assertInvalidParams(t, err) - }) - t.Run(tc.name+"/SendRawTransactionSync", func(t *testing.T) { - _, err := api.SendRawTransactionSync(context.Background(), hexutil.MustDecode(tc.raw), nil) - assertInvalidParams(t, err) - }) - } -} - -func assertInvalidParams(t *testing.T, err error) { - t.Helper() - if err == nil { - t.Fatal("expected error, got nil") - } - var ec interface{ ErrorCode() int } - if !errors.As(err, &ec) { - t.Fatalf("expected error implementing ErrorCode(), got %T: %v", err, err) - } - if got, want := ec.ErrorCode(), -32602; got != want { - t.Fatalf("expected ErrorCode=%d (InvalidParams), got %d (%v)", want, got, err) - } -} - func TestGetStorageValues(t *testing.T) { t.Parallel()