Skip to content

Commit 9a8da8a

Browse files
committed
core: implement GetReceiptByIndex
this is an attempt at reducing wasted effort in GetReceiptByHash by not decoding or deriving unrelated receipts
1 parent 09289fd commit 9a8da8a

File tree

10 files changed

+399
-57
lines changed

10 files changed

+399
-57
lines changed

core/blockchain_reader.go

Lines changed: 39 additions & 0 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,42 @@ func (bc *BlockChain) GetBlocksFromHash(hash common.Hash, n int) (blocks []*type
213216
return
214217
}
215218

219+
// GetReceiptByIndex allows fetching a receipt for a transaction that was already
220+
// looked up on the index.
221+
func (bc *BlockChain) GetReceiptByIndex(tx *types.Transaction, blockHash common.Hash, blockNumber, txIndex uint64) (*types.Receipt, error) {
222+
if receipts, ok := bc.receiptsCache.Get(blockHash); ok {
223+
if int(txIndex) >= len(receipts) {
224+
return nil, fmt.Errorf("receipt out of index, length: %d, index: %d", len(receipts), txIndex)
225+
}
226+
return receipts[int(txIndex)], nil
227+
}
228+
header := bc.GetHeader(blockHash, blockNumber)
229+
if header == nil {
230+
return nil, fmt.Errorf("block header is not found, %d, %x", blockNumber, blockHash)
231+
}
232+
var blobGasPrice *big.Int
233+
if header.ExcessBlobGas != nil {
234+
blobGasPrice = eip4844.CalcBlobFee(bc.chainConfig, header)
235+
}
236+
receipt, ctx, err := rawdb.ReadRawReceipt(bc.db, blockHash, blockNumber, txIndex)
237+
if err != nil {
238+
return nil, err
239+
}
240+
signer := types.MakeSigner(bc.chainConfig, new(big.Int).SetUint64(blockNumber), header.Time)
241+
receipt.DeriveFields(signer, types.DeriveReceiptContext{
242+
BlockHash: blockHash,
243+
BlockNumber: blockNumber,
244+
BlockTime: header.Time,
245+
BaseFee: header.BaseFee,
246+
BlobGasPrice: blobGasPrice,
247+
GasUsed: ctx.GasUsed,
248+
LogIndex: ctx.LogIndex,
249+
Tx: tx,
250+
TxIndex: uint(txIndex),
251+
})
252+
return receipt, nil
253+
}
254+
216255
// GetReceiptsByHash retrieves the receipts for all transactions in a given block.
217256
func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
218257
if receipts, ok := bc.receiptsCache.Get(hash); ok {

core/blockchain_test.go

Lines changed: 110 additions & 2 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
@@ -1016,17 +1019,35 @@ func testChainTxReorgs(t *testing.T, scheme string) {
10161019
if txn, _, _, _ := rawdb.ReadTransaction(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.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil {
10201023
t.Errorf("add %d: expected receipt to be found", i)
1024+
} else if rawRcpt, ctx, _ := rawdb.ReadRawReceipt(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}) {
10251037
if txn, _, _, _ := rawdb.ReadTransaction(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.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil {
10291041
t.Errorf("share %d: expected receipt to be found", i)
1042+
} else if rawRcpt, ctx, _ := rawdb.ReadRawReceipt(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.GetReceiptByIndex(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 TestGetReceiptByIndex(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.GetReceiptByIndex(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_indexes.go

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"bytes"
2121
"encoding/binary"
2222
"errors"
23+
"fmt"
2324
"math/big"
2425

2526
"github.com/ethereum/go-ethereum/common"
@@ -220,7 +221,89 @@ func ReadReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig)
220221
return nil, common.Hash{}, 0, 0
221222
}
222223

223-
// ReadFilterMapRow retrieves a filter map row at the given mapRowIndex
224+
// extractReceiptFields takes a raw RLP-encoded receipt blob and extracts
225+
// specific fields from it.
226+
func extractReceiptFields(receiptRLP rlp.RawValue) (uint64, uint, error) {
227+
receiptList, _, err := rlp.SplitList(receiptRLP)
228+
if err != nil {
229+
return 0, 0, err
230+
}
231+
// Decode the field: receipt status
232+
// for receipt before the byzantium fork:
233+
// - bytes: post state root
234+
// for receipt after the byzantium fork:
235+
// - bytes: receipt status flag
236+
_, _, rest, err := rlp.Split(receiptList)
237+
if err != nil {
238+
return 0, 0, err
239+
}
240+
// Decode the field: cumulative gas used (type: uint64)
241+
gasUsed, rest, err := rlp.SplitUint64(rest)
242+
if err != nil {
243+
return 0, 0, err
244+
}
245+
// Decode the field: logs (type: rlp list)
246+
logList, _, err := rlp.SplitList(rest)
247+
if err != nil {
248+
return 0, 0, err
249+
}
250+
logCount, err := rlp.CountValues(logList)
251+
if err != nil {
252+
return 0, 0, err
253+
}
254+
return gasUsed, uint(logCount), nil
255+
}
256+
257+
// RawReceiptContext carries the contextual information that is needed to derive
258+
// a complete receipt from a raw one.
259+
type RawReceiptContext struct {
260+
GasUsed uint64 // Amount of gas used by the associated transaction
261+
LogIndex uint // Starting index of the logs within the block
262+
}
263+
264+
// ReadRawReceipt reads a raw receipt at the specified position. It also returns
265+
// the gas used by the associated transaction and the starting index of the logs
266+
// within the block.
267+
func ReadRawReceipt(db ethdb.Reader, blockHash common.Hash, blockNumber, txIndex uint64) (*types.Receipt, RawReceiptContext, error) {
268+
receiptIt, err := rlp.NewListIterator(ReadReceiptsRLP(db, blockHash, blockNumber))
269+
if err != nil {
270+
return nil, RawReceiptContext{}, err
271+
}
272+
var (
273+
cumulativeGasUsed uint64
274+
logIndex uint
275+
)
276+
for i := uint64(0); i <= txIndex; i++ {
277+
// Unexpected iteration error
278+
if receiptIt.Err() != nil {
279+
return nil, RawReceiptContext{}, receiptIt.Err()
280+
}
281+
// Unexpected end of iteration
282+
if !receiptIt.Next() {
283+
return nil, RawReceiptContext{}, fmt.Errorf("receipt not found, %d, %x, %d", blockNumber, blockHash, txIndex)
284+
}
285+
if i == txIndex {
286+
var stored types.ReceiptForStorage
287+
if err := rlp.DecodeBytes(receiptIt.Value(), &stored); err != nil {
288+
return nil, RawReceiptContext{}, err
289+
}
290+
return (*types.Receipt)(&stored), RawReceiptContext{
291+
GasUsed: stored.CumulativeGasUsed - cumulativeGasUsed,
292+
LogIndex: logIndex,
293+
}, nil
294+
} else {
295+
gas, logs, err := extractReceiptFields(receiptIt.Value())
296+
if err != nil {
297+
return nil, RawReceiptContext{}, err
298+
}
299+
cumulativeGasUsed = gas
300+
logIndex += logs
301+
}
302+
}
303+
return nil, RawReceiptContext{}, fmt.Errorf("receipt not found, %d, %x, %d", blockNumber, blockHash, txIndex)
304+
}
305+
306+
// ReadFilterMapExtRow retrieves a filter map row at the given mapRowIndex
224307
// (see filtermaps.mapRowIndex for the storage index encoding).
225308
// Note that zero length rows are not stored in the database and therefore all
226309
// non-existent entries are interpreted as empty rows and return no error.
@@ -247,7 +330,7 @@ func ReadFilterMapExtRow(db ethdb.KeyValueReader, mapRowIndex uint64, bitLength
247330
return nil, err
248331
}
249332
if len(encRow)%byteLength != 0 {
250-
return nil, errors.New("Invalid encoded extended filter row length")
333+
return nil, errors.New("invalid encoded extended filter row length")
251334
}
252335
row := make([]uint32, len(encRow)/byteLength)
253336
var b [4]byte
@@ -318,7 +401,7 @@ func ReadFilterMapBaseRows(db ethdb.KeyValueReader, mapRowIndex uint64, rowCount
318401
return rows, nil
319402
}
320403

321-
// WriteFilterMapRow stores a filter map row at the given mapRowIndex or deletes
404+
// WriteFilterMapExtRow stores a filter map row at the given mapRowIndex or deletes
322405
// any existing entry if the row is empty.
323406
func WriteFilterMapExtRow(db ethdb.KeyValueWriter, mapRowIndex uint64, row []uint32, bitLength uint) {
324407
byteLength := int(bitLength) / 8

0 commit comments

Comments
 (0)