core/rawdb: don't decode the full block body in ReadTransaction (#32027)
Some checks are pending
/ Linux Build (push) Waiting to run
/ Linux Build (arm) (push) Waiting to run
/ Docker Image (push) Waiting to run

Reading a single transaction out of a block shouldn't need decoding the
entire body

---------

Co-authored-by: Felix Lange <fjl@twurst.com>
Co-authored-by: Gary Rong <garyrong0905@gmail.com>
This commit is contained in:
Ömer Faruk Irmak 2025-06-19 05:05:32 +03:00 committed by GitHub
parent 8219bfcadd
commit 4997a248ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 159 additions and 9 deletions

View file

@ -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

View file

@ -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)
}
}
}