From 4997a248ab4acdb40383f1e1a5d3813a634370a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Irmak?= Date: Thu, 19 Jun 2025 05:05:32 +0300 Subject: [PATCH] core/rawdb: don't decode the full block body in ReadTransaction (#32027) Reading a single transaction out of a block shouldn't need decoding the entire body --------- Co-authored-by: Felix Lange Co-authored-by: Gary Rong --- core/rawdb/accessors_indexes.go | 56 ++++++++++++-- core/rawdb/accessors_indexes_test.go | 112 ++++++++++++++++++++++++++- 2 files changed, 159 insertions(+), 9 deletions(-) diff --git a/core/rawdb/accessors_indexes.go b/core/rawdb/accessors_indexes.go index fafde25606..b5491f76ef 100644 --- a/core/rawdb/accessors_indexes.go +++ b/core/rawdb/accessors_indexes.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -128,6 +129,46 @@ func DeleteAllTxLookupEntries(db ethdb.KeyValueStore, condition func(common.Hash } } +// findTxInBlockBody traverses the given RLP-encoded block body, searching for +// the transaction specified by its hash. +func findTxInBlockBody(blockbody rlp.RawValue, target common.Hash) (*types.Transaction, uint64, error) { + txnListRLP, _, err := rlp.SplitList(blockbody) + if err != nil { + return nil, 0, err + } + iter, err := rlp.NewListIterator(txnListRLP) + if err != nil { + return nil, 0, err + } + txIndex := uint64(0) + for iter.Next() { + if iter.Err() != nil { + return nil, 0, err + } + // The preimage for the hash calculation of legacy transactions + // is just their RLP encoding. For typed (EIP-2718) transactions, + // which are encoded as byte arrays, the preimage is the content of + // the byte array, so trim their prefix here. + txRLP := iter.Value() + kind, txHashPayload, _, err := rlp.Split(txRLP) + if err != nil { + return nil, 0, err + } + if kind == rlp.List { // Legacy transaction + txHashPayload = txRLP + } + if crypto.Keccak256Hash(txHashPayload) == target { + var tx types.Transaction + if err := rlp.DecodeBytes(txRLP, &tx); err != nil { + return nil, 0, err + } + return &tx, txIndex, nil + } + txIndex++ + } + return nil, 0, errors.New("transaction not found") +} + // ReadTransaction retrieves a specific transaction from the database, along with // its added positional metadata. func ReadTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) { @@ -139,18 +180,17 @@ func ReadTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, com if blockHash == (common.Hash{}) { return nil, common.Hash{}, 0, 0 } - body := ReadBody(db, blockHash, *blockNumber) - if body == nil { + bodyRLP := ReadBodyRLP(db, blockHash, *blockNumber) + if bodyRLP == nil { log.Error("Transaction referenced missing", "number", *blockNumber, "hash", blockHash) return nil, common.Hash{}, 0, 0 } - for txIndex, tx := range body.Transactions { - if tx.Hash() == hash { - return tx, blockHash, *blockNumber, uint64(txIndex) - } + tx, txIndex, err := findTxInBlockBody(bodyRLP, hash) + if err != nil { + log.Error("Transaction not found", "number", *blockNumber, "hash", blockHash, "txhash", hash, "err", err) + return nil, common.Hash{}, 0, 0 } - log.Error("Transaction not found", "number", *blockNumber, "hash", blockHash, "txhash", hash) - return nil, common.Hash{}, 0, 0 + return tx, blockHash, *blockNumber, txIndex } // ReadReceipt retrieves a specific transaction receipt from the database, along with diff --git a/core/rawdb/accessors_indexes_test.go b/core/rawdb/accessors_indexes_test.go index 29b468fb2a..b768deed9b 100644 --- a/core/rawdb/accessors_indexes_test.go +++ b/core/rawdb/accessors_indexes_test.go @@ -20,11 +20,13 @@ import ( "math/big" "testing" + "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/internal/blocktest" "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/uint256" ) var newTestHasher = blocktest.NewHasher @@ -72,7 +74,15 @@ func TestLookupStorage(t *testing.T) { tx1 := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11}) tx2 := types.NewTransaction(2, common.BytesToAddress([]byte{0x22}), big.NewInt(222), 2222, big.NewInt(22222), []byte{0x22, 0x22, 0x22}) tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33}) - txs := []*types.Transaction{tx1, tx2, tx3} + tx4 := types.NewTx(&types.DynamicFeeTx{ + To: new(common.Address), + Nonce: 5, + Value: big.NewInt(5), + Gas: 5, + GasTipCap: big.NewInt(55), + GasFeeCap: big.NewInt(1055), + }) + txs := []*types.Transaction{tx1, tx2, tx3, tx4} block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, &types.Body{Transactions: txs}, nil, newTestHasher()) @@ -109,3 +119,103 @@ func TestLookupStorage(t *testing.T) { }) } } + +func TestFindTxInBlockBody(t *testing.T) { + tx1 := types.NewTx(&types.LegacyTx{ + Nonce: 1, + GasPrice: big.NewInt(1), + Gas: 1, + To: new(common.Address), + Value: big.NewInt(5), + Data: []byte{0x11, 0x11, 0x11}, + }) + tx2 := types.NewTx(&types.AccessListTx{ + Nonce: 1, + GasPrice: big.NewInt(1), + Gas: 1, + To: new(common.Address), + Value: big.NewInt(5), + Data: []byte{0x11, 0x11, 0x11}, + AccessList: []types.AccessTuple{ + { + Address: common.Address{0x1}, + StorageKeys: []common.Hash{{0x1}, {0x2}}, + }, + }, + }) + tx3 := types.NewTx(&types.DynamicFeeTx{ + Nonce: 1, + Gas: 1, + To: new(common.Address), + Value: big.NewInt(5), + Data: []byte{0x11, 0x11, 0x11}, + GasTipCap: big.NewInt(55), + GasFeeCap: big.NewInt(1055), + AccessList: []types.AccessTuple{ + { + Address: common.Address{0x1}, + StorageKeys: []common.Hash{{0x1}, {0x2}}, + }, + }, + }) + tx4 := types.NewTx(&types.BlobTx{ + Nonce: 1, + Gas: 1, + To: common.Address{0x1}, + Value: uint256.NewInt(5), + Data: []byte{0x11, 0x11, 0x11}, + GasTipCap: uint256.NewInt(55), + GasFeeCap: uint256.NewInt(1055), + AccessList: []types.AccessTuple{ + { + Address: common.Address{0x1}, + StorageKeys: []common.Hash{{0x1}, {0x2}}, + }, + }, + BlobFeeCap: uint256.NewInt(1), + BlobHashes: []common.Hash{{0x1}, {0x2}}, + }) + tx5 := types.NewTx(&types.SetCodeTx{ + Nonce: 1, + Gas: 1, + To: common.Address{0x1}, + Value: uint256.NewInt(5), + Data: []byte{0x11, 0x11, 0x11}, + GasTipCap: uint256.NewInt(55), + GasFeeCap: uint256.NewInt(1055), + AccessList: []types.AccessTuple{ + { + Address: common.Address{0x1}, + StorageKeys: []common.Hash{{0x1}, {0x2}}, + }, + }, + AuthList: []types.SetCodeAuthorization{ + { + ChainID: uint256.Int{1}, + Address: common.Address{0x1}, + }, + }, + }) + + txs := []*types.Transaction{tx1, tx2, tx3, tx4, tx5} + + block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, &types.Body{Transactions: txs}, nil, newTestHasher()) + db := NewMemoryDatabase() + WriteBlock(db, block) + + rlp := ReadBodyRLP(db, block.Hash(), block.NumberU64()) + for i := 0; i < len(txs); i++ { + tx, txIndex, err := findTxInBlockBody(rlp, txs[i].Hash()) + if err != nil { + t.Fatalf("Failed to retrieve tx, err: %v", err) + } + if txIndex != uint64(i) { + t.Fatalf("Unexpected transaction index, want: %d, got: %d", i, txIndex) + } + if tx.Hash() != txs[i].Hash() { + want := spew.Sdump(txs[i]) + got := spew.Sdump(tx) + t.Fatalf("Unexpected transaction, want: %s, got: %s", want, got) + } + } +}