diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 2eb4dee3c0..7904a1f3f8 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1220,13 +1220,14 @@ func newRPCTransactionFromBlockIndex(b *types.Block, index uint64, config *param } // newRPCRawTransactionFromBlockIndex returns the bytes of a transaction given a block and a transaction index. -func newRPCRawTransactionFromBlockIndex(b *types.Block, index uint64) hexutil.Bytes { +func newRPCRawTransactionFromBlockIndex(b *types.Block, index uint64) *hexutil.Bytes { txs := b.Transactions() if index >= uint64(len(txs)) { return nil } blob, _ := txs[index].MarshalBinary() - return blob + b2 := hexutil.Bytes(blob) + return &b2 } // accessListResult returns an optional accesslist @@ -1485,7 +1486,7 @@ func (api *TransactionAPI) GetTransactionByBlockHashAndIndex(ctx context.Context } // GetRawTransactionByBlockNumberAndIndex returns the bytes of the transaction for the given block number and index. -func (api *TransactionAPI) GetRawTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) hexutil.Bytes { +func (api *TransactionAPI) GetRawTransactionByBlockNumberAndIndex(ctx context.Context, blockNr rpc.BlockNumber, index hexutil.Uint) *hexutil.Bytes { if block, _ := api.b.BlockByNumber(ctx, blockNr); block != nil { return newRPCRawTransactionFromBlockIndex(block, uint64(index)) } @@ -1493,7 +1494,7 @@ func (api *TransactionAPI) GetRawTransactionByBlockNumberAndIndex(ctx context.Co } // GetRawTransactionByBlockHashAndIndex returns the bytes of the transaction for the given block hash and index. -func (api *TransactionAPI) GetRawTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) hexutil.Bytes { +func (api *TransactionAPI) GetRawTransactionByBlockHashAndIndex(ctx context.Context, blockHash common.Hash, index hexutil.Uint) *hexutil.Bytes { if block, _ := api.b.BlockByHash(ctx, blockHash); block != nil { return newRPCRawTransactionFromBlockIndex(block, uint64(index)) } @@ -1545,12 +1546,17 @@ func (api *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common } // GetRawTransactionByHash returns the bytes of the transaction for the given hash. -func (api *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) { +func (api *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (*hexutil.Bytes, error) { // Retrieve a finalized transaction, or a pooled otherwise found, tx, _, _, _ := api.b.GetCanonicalTransaction(hash) if !found { if tx = api.b.GetPoolTransaction(hash); tx != nil { - return tx.MarshalBinary() + blob, err := tx.MarshalBinary() + if err != nil { + return nil, err + } + b := hexutil.Bytes(blob) + return &b, nil } // If also not in the pool there is a chance the tx indexer is still in progress. if !api.b.TxIndexDone() { @@ -1559,7 +1565,12 @@ func (api *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash com // If the transaction is not found in the pool and the indexer is done, return nil return nil, nil } - return tx.MarshalBinary() + blob, err := tx.MarshalBinary() + if err != nil { + return nil, err + } + b := hexutil.Bytes(blob) + return &b, nil } // GetTransactionReceipt returns the transaction receipt for the given transaction hash. @@ -2089,12 +2100,17 @@ func (api *DebugAPI) GetRawReceipts(ctx context.Context, blockNrOrHash rpc.Block } // GetRawTransaction returns the bytes of the transaction for the given hash. -func (api *DebugAPI) GetRawTransaction(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) { +func (api *DebugAPI) GetRawTransaction(ctx context.Context, hash common.Hash) (*hexutil.Bytes, error) { // Retrieve a finalized transaction, or a pooled otherwise found, tx, _, _, _ := api.b.GetCanonicalTransaction(hash) if !found { if tx = api.b.GetPoolTransaction(hash); tx != nil { - return tx.MarshalBinary() + blob, err := tx.MarshalBinary() + if err != nil { + return nil, err + } + b := hexutil.Bytes(blob) + return &b, nil } // If also not in the pool there is a chance the tx indexer is still in progress. if !api.b.TxIndexDone() { @@ -2103,7 +2119,12 @@ func (api *DebugAPI) GetRawTransaction(ctx context.Context, hash common.Hash) (h // Transaction is not found in the pool and the indexer is done. return nil, nil } - return tx.MarshalBinary() + blob, err := tx.MarshalBinary() + if err != nil { + return nil, err + } + b := hexutil.Bytes(blob) + return &b, nil } // PrintBlock retrieves a block and returns its pretty printed form. diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 80a9036ecc..13bd8c96cc 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -672,7 +672,7 @@ func (b testBackend) TxIndexDone() bool { return true } func (b testBackend) GetPoolTransactions() (types.Transactions, error) { panic("implement me") } -func (b testBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { panic("implement me") } +func (b testBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { return nil } func (b testBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { return 0, nil } @@ -4339,3 +4339,171 @@ func TestStateMethodsDefaultToLatest(t *testing.T) { []any{map[common.Address][]common.Hash{acc: {slot}}, "latest"}, []any{map[common.Address][]common.Hash{acc: {slot}}}) } + +func TestRPCGetRawTransactionByHash(t *testing.T) { + t.Parallel() + + var ( + backend, txHashes = setupReceiptBackend(t, 2) + api = NewTransactionAPI(backend, new(AddrLocker)) + ) + + var testSuite = []struct { + txHash common.Hash + file string + }{ + // 0. legacy transfer tx (block 0 default case) + { + txHash: txHashes[0], + file: "found-legacy", + }, + // 1. create contract tx (block 1) + { + txHash: txHashes[1], + file: "found-create", + }, + // 2. zero hash, not in chain + { + txHash: common.Hash{}, + file: "txhash-empty", + }, + // 3. unknown hash, not in chain + { + txHash: common.HexToHash("deadbeef"), + file: "txhash-notfound", + }, + } + + for i, tt := range testSuite { + result, err := api.GetRawTransactionByHash(context.Background(), tt.txHash) + if err != nil { + t.Errorf("test %d: want no error, have %v", i, err) + continue + } + testRPCResponseWithFile(t, i, result, "eth_getRawTransactionByHash", tt.file) + } +} + +func TestRPCGetRawTransactionByBlockHashAndIndex(t *testing.T) { + t.Parallel() + + var ( + backend, _ = setupReceiptBackend(t, 2) + api = NewTransactionAPI(backend, new(AddrLocker)) + ) + + block1 := backend.chain.GetBlockByNumber(1) + if block1 == nil { + t.Fatal("block 1 not found") + } + block1Hash := block1.Hash() + unknownHash := common.HexToHash("deadbeef") + + var testSuite = []struct { + blockHash common.Hash + index hexutil.Uint + file string + }{ + // 0. block 1, index 0 → found (legacy tx) + { + blockHash: block1Hash, + index: 0, + file: "found-legacy", + }, + // 1. block 1, index 1 → out of range → null + { + blockHash: block1Hash, + index: 1, + file: "index-outofrange", + }, + // 2. unknown block hash, index 0 → null + { + blockHash: unknownHash, + index: 0, + file: "blockhash-notfound", + }, + } + + for i, tt := range testSuite { + result := api.GetRawTransactionByBlockHashAndIndex(context.Background(), tt.blockHash, tt.index) + testRPCResponseWithFile(t, i, result, "eth_getRawTransactionByBlockHashAndIndex", tt.file) + } +} + +func TestRPCGetRawTransactionByBlockNumberAndIndex(t *testing.T) { + t.Parallel() + + var ( + backend, _ = setupReceiptBackend(t, 2) + api = NewTransactionAPI(backend, new(AddrLocker)) + ) + + var testSuite = []struct { + blockNumber rpc.BlockNumber + index hexutil.Uint + file string + }{ + // 0. block 1, index 0 → found (legacy tx) + { + blockNumber: 1, + index: 0, + file: "found-legacy", + }, + // 1. block 1, index 1 → out of range → null + { + blockNumber: 1, + index: 1, + file: "index-outofrange", + }, + // 2. block 999, doesn't exist → null + { + blockNumber: 999, + index: 0, + file: "blocknumber-notfound", + }, + } + + for i, tt := range testSuite { + result := api.GetRawTransactionByBlockNumberAndIndex(context.Background(), tt.blockNumber, tt.index) + testRPCResponseWithFile(t, i, result, "eth_getRawTransactionByBlockNumberAndIndex", tt.file) + } +} + +func TestRPCDebugGetRawTransaction(t *testing.T) { + t.Parallel() + + var ( + backend, txHashes = setupReceiptBackend(t, 2) + api = NewDebugAPI(backend) + ) + + var testSuite = []struct { + txHash common.Hash + file string + }{ + // 0. legacy transfer tx (block 0 default case) + { + txHash: txHashes[0], + file: "found-legacy", + }, + // 1. zero hash, not in chain + { + txHash: common.Hash{}, + file: "txhash-empty", + }, + // 2. unknown hash, not in chain + { + txHash: common.HexToHash("deadbeef"), + file: "txhash-notfound", + }, + } + + for i, tt := range testSuite { + result, err := api.GetRawTransaction(context.Background(), tt.txHash) + if err != nil { + t.Errorf("test %d: want no error, have %v", i, err) + continue + } + testRPCResponseWithFile(t, i, result, "debug_getRawTransaction", tt.file) + } +} diff --git a/internal/ethapi/testdata/debug_getRawTransaction-found-legacy.json b/internal/ethapi/testdata/debug_getRawTransaction-found-legacy.json new file mode 100644 index 0000000000..90360566da --- /dev/null +++ b/internal/ethapi/testdata/debug_getRawTransaction-found-legacy.json @@ -0,0 +1 @@ +"0xf8658084342770c0825208940d3ab14bbad3d99f4203bd7a11acb94882050e7e8203e8801ba0cd190747077598c3b9c5f21ea06f8487d6ab1e23358fbe0e0e0c4e64651b68f3a0467ebcb2186b332969991e2e25244959dcdb3feb7652ff6b81ce96e646d86abd" \ No newline at end of file diff --git a/internal/ethapi/testdata/debug_getRawTransaction-txhash-empty.json b/internal/ethapi/testdata/debug_getRawTransaction-txhash-empty.json new file mode 100644 index 0000000000..ec747fa47d --- /dev/null +++ b/internal/ethapi/testdata/debug_getRawTransaction-txhash-empty.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/debug_getRawTransaction-txhash-notfound.json b/internal/ethapi/testdata/debug_getRawTransaction-txhash-notfound.json new file mode 100644 index 0000000000..ec747fa47d --- /dev/null +++ b/internal/ethapi/testdata/debug_getRawTransaction-txhash-notfound.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getRawTransactionByBlockHashAndIndex-blockhash-notfound.json b/internal/ethapi/testdata/eth_getRawTransactionByBlockHashAndIndex-blockhash-notfound.json new file mode 100644 index 0000000000..ec747fa47d --- /dev/null +++ b/internal/ethapi/testdata/eth_getRawTransactionByBlockHashAndIndex-blockhash-notfound.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getRawTransactionByBlockHashAndIndex-found-legacy.json b/internal/ethapi/testdata/eth_getRawTransactionByBlockHashAndIndex-found-legacy.json new file mode 100644 index 0000000000..90360566da --- /dev/null +++ b/internal/ethapi/testdata/eth_getRawTransactionByBlockHashAndIndex-found-legacy.json @@ -0,0 +1 @@ +"0xf8658084342770c0825208940d3ab14bbad3d99f4203bd7a11acb94882050e7e8203e8801ba0cd190747077598c3b9c5f21ea06f8487d6ab1e23358fbe0e0e0c4e64651b68f3a0467ebcb2186b332969991e2e25244959dcdb3feb7652ff6b81ce96e646d86abd" \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getRawTransactionByBlockHashAndIndex-index-outofrange.json b/internal/ethapi/testdata/eth_getRawTransactionByBlockHashAndIndex-index-outofrange.json new file mode 100644 index 0000000000..ec747fa47d --- /dev/null +++ b/internal/ethapi/testdata/eth_getRawTransactionByBlockHashAndIndex-index-outofrange.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getRawTransactionByBlockNumberAndIndex-blocknumber-notfound.json b/internal/ethapi/testdata/eth_getRawTransactionByBlockNumberAndIndex-blocknumber-notfound.json new file mode 100644 index 0000000000..ec747fa47d --- /dev/null +++ b/internal/ethapi/testdata/eth_getRawTransactionByBlockNumberAndIndex-blocknumber-notfound.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getRawTransactionByBlockNumberAndIndex-found-legacy.json b/internal/ethapi/testdata/eth_getRawTransactionByBlockNumberAndIndex-found-legacy.json new file mode 100644 index 0000000000..90360566da --- /dev/null +++ b/internal/ethapi/testdata/eth_getRawTransactionByBlockNumberAndIndex-found-legacy.json @@ -0,0 +1 @@ +"0xf8658084342770c0825208940d3ab14bbad3d99f4203bd7a11acb94882050e7e8203e8801ba0cd190747077598c3b9c5f21ea06f8487d6ab1e23358fbe0e0e0c4e64651b68f3a0467ebcb2186b332969991e2e25244959dcdb3feb7652ff6b81ce96e646d86abd" \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getRawTransactionByBlockNumberAndIndex-index-outofrange.json b/internal/ethapi/testdata/eth_getRawTransactionByBlockNumberAndIndex-index-outofrange.json new file mode 100644 index 0000000000..ec747fa47d --- /dev/null +++ b/internal/ethapi/testdata/eth_getRawTransactionByBlockNumberAndIndex-index-outofrange.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getRawTransactionByHash-found-create.json b/internal/ethapi/testdata/eth_getRawTransactionByHash-found-create.json new file mode 100644 index 0000000000..40cec5ee84 --- /dev/null +++ b/internal/ethapi/testdata/eth_getRawTransactionByHash-found-create.json @@ -0,0 +1 @@ +"0xf85301842db1629182cf6c8080846080604026a0fa210b09f8a13ea096b3a005a5feb0270121ab259555898bf03826e993ec14f4a007173d180059852006668647a793207d488723b89acdd6c484273f16beb9aaa3" \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getRawTransactionByHash-found-legacy.json b/internal/ethapi/testdata/eth_getRawTransactionByHash-found-legacy.json new file mode 100644 index 0000000000..90360566da --- /dev/null +++ b/internal/ethapi/testdata/eth_getRawTransactionByHash-found-legacy.json @@ -0,0 +1 @@ +"0xf8658084342770c0825208940d3ab14bbad3d99f4203bd7a11acb94882050e7e8203e8801ba0cd190747077598c3b9c5f21ea06f8487d6ab1e23358fbe0e0e0c4e64651b68f3a0467ebcb2186b332969991e2e25244959dcdb3feb7652ff6b81ce96e646d86abd" \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getRawTransactionByHash-txhash-empty.json b/internal/ethapi/testdata/eth_getRawTransactionByHash-txhash-empty.json new file mode 100644 index 0000000000..ec747fa47d --- /dev/null +++ b/internal/ethapi/testdata/eth_getRawTransactionByHash-txhash-empty.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getRawTransactionByHash-txhash-notfound.json b/internal/ethapi/testdata/eth_getRawTransactionByHash-txhash-notfound.json new file mode 100644 index 0000000000..ec747fa47d --- /dev/null +++ b/internal/ethapi/testdata/eth_getRawTransactionByHash-txhash-notfound.json @@ -0,0 +1 @@ +null \ No newline at end of file