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()