Skip to content

Commit f70aaa8

Browse files
ethapi: reduce some of the wasted effort in GetTransactionReceipt (#32021)
Towards #26974 --------- Co-authored-by: Gary Rong <garyrong0905@gmail.com>
1 parent 7c180f8 commit f70aaa8

17 files changed

+487
-106
lines changed

core/blockchain.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ type BlockChain struct {
314314

315315
bodyCache *lru.Cache[common.Hash, *types.Body]
316316
bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue]
317-
receiptsCache *lru.Cache[common.Hash, []*types.Receipt]
317+
receiptsCache *lru.Cache[common.Hash, []*types.Receipt] // Receipts cache with all fields derived
318318
blockCache *lru.Cache[common.Hash, *types.Block]
319319

320320
txLookupLock sync.RWMutex

core/blockchain_reader.go

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ package core
1818

1919
import (
2020
"errors"
21+
"fmt"
22+
"math/big"
2123

2224
"github.com/ethereum/go-ethereum/common"
2325
"github.com/ethereum/go-ethereum/consensus"
26+
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
2427
"github.com/ethereum/go-ethereum/core/rawdb"
2528
"github.com/ethereum/go-ethereum/core/state"
2629
"github.com/ethereum/go-ethereum/core/state/snapshot"
@@ -213,6 +216,44 @@ func (bc *BlockChain) GetBlocksFromHash(hash common.Hash, n int) (blocks []*type
213216
return
214217
}
215218

219+
// GetCanonicalReceipt allows fetching a receipt for a transaction that was
220+
// already looked up on the index. Notably, only receipt in canonical chain
221+
// is visible.
222+
func (bc *BlockChain) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, txIndex uint64) (*types.Receipt, error) {
223+
// The receipt retrieved from the cache contains all previously derived fields
224+
if receipts, ok := bc.receiptsCache.Get(blockHash); ok {
225+
if int(txIndex) >= len(receipts) {
226+
return nil, fmt.Errorf("receipt out of index, length: %d, index: %d", len(receipts), txIndex)
227+
}
228+
return receipts[int(txIndex)], nil
229+
}
230+
header := bc.GetHeader(blockHash, blockNumber)
231+
if header == nil {
232+
return nil, fmt.Errorf("block header is not found, %d, %x", blockNumber, blockHash)
233+
}
234+
var blobGasPrice *big.Int
235+
if header.ExcessBlobGas != nil {
236+
blobGasPrice = eip4844.CalcBlobFee(bc.chainConfig, header)
237+
}
238+
receipt, ctx, err := rawdb.ReadCanonicalRawReceipt(bc.db, blockHash, blockNumber, txIndex)
239+
if err != nil {
240+
return nil, err
241+
}
242+
signer := types.MakeSigner(bc.chainConfig, new(big.Int).SetUint64(blockNumber), header.Time)
243+
receipt.DeriveFields(signer, types.DeriveReceiptContext{
244+
BlockHash: blockHash,
245+
BlockNumber: blockNumber,
246+
BlockTime: header.Time,
247+
BaseFee: header.BaseFee,
248+
BlobGasPrice: blobGasPrice,
249+
GasUsed: ctx.GasUsed,
250+
LogIndex: ctx.LogIndex,
251+
Tx: tx,
252+
TxIndex: uint(txIndex),
253+
})
254+
return receipt, nil
255+
}
256+
216257
// GetReceiptsByHash retrieves the receipts for all transactions in a given block.
217258
func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
218259
if receipts, ok := bc.receiptsCache.Get(hash); ok {
@@ -277,21 +318,23 @@ func (bc *BlockChain) GetAncestor(hash common.Hash, number, ancestor uint64, max
277318
return bc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical)
278319
}
279320

280-
// GetTransactionLookup retrieves the lookup along with the transaction
321+
// GetCanonicalTransaction retrieves the lookup along with the transaction
281322
// itself associate with the given transaction hash.
282323
//
283324
// A null will be returned if the transaction is not found. This can be due to
284325
// the transaction indexer not being finished. The caller must explicitly check
285326
// the indexer progress.
286-
func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction) {
327+
//
328+
// Notably, only the transaction in the canonical chain is visible.
329+
func (bc *BlockChain) GetCanonicalTransaction(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction) {
287330
bc.txLookupLock.RLock()
288331
defer bc.txLookupLock.RUnlock()
289332

290333
// Short circuit if the txlookup already in the cache, retrieve otherwise
291334
if item, exist := bc.txLookupCache.Get(hash); exist {
292335
return item.lookup, item.transaction
293336
}
294-
tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(bc.db, hash)
337+
tx, blockHash, blockNumber, txIndex := rawdb.ReadCanonicalTransaction(bc.db, hash)
295338
if tx == nil {
296339
return nil, nil
297340
}

core/blockchain_test.go

Lines changed: 114 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ import (
2525
"math/rand"
2626
"os"
2727
"path"
28+
"reflect"
2829
"sync"
2930
"testing"
3031
"time"
3132

33+
"github.com/davecgh/go-spew/spew"
3234
"github.com/ethereum/go-ethereum/common"
3335
"github.com/ethereum/go-ethereum/consensus"
3436
"github.com/ethereum/go-ethereum/consensus/beacon"
@@ -46,6 +48,7 @@ import (
4648
"github.com/ethereum/go-ethereum/params"
4749
"github.com/ethereum/go-ethereum/trie"
4850
"github.com/holiman/uint256"
51+
"github.com/stretchr/testify/assert"
4952
)
5053

5154
// So we can deterministically seed different blockchains
@@ -1004,29 +1007,47 @@ func testChainTxReorgs(t *testing.T, scheme string) {
10041007

10051008
// removed tx
10061009
for i, tx := range (types.Transactions{pastDrop, freshDrop}) {
1007-
if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn != nil {
1010+
if txn, _, _, _ := rawdb.ReadCanonicalTransaction(db, tx.Hash()); txn != nil {
10081011
t.Errorf("drop %d: tx %v found while shouldn't have been", i, txn)
10091012
}
1010-
if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt != nil {
1013+
if rcpt, _, _, _ := rawdb.ReadCanonicalReceipt(db, tx.Hash(), blockchain.Config()); rcpt != nil {
10111014
t.Errorf("drop %d: receipt %v found while shouldn't have been", i, rcpt)
10121015
}
10131016
}
10141017
// added tx
10151018
for i, tx := range (types.Transactions{pastAdd, freshAdd, futureAdd}) {
1016-
if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil {
1019+
if txn, _, _, _ := rawdb.ReadCanonicalTransaction(db, tx.Hash()); txn == nil {
10171020
t.Errorf("add %d: expected tx to be found", i)
10181021
}
1019-
if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil {
1022+
if rcpt, _, _, index := rawdb.ReadCanonicalReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil {
10201023
t.Errorf("add %d: expected receipt to be found", i)
1024+
} else if rawRcpt, ctx, _ := rawdb.ReadCanonicalRawReceipt(db, rcpt.BlockHash, rcpt.BlockNumber.Uint64(), index); rawRcpt == nil {
1025+
t.Errorf("add %d: expected raw receipt to be found", i)
1026+
} else {
1027+
if rcpt.GasUsed != ctx.GasUsed {
1028+
t.Errorf("add %d, raw gasUsedSoFar doesn't make sense", i)
1029+
}
1030+
if len(rcpt.Logs) > 0 && rcpt.Logs[0].Index != ctx.LogIndex {
1031+
t.Errorf("add %d, raw startingLogIndex doesn't make sense", i)
1032+
}
10211033
}
10221034
}
10231035
// shared tx
10241036
for i, tx := range (types.Transactions{postponed, swapped}) {
1025-
if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil {
1037+
if txn, _, _, _ := rawdb.ReadCanonicalTransaction(db, tx.Hash()); txn == nil {
10261038
t.Errorf("share %d: expected tx to be found", i)
10271039
}
1028-
if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil {
1040+
if rcpt, _, _, index := rawdb.ReadCanonicalReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil {
10291041
t.Errorf("share %d: expected receipt to be found", i)
1042+
} else if rawRcpt, ctx, _ := rawdb.ReadCanonicalRawReceipt(db, rcpt.BlockHash, rcpt.BlockNumber.Uint64(), index); rawRcpt == nil {
1043+
t.Errorf("add %d: expected raw receipt to be found", i)
1044+
} else {
1045+
if rcpt.GasUsed != ctx.GasUsed {
1046+
t.Errorf("add %d, raw gasUsedSoFar doesn't make sense", i)
1047+
}
1048+
if len(rcpt.Logs) > 0 && rcpt.Logs[0].Index != ctx.LogIndex {
1049+
t.Errorf("add %d, raw startingLogIndex doesn't make sense", i)
1050+
}
10301051
}
10311052
}
10321053
}
@@ -4404,6 +4425,93 @@ func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64,
44044425
if receipts == nil || len(receipts) != 1 {
44054426
t.Fatalf("Missed block receipts: %d, cutoff: %d", num, cutoffBlock.NumberU64())
44064427
}
4428+
for indx, receipt := range receipts {
4429+
receiptByLookup, err := chain.GetCanonicalReceipt(body.Transactions[indx], receipt.BlockHash,
4430+
receipt.BlockNumber.Uint64(), uint64(indx))
4431+
assert.NoError(t, err)
4432+
assert.Equal(t, receipt, receiptByLookup)
4433+
}
4434+
}
4435+
}
4436+
}
4437+
4438+
func TestGetCanonicalReceipt(t *testing.T) {
4439+
const chainLength = 64
4440+
4441+
// Configure and generate a sample block chain
4442+
var (
4443+
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
4444+
address = crypto.PubkeyToAddress(key.PublicKey)
4445+
funds = big.NewInt(1000000000000000000)
4446+
gspec = &Genesis{
4447+
Config: params.MergedTestChainConfig,
4448+
Alloc: types.GenesisAlloc{address: {Balance: funds}},
4449+
BaseFee: big.NewInt(params.InitialBaseFee),
4450+
}
4451+
signer = types.LatestSigner(gspec.Config)
4452+
engine = beacon.New(ethash.NewFaker())
4453+
codeBin = common.FromHex("0x608060405234801561000f575f5ffd5b507f8ae1c8c6e5f91159d0bc1c4b9a47ce45301753843012cbe641e4456bfc73538b33426040516100419291906100ff565b60405180910390a1610139565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100778261004e565b9050919050565b6100878161006d565b82525050565b5f819050919050565b61009f8161008d565b82525050565b5f82825260208201905092915050565b7f436f6e7374727563746f72207761732063616c6c6564000000000000000000005f82015250565b5f6100e96016836100a5565b91506100f4826100b5565b602082019050919050565b5f6060820190506101125f83018561007e565b61011f6020830184610096565b8181036040830152610130816100dd565b90509392505050565b603e806101455f395ff3fe60806040525f5ffdfea2646970667358221220e8bc3c31e3ac337eab702e8fdfc1c71894f4df1af4221bcde4a2823360f403fb64736f6c634300081e0033")
4454+
)
4455+
_, blocks, receipts := GenerateChainWithGenesis(gspec, engine, chainLength, func(i int, block *BlockGen) {
4456+
// SPDX-License-Identifier: MIT
4457+
// pragma solidity ^0.8.0;
4458+
//
4459+
// contract ConstructorLogger {
4460+
// event ConstructorLog(address sender, uint256 timestamp, string message);
4461+
//
4462+
// constructor() {
4463+
// emit ConstructorLog(msg.sender, block.timestamp, "Constructor was called");
4464+
// }
4465+
// }
4466+
//
4467+
// 608060405234801561000f575f5ffd5b507f8ae1c8c6e5f91159d0bc1c4b9a47ce45301753843012cbe641e4456bfc73538b33426040516100419291906100ff565b60405180910390a1610139565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100778261004e565b9050919050565b6100878161006d565b82525050565b5f819050919050565b61009f8161008d565b82525050565b5f82825260208201905092915050565b7f436f6e7374727563746f72207761732063616c6c6564000000000000000000005f82015250565b5f6100e96016836100a5565b91506100f4826100b5565b602082019050919050565b5f6060820190506101125f83018561007e565b61011f6020830184610096565b8181036040830152610130816100dd565b90509392505050565b603e806101455f395ff3fe60806040525f5ffdfea2646970667358221220e8bc3c31e3ac337eab702e8fdfc1c71894f4df1af4221bcde4a2823360f403fb64736f6c634300081e0033
4468+
nonce := block.TxNonce(address)
4469+
tx, err := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 100_000, block.header.BaseFee, codeBin), signer, key)
4470+
if err != nil {
4471+
panic(err)
4472+
}
4473+
block.AddTx(tx)
4474+
4475+
tx2, err := types.SignTx(types.NewContractCreation(nonce+1, big.NewInt(0), 100_000, block.header.BaseFee, codeBin), signer, key)
4476+
if err != nil {
4477+
panic(err)
4478+
}
4479+
block.AddTx(tx2)
4480+
4481+
tx3, err := types.SignTx(types.NewContractCreation(nonce+2, big.NewInt(0), 100_000, block.header.BaseFee, codeBin), signer, key)
4482+
if err != nil {
4483+
panic(err)
4484+
}
4485+
block.AddTx(tx3)
4486+
})
4487+
4488+
db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
4489+
defer db.Close()
4490+
options := DefaultConfig().WithStateScheme(rawdb.PathScheme)
4491+
chain, _ := NewBlockChain(db, gspec, beacon.New(ethash.NewFaker()), options)
4492+
defer chain.Stop()
4493+
4494+
chain.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), 0)
4495+
4496+
for i := 0; i < chainLength; i++ {
4497+
block := blocks[i]
4498+
blockReceipts := chain.GetReceiptsByHash(block.Hash())
4499+
chain.receiptsCache.Purge() // ugly hack
4500+
for txIndex, tx := range block.Body().Transactions {
4501+
receipt, err := chain.GetCanonicalReceipt(tx, block.Hash(), block.NumberU64(), uint64(txIndex))
4502+
if err != nil {
4503+
t.Fatalf("Unexpected error %v", err)
4504+
}
4505+
if !reflect.DeepEqual(receipts[i][txIndex], receipt) {
4506+
want := spew.Sdump(receipts[i][txIndex])
4507+
got := spew.Sdump(receipt)
4508+
t.Fatalf("Receipt is not matched, want %s, got: %s", want, got)
4509+
}
4510+
if !reflect.DeepEqual(blockReceipts[txIndex], receipt) {
4511+
want := spew.Sdump(blockReceipts[txIndex])
4512+
got := spew.Sdump(receipt)
4513+
t.Fatalf("Receipt is not matched, want %s, got: %s", want, got)
4514+
}
44074515
}
44084516
}
44094517
}

core/rawdb/accessors_chain.go

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -449,20 +449,25 @@ func ReadBodyRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue
449449
return data
450450
}
451451

452-
// ReadCanonicalBodyRLP retrieves the block body (transactions and uncles) for the canonical
453-
// block at number, in RLP encoding.
454-
func ReadCanonicalBodyRLP(db ethdb.Reader, number uint64) rlp.RawValue {
452+
// ReadCanonicalBodyRLP retrieves the block body (transactions and uncles) for the
453+
// canonical block at number, in RLP encoding. Optionally it takes the block hash
454+
// to avoid looking it up
455+
func ReadCanonicalBodyRLP(db ethdb.Reader, number uint64, hash *common.Hash) rlp.RawValue {
455456
var data []byte
456457
db.ReadAncients(func(reader ethdb.AncientReaderOp) error {
457458
data, _ = reader.Ancient(ChainFreezerBodiesTable, number)
458459
if len(data) > 0 {
459460
return nil
460461
}
461462
// Block is not in ancients, read from leveldb by hash and number.
462-
// Note: ReadCanonicalHash cannot be used here because it also
463-
// calls ReadAncients internally.
464-
hash, _ := db.Get(headerHashKey(number))
465-
data, _ = db.Get(blockBodyKey(number, common.BytesToHash(hash)))
463+
if hash != nil {
464+
data, _ = db.Get(blockBodyKey(number, *hash))
465+
} else {
466+
// Note: ReadCanonicalHash cannot be used here because it also
467+
// calls ReadAncients internally.
468+
hashBytes, _ := db.Get(headerHashKey(number))
469+
data, _ = db.Get(blockBodyKey(number, common.BytesToHash(hashBytes)))
470+
}
466471
return nil
467472
})
468473
return data
@@ -544,6 +549,29 @@ func ReadReceiptsRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawVa
544549
return data
545550
}
546551

552+
// ReadCanonicalReceiptsRLP retrieves the receipts RLP for the canonical block at
553+
// number, in RLP encoding. Optionally it takes the block hash to avoid looking it up.
554+
func ReadCanonicalReceiptsRLP(db ethdb.Reader, number uint64, hash *common.Hash) rlp.RawValue {
555+
var data []byte
556+
db.ReadAncients(func(reader ethdb.AncientReaderOp) error {
557+
data, _ = reader.Ancient(ChainFreezerReceiptTable, number)
558+
if len(data) > 0 {
559+
return nil
560+
}
561+
// Block is not in ancients, read from leveldb by hash and number.
562+
if hash != nil {
563+
data, _ = db.Get(blockReceiptsKey(number, *hash))
564+
} else {
565+
// Note: ReadCanonicalHash cannot be used here because it also
566+
// calls ReadAncients internally.
567+
hashBytes, _ := db.Get(headerHashKey(number))
568+
data, _ = db.Get(blockReceiptsKey(number, common.BytesToHash(hashBytes)))
569+
}
570+
return nil
571+
})
572+
return data
573+
}
574+
547575
// ReadRawReceipts retrieves all the transaction receipts belonging to a block.
548576
// The receipt metadata fields and the Bloom are not guaranteed to be populated,
549577
// so they should not be used. Use ReadReceipts instead if the metadata is needed.

core/rawdb/accessors_chain_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,9 @@ func TestAncientStorage(t *testing.T) {
441441
if blob := ReadReceiptsRLP(db, hash, number); len(blob) > 0 {
442442
t.Fatalf("non existent receipts returned")
443443
}
444+
if blob := ReadCanonicalReceiptsRLP(db, number, &hash); len(blob) > 0 {
445+
t.Fatalf("non existent receipts returned")
446+
}
444447

445448
// Write and verify the header in the database
446449
WriteAncientBlocks(db, []*types.Block{block}, types.EncodeBlockReceiptLists([]types.Receipts{nil}))
@@ -454,6 +457,9 @@ func TestAncientStorage(t *testing.T) {
454457
if blob := ReadReceiptsRLP(db, hash, number); len(blob) == 0 {
455458
t.Fatalf("no receipts returned")
456459
}
460+
if blob := ReadCanonicalReceiptsRLP(db, number, &hash); len(blob) == 0 {
461+
t.Fatalf("no receipts returned")
462+
}
457463

458464
// Use a fake hash for data retrieval, nothing should be returned.
459465
fakeHash := common.BytesToHash([]byte{0x01, 0x02, 0x03})

0 commit comments

Comments
 (0)