Merge branch 'ethereum:master' into master

This commit is contained in:
juga1980 2025-07-01 11:44:49 +02:00 committed by GitHub
commit eafecb2319
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 593 additions and 141 deletions

View file

@ -1,8 +1,13 @@
on: on:
schedule:
- cron: '0 15 * * *'
workflow_dispatch: workflow_dispatch:
### Note we cannot use cron-triggered builds right now, Gitea seems to have
### a few bugs in that area. So this workflow is scheduled using an external
### triggering mechanism and workflow_dispatch.
#
# schedule:
# - cron: '0 15 * * *'
jobs: jobs:
azure-cleanup: azure-cleanup:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View file

@ -1,11 +1,17 @@
on: on:
schedule:
- cron: '0 16 * * *'
push: push:
tags: tags:
- "v*" - "v*"
workflow_dispatch: workflow_dispatch:
### Note we cannot use cron-triggered builds right now, Gitea seems to have
### a few bugs in that area. So this workflow is scheduled using an external
### triggering mechanism and workflow_dispatch.
#
# schedule:
# - cron: '0 16 * * *'
jobs: jobs:
ppa: ppa:
name: PPA Upload name: PPA Upload

View file

@ -314,7 +314,7 @@ type BlockChain struct {
bodyCache *lru.Cache[common.Hash, *types.Body] bodyCache *lru.Cache[common.Hash, *types.Body]
bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue] bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue]
receiptsCache *lru.Cache[common.Hash, []*types.Receipt] receiptsCache *lru.Cache[common.Hash, []*types.Receipt] // Receipts cache with all fields derived
blockCache *lru.Cache[common.Hash, *types.Block] blockCache *lru.Cache[common.Hash, *types.Block]
txLookupLock sync.RWMutex txLookupLock sync.RWMutex

View file

@ -18,9 +18,12 @@ package core
import ( import (
"errors" "errors"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/state/snapshot"
@ -213,6 +216,44 @@ func (bc *BlockChain) GetBlocksFromHash(hash common.Hash, n int) (blocks []*type
return return
} }
// GetCanonicalReceipt allows fetching a receipt for a transaction that was
// already looked up on the index. Notably, only receipt in canonical chain
// is visible.
func (bc *BlockChain) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, txIndex uint64) (*types.Receipt, error) {
// The receipt retrieved from the cache contains all previously derived fields
if receipts, ok := bc.receiptsCache.Get(blockHash); ok {
if int(txIndex) >= len(receipts) {
return nil, fmt.Errorf("receipt out of index, length: %d, index: %d", len(receipts), txIndex)
}
return receipts[int(txIndex)], nil
}
header := bc.GetHeader(blockHash, blockNumber)
if header == nil {
return nil, fmt.Errorf("block header is not found, %d, %x", blockNumber, blockHash)
}
var blobGasPrice *big.Int
if header.ExcessBlobGas != nil {
blobGasPrice = eip4844.CalcBlobFee(bc.chainConfig, header)
}
receipt, ctx, err := rawdb.ReadCanonicalRawReceipt(bc.db, blockHash, blockNumber, txIndex)
if err != nil {
return nil, err
}
signer := types.MakeSigner(bc.chainConfig, new(big.Int).SetUint64(blockNumber), header.Time)
receipt.DeriveFields(signer, types.DeriveReceiptContext{
BlockHash: blockHash,
BlockNumber: blockNumber,
BlockTime: header.Time,
BaseFee: header.BaseFee,
BlobGasPrice: blobGasPrice,
GasUsed: ctx.GasUsed,
LogIndex: ctx.LogIndex,
Tx: tx,
TxIndex: uint(txIndex),
})
return receipt, nil
}
// GetReceiptsByHash retrieves the receipts for all transactions in a given block. // GetReceiptsByHash retrieves the receipts for all transactions in a given block.
func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts { func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
if receipts, ok := bc.receiptsCache.Get(hash); ok { if receipts, ok := bc.receiptsCache.Get(hash); ok {
@ -277,13 +318,15 @@ func (bc *BlockChain) GetAncestor(hash common.Hash, number, ancestor uint64, max
return bc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical) return bc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical)
} }
// GetTransactionLookup retrieves the lookup along with the transaction // GetCanonicalTransaction retrieves the lookup along with the transaction
// itself associate with the given transaction hash. // itself associate with the given transaction hash.
// //
// A null will be returned if the transaction is not found. This can be due to // A null will be returned if the transaction is not found. This can be due to
// the transaction indexer not being finished. The caller must explicitly check // the transaction indexer not being finished. The caller must explicitly check
// the indexer progress. // the indexer progress.
func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction) { //
// Notably, only the transaction in the canonical chain is visible.
func (bc *BlockChain) GetCanonicalTransaction(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction) {
bc.txLookupLock.RLock() bc.txLookupLock.RLock()
defer bc.txLookupLock.RUnlock() defer bc.txLookupLock.RUnlock()
@ -291,7 +334,7 @@ func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLoo
if item, exist := bc.txLookupCache.Get(hash); exist { if item, exist := bc.txLookupCache.Get(hash); exist {
return item.lookup, item.transaction return item.lookup, item.transaction
} }
tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(bc.db, hash) tx, blockHash, blockNumber, txIndex := rawdb.ReadCanonicalTransaction(bc.db, hash)
if tx == nil { if tx == nil {
return nil, nil return nil, nil
} }

View file

@ -25,10 +25,12 @@ import (
"math/rand" "math/rand"
"os" "os"
"path" "path"
"reflect"
"sync" "sync"
"testing" "testing"
"time" "time"
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/beacon"
@ -46,6 +48,7 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"github.com/stretchr/testify/assert"
) )
// So we can deterministically seed different blockchains // So we can deterministically seed different blockchains
@ -1004,29 +1007,47 @@ func testChainTxReorgs(t *testing.T, scheme string) {
// removed tx // removed tx
for i, tx := range (types.Transactions{pastDrop, freshDrop}) { for i, tx := range (types.Transactions{pastDrop, freshDrop}) {
if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn != nil { if txn, _, _, _ := rawdb.ReadCanonicalTransaction(db, tx.Hash()); txn != nil {
t.Errorf("drop %d: tx %v found while shouldn't have been", i, txn) t.Errorf("drop %d: tx %v found while shouldn't have been", i, txn)
} }
if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt != nil { if rcpt, _, _, _ := rawdb.ReadCanonicalReceipt(db, tx.Hash(), blockchain.Config()); rcpt != nil {
t.Errorf("drop %d: receipt %v found while shouldn't have been", i, rcpt) t.Errorf("drop %d: receipt %v found while shouldn't have been", i, rcpt)
} }
} }
// added tx // added tx
for i, tx := range (types.Transactions{pastAdd, freshAdd, futureAdd}) { for i, tx := range (types.Transactions{pastAdd, freshAdd, futureAdd}) {
if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil { if txn, _, _, _ := rawdb.ReadCanonicalTransaction(db, tx.Hash()); txn == nil {
t.Errorf("add %d: expected tx to be found", i) t.Errorf("add %d: expected tx to be found", i)
} }
if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil { if rcpt, _, _, index := rawdb.ReadCanonicalReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil {
t.Errorf("add %d: expected receipt to be found", i) t.Errorf("add %d: expected receipt to be found", i)
} else if rawRcpt, ctx, _ := rawdb.ReadCanonicalRawReceipt(db, rcpt.BlockHash, rcpt.BlockNumber.Uint64(), index); rawRcpt == nil {
t.Errorf("add %d: expected raw receipt to be found", i)
} else {
if rcpt.GasUsed != ctx.GasUsed {
t.Errorf("add %d, raw gasUsedSoFar doesn't make sense", i)
}
if len(rcpt.Logs) > 0 && rcpt.Logs[0].Index != ctx.LogIndex {
t.Errorf("add %d, raw startingLogIndex doesn't make sense", i)
}
} }
} }
// shared tx // shared tx
for i, tx := range (types.Transactions{postponed, swapped}) { for i, tx := range (types.Transactions{postponed, swapped}) {
if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil { if txn, _, _, _ := rawdb.ReadCanonicalTransaction(db, tx.Hash()); txn == nil {
t.Errorf("share %d: expected tx to be found", i) t.Errorf("share %d: expected tx to be found", i)
} }
if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil { if rcpt, _, _, index := rawdb.ReadCanonicalReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil {
t.Errorf("share %d: expected receipt to be found", i) t.Errorf("share %d: expected receipt to be found", i)
} else if rawRcpt, ctx, _ := rawdb.ReadCanonicalRawReceipt(db, rcpt.BlockHash, rcpt.BlockNumber.Uint64(), index); rawRcpt == nil {
t.Errorf("add %d: expected raw receipt to be found", i)
} else {
if rcpt.GasUsed != ctx.GasUsed {
t.Errorf("add %d, raw gasUsedSoFar doesn't make sense", i)
}
if len(rcpt.Logs) > 0 && rcpt.Logs[0].Index != ctx.LogIndex {
t.Errorf("add %d, raw startingLogIndex doesn't make sense", i)
}
} }
} }
} }
@ -4404,6 +4425,93 @@ func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64,
if receipts == nil || len(receipts) != 1 { if receipts == nil || len(receipts) != 1 {
t.Fatalf("Missed block receipts: %d, cutoff: %d", num, cutoffBlock.NumberU64()) t.Fatalf("Missed block receipts: %d, cutoff: %d", num, cutoffBlock.NumberU64())
} }
for indx, receipt := range receipts {
receiptByLookup, err := chain.GetCanonicalReceipt(body.Transactions[indx], receipt.BlockHash,
receipt.BlockNumber.Uint64(), uint64(indx))
assert.NoError(t, err)
assert.Equal(t, receipt, receiptByLookup)
}
}
}
}
func TestGetCanonicalReceipt(t *testing.T) {
const chainLength = 64
// Configure and generate a sample block chain
var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
address = crypto.PubkeyToAddress(key.PublicKey)
funds = big.NewInt(1000000000000000000)
gspec = &Genesis{
Config: params.MergedTestChainConfig,
Alloc: types.GenesisAlloc{address: {Balance: funds}},
BaseFee: big.NewInt(params.InitialBaseFee),
}
signer = types.LatestSigner(gspec.Config)
engine = beacon.New(ethash.NewFaker())
codeBin = common.FromHex("0x608060405234801561000f575f5ffd5b507f8ae1c8c6e5f91159d0bc1c4b9a47ce45301753843012cbe641e4456bfc73538b33426040516100419291906100ff565b60405180910390a1610139565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100778261004e565b9050919050565b6100878161006d565b82525050565b5f819050919050565b61009f8161008d565b82525050565b5f82825260208201905092915050565b7f436f6e7374727563746f72207761732063616c6c6564000000000000000000005f82015250565b5f6100e96016836100a5565b91506100f4826100b5565b602082019050919050565b5f6060820190506101125f83018561007e565b61011f6020830184610096565b8181036040830152610130816100dd565b90509392505050565b603e806101455f395ff3fe60806040525f5ffdfea2646970667358221220e8bc3c31e3ac337eab702e8fdfc1c71894f4df1af4221bcde4a2823360f403fb64736f6c634300081e0033")
)
_, blocks, receipts := GenerateChainWithGenesis(gspec, engine, chainLength, func(i int, block *BlockGen) {
// SPDX-License-Identifier: MIT
// pragma solidity ^0.8.0;
//
// contract ConstructorLogger {
// event ConstructorLog(address sender, uint256 timestamp, string message);
//
// constructor() {
// emit ConstructorLog(msg.sender, block.timestamp, "Constructor was called");
// }
// }
//
// 608060405234801561000f575f5ffd5b507f8ae1c8c6e5f91159d0bc1c4b9a47ce45301753843012cbe641e4456bfc73538b33426040516100419291906100ff565b60405180910390a1610139565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100778261004e565b9050919050565b6100878161006d565b82525050565b5f819050919050565b61009f8161008d565b82525050565b5f82825260208201905092915050565b7f436f6e7374727563746f72207761732063616c6c6564000000000000000000005f82015250565b5f6100e96016836100a5565b91506100f4826100b5565b602082019050919050565b5f6060820190506101125f83018561007e565b61011f6020830184610096565b8181036040830152610130816100dd565b90509392505050565b603e806101455f395ff3fe60806040525f5ffdfea2646970667358221220e8bc3c31e3ac337eab702e8fdfc1c71894f4df1af4221bcde4a2823360f403fb64736f6c634300081e0033
nonce := block.TxNonce(address)
tx, err := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 100_000, block.header.BaseFee, codeBin), signer, key)
if err != nil {
panic(err)
}
block.AddTx(tx)
tx2, err := types.SignTx(types.NewContractCreation(nonce+1, big.NewInt(0), 100_000, block.header.BaseFee, codeBin), signer, key)
if err != nil {
panic(err)
}
block.AddTx(tx2)
tx3, err := types.SignTx(types.NewContractCreation(nonce+2, big.NewInt(0), 100_000, block.header.BaseFee, codeBin), signer, key)
if err != nil {
panic(err)
}
block.AddTx(tx3)
})
db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
defer db.Close()
options := DefaultConfig().WithStateScheme(rawdb.PathScheme)
chain, _ := NewBlockChain(db, gspec, beacon.New(ethash.NewFaker()), options)
defer chain.Stop()
chain.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), 0)
for i := 0; i < chainLength; i++ {
block := blocks[i]
blockReceipts := chain.GetReceiptsByHash(block.Hash())
chain.receiptsCache.Purge() // ugly hack
for txIndex, tx := range block.Body().Transactions {
receipt, err := chain.GetCanonicalReceipt(tx, block.Hash(), block.NumberU64(), uint64(txIndex))
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if !reflect.DeepEqual(receipts[i][txIndex], receipt) {
want := spew.Sdump(receipts[i][txIndex])
got := spew.Sdump(receipt)
t.Fatalf("Receipt is not matched, want %s, got: %s", want, got)
}
if !reflect.DeepEqual(blockReceipts[txIndex], receipt) {
want := spew.Sdump(blockReceipts[txIndex])
got := spew.Sdump(receipt)
t.Fatalf("Receipt is not matched, want %s, got: %s", want, got)
}
} }
} }
} }

View file

@ -55,7 +55,9 @@ func NewChainView(chain blockchain, number uint64, hash common.Hash) *ChainView
headNumber: number, headNumber: number,
hashes: []common.Hash{hash}, hashes: []common.Hash{hash},
} }
cv.extendNonCanonical() if !cv.extendNonCanonical() {
return nil
}
return cv return cv
} }
@ -129,7 +131,11 @@ func (cv *ChainView) SharedRange(cv2 *ChainView) common.Range[uint64] {
return common.Range[uint64]{} return common.Range[uint64]{}
} }
var sharedLen uint64 var sharedLen uint64
for n := min(cv.headNumber+1-uint64(len(cv.hashes)), cv2.headNumber+1-uint64(len(cv2.hashes))); n <= cv.headNumber && n <= cv2.headNumber && cv.blockHash(n) == cv2.blockHash(n); n++ { for n := min(cv.headNumber+1-uint64(len(cv.hashes)), cv2.headNumber+1-uint64(len(cv2.hashes))); n <= cv.headNumber && n <= cv2.headNumber; n++ {
h1, h2 := cv.blockHash(n), cv2.blockHash(n)
if h1 != h2 || h1 == (common.Hash{}) {
break
}
sharedLen = n + 1 sharedLen = n + 1
} }
return common.NewRange(0, sharedLen) return common.NewRange(0, sharedLen)
@ -153,10 +159,13 @@ func matchViews(cv1, cv2 *ChainView, number uint64) bool {
if cv1.headNumber < number || cv2.headNumber < number { if cv1.headNumber < number || cv2.headNumber < number {
return false return false
} }
var h1, h2 common.Hash
if number == cv1.headNumber || number == cv2.headNumber { if number == cv1.headNumber || number == cv2.headNumber {
return cv1.BlockId(number) == cv2.BlockId(number) h1, h2 = cv1.BlockId(number), cv2.BlockId(number)
} else {
h1, h2 = cv1.BlockHash(number), cv2.BlockHash(number)
} }
return cv1.BlockHash(number) == cv2.BlockHash(number) return h1 == h2 && h1 != common.Hash{}
} }
// extendNonCanonical checks whether the previously known reverse list of head // extendNonCanonical checks whether the previously known reverse list of head
@ -175,7 +184,10 @@ func (cv *ChainView) extendNonCanonical() bool {
} }
header := cv.chain.GetHeader(hash, number) header := cv.chain.GetHeader(hash, number)
if header == nil { if header == nil {
log.Error("Header not found", "number", number, "hash", hash) // Header not found - this can happen after debug_setHead operations
// where blocks have been deleted. Return false to indicate the chain view
// is no longer valid rather than logging repeated errors.
log.Debug("Header not found during chain view extension", "number", number, "hash", hash)
return false return false
} }
cv.hashes = append(cv.hashes, header.ParentHash) cv.hashes = append(cv.hashes, header.ParentHash)

View file

@ -268,9 +268,9 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f
if f.indexedRange.hasIndexedBlocks() { if f.indexedRange.hasIndexedBlocks() {
log.Info("Initialized log indexer", log.Info("Initialized log indexer",
"first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(), "firstblock", f.indexedRange.blocks.First(), "lastblock", f.indexedRange.blocks.Last(),
"first map", f.indexedRange.maps.First(), "last map", f.indexedRange.maps.Last(), "firstmap", f.indexedRange.maps.First(), "lastmap", f.indexedRange.maps.Last(),
"head indexed", f.indexedRange.headIndexed) "headindexed", f.indexedRange.headIndexed)
} }
return f return f
} }

View file

@ -380,9 +380,9 @@ func (f *FilterMaps) tryUnindexTail() (bool, error) {
} }
if f.startedTailUnindex && f.indexedRange.hasIndexedBlocks() { if f.startedTailUnindex && f.indexedRange.hasIndexedBlocks() {
log.Info("Log index tail unindexing finished", log.Info("Log index tail unindexing finished",
"first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(), "firstblock", f.indexedRange.blocks.First(), "lastblock", f.indexedRange.blocks.Last(),
"removed maps", f.indexedRange.maps.First()-f.ptrTailUnindexMap, "removedmaps", f.indexedRange.maps.First()-f.ptrTailUnindexMap,
"removed blocks", f.indexedRange.blocks.First()-f.tailPartialBlocks()-f.ptrTailUnindexBlock, "removedblocks", f.indexedRange.blocks.First()-f.tailPartialBlocks()-f.ptrTailUnindexBlock,
"elapsed", common.PrettyDuration(time.Since(f.startedTailUnindexAt))) "elapsed", common.PrettyDuration(time.Since(f.startedTailUnindexAt)))
f.startedTailUnindex = false f.startedTailUnindex = false
} }

View file

@ -449,9 +449,10 @@ func ReadBodyRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue
return data return data
} }
// ReadCanonicalBodyRLP retrieves the block body (transactions and uncles) for the canonical // ReadCanonicalBodyRLP retrieves the block body (transactions and uncles) for the
// block at number, in RLP encoding. // canonical block at number, in RLP encoding. Optionally it takes the block hash
func ReadCanonicalBodyRLP(db ethdb.Reader, number uint64) rlp.RawValue { // to avoid looking it up
func ReadCanonicalBodyRLP(db ethdb.Reader, number uint64, hash *common.Hash) rlp.RawValue {
var data []byte var data []byte
db.ReadAncients(func(reader ethdb.AncientReaderOp) error { db.ReadAncients(func(reader ethdb.AncientReaderOp) error {
data, _ = reader.Ancient(ChainFreezerBodiesTable, number) data, _ = reader.Ancient(ChainFreezerBodiesTable, number)
@ -459,10 +460,14 @@ func ReadCanonicalBodyRLP(db ethdb.Reader, number uint64) rlp.RawValue {
return nil return nil
} }
// Block is not in ancients, read from leveldb by hash and number. // Block is not in ancients, read from leveldb by hash and number.
if hash != nil {
data, _ = db.Get(blockBodyKey(number, *hash))
} else {
// Note: ReadCanonicalHash cannot be used here because it also // Note: ReadCanonicalHash cannot be used here because it also
// calls ReadAncients internally. // calls ReadAncients internally.
hash, _ := db.Get(headerHashKey(number)) hashBytes, _ := db.Get(headerHashKey(number))
data, _ = db.Get(blockBodyKey(number, common.BytesToHash(hash))) data, _ = db.Get(blockBodyKey(number, common.BytesToHash(hashBytes)))
}
return nil return nil
}) })
return data return data
@ -544,6 +549,29 @@ func ReadReceiptsRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawVa
return data return data
} }
// ReadCanonicalReceiptsRLP retrieves the receipts RLP for the canonical block at
// number, in RLP encoding. Optionally it takes the block hash to avoid looking it up.
func ReadCanonicalReceiptsRLP(db ethdb.Reader, number uint64, hash *common.Hash) rlp.RawValue {
var data []byte
db.ReadAncients(func(reader ethdb.AncientReaderOp) error {
data, _ = reader.Ancient(ChainFreezerReceiptTable, number)
if len(data) > 0 {
return nil
}
// Block is not in ancients, read from leveldb by hash and number.
if hash != nil {
data, _ = db.Get(blockReceiptsKey(number, *hash))
} else {
// Note: ReadCanonicalHash cannot be used here because it also
// calls ReadAncients internally.
hashBytes, _ := db.Get(headerHashKey(number))
data, _ = db.Get(blockReceiptsKey(number, common.BytesToHash(hashBytes)))
}
return nil
})
return data
}
// ReadRawReceipts retrieves all the transaction receipts belonging to a block. // ReadRawReceipts retrieves all the transaction receipts belonging to a block.
// The receipt metadata fields and the Bloom are not guaranteed to be populated, // The receipt metadata fields and the Bloom are not guaranteed to be populated,
// so they should not be used. Use ReadReceipts instead if the metadata is needed. // so they should not be used. Use ReadReceipts instead if the metadata is needed.

View file

@ -441,6 +441,9 @@ func TestAncientStorage(t *testing.T) {
if blob := ReadReceiptsRLP(db, hash, number); len(blob) > 0 { if blob := ReadReceiptsRLP(db, hash, number); len(blob) > 0 {
t.Fatalf("non existent receipts returned") t.Fatalf("non existent receipts returned")
} }
if blob := ReadCanonicalReceiptsRLP(db, number, &hash); len(blob) > 0 {
t.Fatalf("non existent receipts returned")
}
// Write and verify the header in the database // Write and verify the header in the database
WriteAncientBlocks(db, []*types.Block{block}, types.EncodeBlockReceiptLists([]types.Receipts{nil})) WriteAncientBlocks(db, []*types.Block{block}, types.EncodeBlockReceiptLists([]types.Receipts{nil}))
@ -454,6 +457,9 @@ func TestAncientStorage(t *testing.T) {
if blob := ReadReceiptsRLP(db, hash, number); len(blob) == 0 { if blob := ReadReceiptsRLP(db, hash, number); len(blob) == 0 {
t.Fatalf("no receipts returned") t.Fatalf("no receipts returned")
} }
if blob := ReadCanonicalReceiptsRLP(db, number, &hash); len(blob) == 0 {
t.Fatalf("no receipts returned")
}
// Use a fake hash for data retrieval, nothing should be returned. // Use a fake hash for data retrieval, nothing should be returned.
fakeHash := common.BytesToHash([]byte{0x01, 0x02, 0x03}) fakeHash := common.BytesToHash([]byte{0x01, 0x02, 0x03})

View file

@ -20,6 +20,7 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -169,9 +170,10 @@ func findTxInBlockBody(blockbody rlp.RawValue, target common.Hash) (*types.Trans
return nil, 0, errors.New("transaction not found") return nil, 0, errors.New("transaction not found")
} }
// ReadTransaction retrieves a specific transaction from the database, along with // ReadCanonicalTransaction retrieves a specific transaction from the database, along
// its added positional metadata. // with its added positional metadata. Notably, only the transaction in the canonical
func ReadTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) { // chain is visible.
func ReadCanonicalTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) {
blockNumber := ReadTxLookupEntry(db, hash) blockNumber := ReadTxLookupEntry(db, hash)
if blockNumber == nil { if blockNumber == nil {
return nil, common.Hash{}, 0, 0 return nil, common.Hash{}, 0, 0
@ -180,7 +182,7 @@ func ReadTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, com
if blockHash == (common.Hash{}) { if blockHash == (common.Hash{}) {
return nil, common.Hash{}, 0, 0 return nil, common.Hash{}, 0, 0
} }
bodyRLP := ReadBodyRLP(db, blockHash, *blockNumber) bodyRLP := ReadCanonicalBodyRLP(db, *blockNumber, &blockHash)
if bodyRLP == nil { if bodyRLP == nil {
log.Error("Transaction referenced missing", "number", *blockNumber, "hash", blockHash) log.Error("Transaction referenced missing", "number", *blockNumber, "hash", blockHash)
return nil, common.Hash{}, 0, 0 return nil, common.Hash{}, 0, 0
@ -193,9 +195,10 @@ func ReadTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, com
return tx, blockHash, *blockNumber, txIndex return tx, blockHash, *blockNumber, txIndex
} }
// ReadReceipt retrieves a specific transaction receipt from the database, along with // ReadCanonicalReceipt retrieves a specific transaction receipt from the database,
// its added positional metadata. // along with its added positional metadata. Notably, only the receipt in the canonical
func ReadReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig) (*types.Receipt, common.Hash, uint64, uint64) { // chain is visible.
func ReadCanonicalReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig) (*types.Receipt, common.Hash, uint64, uint64) {
// Retrieve the context of the receipt based on the transaction hash // Retrieve the context of the receipt based on the transaction hash
blockNumber := ReadTxLookupEntry(db, hash) blockNumber := ReadTxLookupEntry(db, hash)
if blockNumber == nil { if blockNumber == nil {
@ -220,7 +223,91 @@ func ReadReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig)
return nil, common.Hash{}, 0, 0 return nil, common.Hash{}, 0, 0
} }
// ReadFilterMapRow retrieves a filter map row at the given mapRowIndex // extractReceiptFields takes a raw RLP-encoded receipt blob and extracts
// specific fields from it.
func extractReceiptFields(receiptRLP rlp.RawValue) (uint64, uint, error) {
receiptList, _, err := rlp.SplitList(receiptRLP)
if err != nil {
return 0, 0, err
}
// Decode the field: receipt status
// for receipt before the byzantium fork:
// - bytes: post state root
// for receipt after the byzantium fork:
// - bytes: receipt status flag
_, _, rest, err := rlp.Split(receiptList)
if err != nil {
return 0, 0, err
}
// Decode the field: cumulative gas used (type: uint64)
gasUsed, rest, err := rlp.SplitUint64(rest)
if err != nil {
return 0, 0, err
}
// Decode the field: logs (type: rlp list)
logList, _, err := rlp.SplitList(rest)
if err != nil {
return 0, 0, err
}
logCount, err := rlp.CountValues(logList)
if err != nil {
return 0, 0, err
}
return gasUsed, uint(logCount), nil
}
// RawReceiptContext carries the contextual information that is needed to derive
// a complete receipt from a raw one.
type RawReceiptContext struct {
GasUsed uint64 // Amount of gas used by the associated transaction
LogIndex uint // Starting index of the logs within the block
}
// ReadCanonicalRawReceipt reads a raw receipt at the specified position. It also
// returns the gas used by the associated transaction and the starting index of
// the logs within the block. The main difference with ReadCanonicalReceipt is
// that the additional positional fields are not directly included in the receipt.
// Notably, only receipts from the canonical chain are visible.
func ReadCanonicalRawReceipt(db ethdb.Reader, blockHash common.Hash, blockNumber, txIndex uint64) (*types.Receipt, RawReceiptContext, error) {
receiptIt, err := rlp.NewListIterator(ReadCanonicalReceiptsRLP(db, blockNumber, &blockHash))
if err != nil {
return nil, RawReceiptContext{}, err
}
var (
cumulativeGasUsed uint64
logIndex uint
)
for i := uint64(0); i <= txIndex; i++ {
// Unexpected iteration error
if receiptIt.Err() != nil {
return nil, RawReceiptContext{}, receiptIt.Err()
}
// Unexpected end of iteration
if !receiptIt.Next() {
return nil, RawReceiptContext{}, fmt.Errorf("receipt not found, %d, %x, %d", blockNumber, blockHash, txIndex)
}
if i == txIndex {
var stored types.ReceiptForStorage
if err := rlp.DecodeBytes(receiptIt.Value(), &stored); err != nil {
return nil, RawReceiptContext{}, err
}
return (*types.Receipt)(&stored), RawReceiptContext{
GasUsed: stored.CumulativeGasUsed - cumulativeGasUsed,
LogIndex: logIndex,
}, nil
} else {
gas, logs, err := extractReceiptFields(receiptIt.Value())
if err != nil {
return nil, RawReceiptContext{}, err
}
cumulativeGasUsed = gas
logIndex += logs
}
}
return nil, RawReceiptContext{}, fmt.Errorf("receipt not found, %d, %x, %d", blockNumber, blockHash, txIndex)
}
// ReadFilterMapExtRow retrieves a filter map row at the given mapRowIndex
// (see filtermaps.mapRowIndex for the storage index encoding). // (see filtermaps.mapRowIndex for the storage index encoding).
// Note that zero length rows are not stored in the database and therefore all // Note that zero length rows are not stored in the database and therefore all
// non-existent entries are interpreted as empty rows and return no error. // non-existent entries are interpreted as empty rows and return no error.
@ -247,7 +334,7 @@ func ReadFilterMapExtRow(db ethdb.KeyValueReader, mapRowIndex uint64, bitLength
return nil, err return nil, err
} }
if len(encRow)%byteLength != 0 { if len(encRow)%byteLength != 0 {
return nil, errors.New("Invalid encoded extended filter row length") return nil, errors.New("invalid encoded extended filter row length")
} }
row := make([]uint32, len(encRow)/byteLength) row := make([]uint32, len(encRow)/byteLength)
var b [4]byte var b [4]byte
@ -318,7 +405,7 @@ func ReadFilterMapBaseRows(db ethdb.KeyValueReader, mapRowIndex uint64, rowCount
return rows, nil return rows, nil
} }
// WriteFilterMapRow stores a filter map row at the given mapRowIndex or deletes // WriteFilterMapExtRow stores a filter map row at the given mapRowIndex or deletes
// any existing entry if the row is empty. // any existing entry if the row is empty.
func WriteFilterMapExtRow(db ethdb.KeyValueWriter, mapRowIndex uint64, row []uint32, bitLength uint) { func WriteFilterMapExtRow(db ethdb.KeyValueWriter, mapRowIndex uint64, row []uint32, bitLength uint) {
byteLength := int(bitLength) / 8 byteLength := int(bitLength) / 8

View file

@ -17,6 +17,7 @@
package rawdb package rawdb
import ( import (
"errors"
"math/big" "math/big"
"testing" "testing"
@ -88,7 +89,7 @@ func TestLookupStorage(t *testing.T) {
// Check that no transactions entries are in a pristine database // Check that no transactions entries are in a pristine database
for i, tx := range txs { for i, tx := range txs {
if txn, _, _, _ := ReadTransaction(db, tx.Hash()); txn != nil { if txn, _, _, _ := ReadCanonicalTransaction(db, tx.Hash()); txn != nil {
t.Fatalf("tx #%d [%x]: non existent transaction returned: %v", i, tx.Hash(), txn) t.Fatalf("tx #%d [%x]: non existent transaction returned: %v", i, tx.Hash(), txn)
} }
} }
@ -98,7 +99,7 @@ func TestLookupStorage(t *testing.T) {
tc.writeTxLookupEntriesByBlock(db, block) tc.writeTxLookupEntriesByBlock(db, block)
for i, tx := range txs { for i, tx := range txs {
if txn, hash, number, index := ReadTransaction(db, tx.Hash()); txn == nil { if txn, hash, number, index := ReadCanonicalTransaction(db, tx.Hash()); txn == nil {
t.Fatalf("tx #%d [%x]: transaction not found", i, tx.Hash()) t.Fatalf("tx #%d [%x]: transaction not found", i, tx.Hash())
} else { } else {
if hash != block.Hash() || number != block.NumberU64() || index != uint64(i) { if hash != block.Hash() || number != block.NumberU64() || index != uint64(i) {
@ -112,7 +113,7 @@ func TestLookupStorage(t *testing.T) {
// Delete the transactions and check purge // Delete the transactions and check purge
for i, tx := range txs { for i, tx := range txs {
DeleteTxLookupEntry(db, tx.Hash()) DeleteTxLookupEntry(db, tx.Hash())
if txn, _, _, _ := ReadTransaction(db, tx.Hash()); txn != nil { if txn, _, _, _ := ReadCanonicalTransaction(db, tx.Hash()); txn != nil {
t.Fatalf("tx #%d [%x]: deleted transaction returned: %v", i, tx.Hash(), txn) t.Fatalf("tx #%d [%x]: deleted transaction returned: %v", i, tx.Hash(), txn)
} }
} }
@ -219,3 +220,80 @@ func TestFindTxInBlockBody(t *testing.T) {
} }
} }
} }
func TestExtractReceiptFields(t *testing.T) {
receiptWithPostState := types.ReceiptForStorage(types.Receipt{
Type: types.LegacyTxType,
PostState: []byte{0x1, 0x2, 0x3},
CumulativeGasUsed: 100,
})
receiptWithPostStateBlob, _ := rlp.EncodeToBytes(&receiptWithPostState)
receiptNoLogs := types.ReceiptForStorage(types.Receipt{
Type: types.LegacyTxType,
Status: types.ReceiptStatusSuccessful,
CumulativeGasUsed: 100,
})
receiptNoLogBlob, _ := rlp.EncodeToBytes(&receiptNoLogs)
receiptWithLogs := types.ReceiptForStorage(types.Receipt{
Type: types.LegacyTxType,
Status: types.ReceiptStatusSuccessful,
CumulativeGasUsed: 100,
Logs: []*types.Log{
{
Address: common.BytesToAddress([]byte{0x1}),
Topics: []common.Hash{
common.BytesToHash([]byte{0x1}),
},
Data: []byte{0x1},
},
{
Address: common.BytesToAddress([]byte{0x2}),
Topics: []common.Hash{
common.BytesToHash([]byte{0x2}),
},
Data: []byte{0x2},
},
},
})
receiptWithLogBlob, _ := rlp.EncodeToBytes(&receiptWithLogs)
invalidReceipt := types.ReceiptForStorage(types.Receipt{
Type: types.LegacyTxType,
Status: types.ReceiptStatusSuccessful,
CumulativeGasUsed: 100,
})
invalidReceiptBlob, _ := rlp.EncodeToBytes(&invalidReceipt)
invalidReceiptBlob[len(invalidReceiptBlob)-1] = 0xf
var cases = []struct {
logs rlp.RawValue
expErr error
expGasUsed uint64
expLogs uint
}{
{receiptWithPostStateBlob, nil, 100, 0},
{receiptNoLogBlob, nil, 100, 0},
{receiptWithLogBlob, nil, 100, 2},
{invalidReceiptBlob, rlp.ErrExpectedList, 100, 0},
}
for _, c := range cases {
gasUsed, logs, err := extractReceiptFields(c.logs)
if c.expErr != nil {
if !errors.Is(err, c.expErr) {
t.Fatalf("Unexpected error, want: %v, got: %v", c.expErr, err)
}
} else {
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if gasUsed != c.expGasUsed {
t.Fatalf("Unexpected gas used, want %d, got %d", c.expGasUsed, gasUsed)
}
if logs != c.expLogs {
t.Fatalf("Unexpected logs, want %d, got %d", c.expLogs, logs)
}
}
}
}

View file

@ -118,7 +118,7 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool
} }
defer close(rlpCh) defer close(rlpCh)
for n != end { for n != end {
data := ReadCanonicalBodyRLP(db, n) data := ReadCanonicalBodyRLP(db, n, nil)
// Feed the block to the aggregator, or abort on interrupt // Feed the block to the aggregator, or abort on interrupt
select { select {
case rlpCh <- &numberRlp{n, data}: case rlpCh <- &numberRlp{n, data}:

View file

@ -258,6 +258,62 @@ func (r *Receipt) Size() common.StorageSize {
return size return size
} }
// DeriveReceiptContext holds the contextual information needed to derive a receipt
type DeriveReceiptContext struct {
BlockHash common.Hash
BlockNumber uint64
BlockTime uint64
BaseFee *big.Int
BlobGasPrice *big.Int
GasUsed uint64
LogIndex uint // Number of logs in the block until this receipt
Tx *Transaction
TxIndex uint
}
// DeriveFields fills the receipt with computed fields based on consensus
// data and contextual infos like containing block and transactions.
func (r *Receipt) DeriveFields(signer Signer, context DeriveReceiptContext) {
// The transaction type and hash can be retrieved from the transaction itself
r.Type = context.Tx.Type()
r.TxHash = context.Tx.Hash()
r.GasUsed = context.GasUsed
r.EffectiveGasPrice = context.Tx.inner.effectiveGasPrice(new(big.Int), context.BaseFee)
// EIP-4844 blob transaction fields
if context.Tx.Type() == BlobTxType {
r.BlobGasUsed = context.Tx.BlobGas()
r.BlobGasPrice = context.BlobGasPrice
}
// Block location fields
r.BlockHash = context.BlockHash
r.BlockNumber = new(big.Int).SetUint64(context.BlockNumber)
r.TransactionIndex = context.TxIndex
// The contract address can be derived from the transaction itself
if context.Tx.To() == nil {
// Deriving the signer is expensive, only do if it's actually needed
from, _ := Sender(signer, context.Tx)
r.ContractAddress = crypto.CreateAddress(from, context.Tx.Nonce())
} else {
r.ContractAddress = common.Address{}
}
// The derived log fields can simply be set from the block and transaction
logIndex := context.LogIndex
for j := 0; j < len(r.Logs); j++ {
r.Logs[j].BlockNumber = context.BlockNumber
r.Logs[j].BlockHash = context.BlockHash
r.Logs[j].BlockTimestamp = context.BlockTime
r.Logs[j].TxHash = r.TxHash
r.Logs[j].TxIndex = context.TxIndex
r.Logs[j].Index = logIndex
logIndex++
}
// Also derive the Bloom if not derived yet
r.Bloom = CreateBloom(r)
}
// ReceiptForStorage is a wrapper around a Receipt with RLP serialization // ReceiptForStorage is a wrapper around a Receipt with RLP serialization
// that omits the Bloom field. The Bloom field is recomputed by DeriveFields. // that omits the Bloom field. The Bloom field is recomputed by DeriveFields.
type ReceiptForStorage Receipt type ReceiptForStorage Receipt
@ -323,58 +379,30 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) {
// DeriveFields fills the receipts with their computed fields based on consensus // DeriveFields fills the receipts with their computed fields based on consensus
// data and contextual infos like containing block and transactions. // data and contextual infos like containing block and transactions.
func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, time uint64, baseFee *big.Int, blobGasPrice *big.Int, txs []*Transaction) error { func (rs Receipts) DeriveFields(config *params.ChainConfig, blockHash common.Hash, blockNumber uint64, blockTime uint64, baseFee *big.Int, blobGasPrice *big.Int, txs []*Transaction) error {
signer := MakeSigner(config, new(big.Int).SetUint64(number), time) signer := MakeSigner(config, new(big.Int).SetUint64(blockNumber), blockTime)
logIndex := uint(0) logIndex := uint(0)
if len(txs) != len(rs) { if len(txs) != len(rs) {
return errors.New("transaction and receipt count mismatch") return errors.New("transaction and receipt count mismatch")
} }
for i := 0; i < len(rs); i++ { for i := 0; i < len(rs); i++ {
// The transaction type and hash can be retrieved from the transaction itself var cumulativeGasUsed uint64
rs[i].Type = txs[i].Type() if i > 0 {
rs[i].TxHash = txs[i].Hash() cumulativeGasUsed = rs[i-1].CumulativeGasUsed
rs[i].EffectiveGasPrice = txs[i].inner.effectiveGasPrice(new(big.Int), baseFee)
// EIP-4844 blob transaction fields
if txs[i].Type() == BlobTxType {
rs[i].BlobGasUsed = txs[i].BlobGas()
rs[i].BlobGasPrice = blobGasPrice
} }
rs[i].DeriveFields(signer, DeriveReceiptContext{
// block location fields BlockHash: blockHash,
rs[i].BlockHash = hash BlockNumber: blockNumber,
rs[i].BlockNumber = new(big.Int).SetUint64(number) BlockTime: blockTime,
rs[i].TransactionIndex = uint(i) BaseFee: baseFee,
BlobGasPrice: blobGasPrice,
// The contract address can be derived from the transaction itself GasUsed: rs[i].CumulativeGasUsed - cumulativeGasUsed,
if txs[i].To() == nil { LogIndex: logIndex,
// Deriving the signer is expensive, only do if it's actually needed Tx: txs[i],
from, _ := Sender(signer, txs[i]) TxIndex: uint(i),
rs[i].ContractAddress = crypto.CreateAddress(from, txs[i].Nonce()) })
} else { logIndex += uint(len(rs[i].Logs))
rs[i].ContractAddress = common.Address{}
}
// The used gas can be calculated based on previous r
if i == 0 {
rs[i].GasUsed = rs[i].CumulativeGasUsed
} else {
rs[i].GasUsed = rs[i].CumulativeGasUsed - rs[i-1].CumulativeGasUsed
}
// The derived log fields can simply be set from the block and transaction
for j := 0; j < len(rs[i].Logs); j++ {
rs[i].Logs[j].BlockNumber = number
rs[i].Logs[j].BlockHash = hash
rs[i].Logs[j].BlockTimestamp = time
rs[i].Logs[j].TxHash = rs[i].TxHash
rs[i].Logs[j].TxIndex = uint(i)
rs[i].Logs[j].Index = logIndex
logIndex++
}
// also derive the Bloom if not derived yet
rs[i].Bloom = CreateBloom(rs[i])
} }
return nil return nil
} }

View file

@ -282,6 +282,10 @@ func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (type
return b.eth.blockchain.GetReceiptsByHash(hash), nil return b.eth.blockchain.GetReceiptsByHash(hash), nil
} }
func (b *EthAPIBackend) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) {
return b.eth.blockchain.GetCanonicalReceipt(tx, blockHash, blockNumber, blockIndex)
}
func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) { func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) {
return rawdb.ReadLogs(b.eth.chainDb, hash, number), nil return rawdb.ReadLogs(b.eth.chainDb, hash, number), nil
} }
@ -354,14 +358,16 @@ func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction
return b.eth.txPool.Get(hash) return b.eth.txPool.Get(hash)
} }
// GetTransaction retrieves the lookup along with the transaction itself associate // GetCanonicalTransaction retrieves the lookup along with the transaction itself
// with the given transaction hash. // associate with the given transaction hash.
// //
// A null will be returned if the transaction is not found. The transaction is not // A null will be returned if the transaction is not found. The transaction is not
// existent from the node's perspective. This can be due to the transaction indexer // existent from the node's perspective. This can be due to the transaction indexer
// not being finished. The caller must explicitly check the indexer progress. // not being finished. The caller must explicitly check the indexer progress.
func (b *EthAPIBackend) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) { //
lookup, tx := b.eth.blockchain.GetTransactionLookup(txHash) // Notably, only the transaction in the canonical chain is visible.
func (b *EthAPIBackend) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
lookup, tx := b.eth.blockchain.GetCanonicalTransaction(txHash)
if lookup == nil || tx == nil { if lookup == nil || tx == nil {
return false, nil, common.Hash{}, 0, 0 return false, nil, common.Hash{}, 0, 0
} }

View file

@ -648,7 +648,7 @@ func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHas
case executionRequests == nil: case executionRequests == nil:
return invalidStatus, paramsErr("nil executionRequests post-prague") return invalidStatus, paramsErr("nil executionRequests post-prague")
case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka): case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka):
return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads") return invalidStatus, unsupportedForkErr("newPayloadV4 must only be called for prague payloads")
} }
requests := convertRequests(executionRequests) requests := convertRequests(executionRequests)
if err := validateRequests(requests); err != nil { if err := validateRequests(requests); err != nil {

View file

@ -152,7 +152,7 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV4(params engine.ExecutableData, v
case executionRequests == nil: case executionRequests == nil:
return invalidStatus, paramsErr("nil executionRequests post-prague") return invalidStatus, paramsErr("nil executionRequests post-prague")
case !api.checkFork(params.Timestamp, forks.Prague): case !api.checkFork(params.Timestamp, forks.Prague):
return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads") return invalidStatus, unsupportedForkErr("newPayloadV4 must only be called for prague payloads")
} }
requests := convertRequests(executionRequests) requests := convertRequests(executionRequests)
if err := validateRequests(requests); err != nil { if err := validateRequests(requests); err != nil {

View file

@ -40,13 +40,17 @@ var (
errInvalidBlockRange = errors.New("invalid block range params") errInvalidBlockRange = errors.New("invalid block range params")
errPendingLogsUnsupported = errors.New("pending logs are not supported") errPendingLogsUnsupported = errors.New("pending logs are not supported")
errExceedMaxTopics = errors.New("exceed max topics") errExceedMaxTopics = errors.New("exceed max topics")
errExceedMaxAddresses = errors.New("exceed max addresses")
) )
// The maximum number of topic criteria allowed, vm.LOG4 - vm.LOG0 const (
const maxTopics = 4 // The maximum number of addresses allowed in a filter criteria
maxAddresses = 1000
// The maximum number of allowed topics within a topic criteria // The maximum number of topic criteria allowed, vm.LOG4 - vm.LOG0
const maxSubTopics = 1000 maxTopics = 4
// The maximum number of allowed topics within a topic criteria
maxSubTopics = 1000
)
// filter is a helper struct that holds meta information over the filter type // filter is a helper struct that holds meta information over the filter type
// and associated subscription in the event system. // and associated subscription in the event system.
@ -341,6 +345,9 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type
if len(crit.Topics) > maxTopics { if len(crit.Topics) > maxTopics {
return nil, errExceedMaxTopics return nil, errExceedMaxTopics
} }
if len(crit.Addresses) > maxAddresses {
return nil, errExceedMaxAddresses
}
var filter *Filter var filter *Filter
if crit.BlockHash != nil { if crit.BlockHash != nil {
// Block filter requested, construct a single-shot filter // Block filter requested, construct a single-shot filter
@ -530,6 +537,9 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error {
// raw.Address can contain a single address or an array of addresses // raw.Address can contain a single address or an array of addresses
switch rawAddr := raw.Addresses.(type) { switch rawAddr := raw.Addresses.(type) {
case []interface{}: case []interface{}:
if len(rawAddr) > maxAddresses {
return errExceedMaxAddresses
}
for i, addr := range rawAddr { for i, addr := range rawAddr {
if strAddr, ok := addr.(string); ok { if strAddr, ok := addr.(string); ok {
addr, err := decodeAddress(strAddr) addr, err := decodeAddress(strAddr)

View file

@ -19,6 +19,7 @@ package filters
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -182,4 +183,15 @@ func TestUnmarshalJSONNewFilterArgs(t *testing.T) {
if len(test7.Topics[2]) != 0 { if len(test7.Topics[2]) != 0 {
t.Fatalf("expected 0 topics, got %d topics", len(test7.Topics[2])) t.Fatalf("expected 0 topics, got %d topics", len(test7.Topics[2]))
} }
// multiple address exceeding max
var test8 FilterCriteria
addresses := make([]string, maxAddresses+1)
for i := 0; i < maxAddresses+1; i++ {
addresses[i] = fmt.Sprintf(`"%s"`, common.HexToAddress(fmt.Sprintf("0x%x", i)).Hex())
}
vector = fmt.Sprintf(`{"address": [%s]}`, strings.Join(addresses, ", "))
if err := json.Unmarshal([]byte(vector), &test8); err != errExceedMaxAddresses {
t.Fatal("expected errExceedMaxAddresses, got", err)
}
} }

View file

@ -291,6 +291,9 @@ func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*typ
if len(crit.Topics) > maxTopics { if len(crit.Topics) > maxTopics {
return nil, errExceedMaxTopics return nil, errExceedMaxTopics
} }
if len(crit.Addresses) > maxAddresses {
return nil, errExceedMaxAddresses
}
var from, to rpc.BlockNumber var from, to rpc.BlockNumber
if crit.FromBlock == nil { if crit.FromBlock == nil {
from = rpc.LatestBlockNumber from = rpc.LatestBlockNumber

View file

@ -435,6 +435,7 @@ func TestInvalidLogFilterCreation(t *testing.T) {
1: {FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(100)}, 1: {FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(100)},
2: {FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(100)}, 2: {FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(100)},
3: {Topics: [][]common.Hash{{}, {}, {}, {}, {}}}, 3: {Topics: [][]common.Hash{{}, {}, {}, {}, {}}},
4: {Addresses: make([]common.Address, maxAddresses+1)},
} }
for i, test := range testCases { for i, test := range testCases {
@ -461,6 +462,7 @@ func TestInvalidGetLogsRequest(t *testing.T) {
1: {BlockHash: &blockHash, ToBlock: big.NewInt(500)}, 1: {BlockHash: &blockHash, ToBlock: big.NewInt(500)},
2: {BlockHash: &blockHash, FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, 2: {BlockHash: &blockHash, FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64())},
3: {BlockHash: &blockHash, Topics: [][]common.Hash{{}, {}, {}, {}, {}}}, 3: {BlockHash: &blockHash, Topics: [][]common.Hash{{}, {}, {}, {}, {}}},
4: {BlockHash: &blockHash, Addresses: make([]common.Address, maxAddresses+1)},
} }
for i, test := range testCases { for i, test := range testCases {

View file

@ -82,7 +82,7 @@ type Backend interface {
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64)
TxIndexDone() bool TxIndexDone() bool
RPCGasCap() uint64 RPCGasCap() uint64
ChainConfig() *params.ChainConfig ChainConfig() *params.ChainConfig
@ -863,7 +863,7 @@ func containsTx(block *types.Block, hash common.Hash) bool {
// TraceTransaction returns the structured logs created during the execution of EVM // TraceTransaction returns the structured logs created during the execution of EVM
// and returns them as a JSON object. // and returns them as a JSON object.
func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) {
found, _, blockHash, blockNumber, index := api.backend.GetTransaction(hash) found, _, blockHash, blockNumber, index := api.backend.GetCanonicalTransaction(hash)
if !found { if !found {
// Warn in case tx indexer is not done. // Warn in case tx indexer is not done.
if !api.backend.TxIndexDone() { if !api.backend.TxIndexDone() {

View file

@ -117,8 +117,8 @@ func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber)
return b.chain.GetBlockByNumber(uint64(number)), nil return b.chain.GetBlockByNumber(uint64(number)), nil
} }
func (b *testBackend) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) { func (b *testBackend) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
tx, hash, blockNumber, index := rawdb.ReadTransaction(b.chaindb, txHash) tx, hash, blockNumber, index := rawdb.ReadCanonicalTransaction(b.chaindb, txHash)
return tx != nil, tx, hash, blockNumber, index return tx != nil, tx, hash, blockNumber, index
} }

View file

@ -229,7 +229,7 @@ func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, *Block)
return t.tx, t.block return t.tx, t.block
} }
// Try to return an already finalized transaction // Try to return an already finalized transaction
found, tx, blockHash, _, index := t.r.backend.GetTransaction(t.hash) found, tx, blockHash, _, index := t.r.backend.GetCanonicalTransaction(t.hash)
if found { if found {
t.tx = tx t.tx = tx
blockNrOrHash := rpc.BlockNumberOrHashWithHash(blockHash, false) blockNrOrHash := rpc.BlockNumberOrHashWithHash(blockHash, false)

View file

@ -186,15 +186,15 @@ func NewTxPoolAPI(b Backend) *TxPoolAPI {
// Content returns the transactions contained within the transaction pool. // Content returns the transactions contained within the transaction pool.
func (api *TxPoolAPI) Content() map[string]map[string]map[string]*RPCTransaction { func (api *TxPoolAPI) Content() map[string]map[string]map[string]*RPCTransaction {
content := map[string]map[string]map[string]*RPCTransaction{
"pending": make(map[string]map[string]*RPCTransaction),
"queued": make(map[string]map[string]*RPCTransaction),
}
pending, queue := api.b.TxPoolContent() pending, queue := api.b.TxPoolContent()
content := map[string]map[string]map[string]*RPCTransaction{
"pending": make(map[string]map[string]*RPCTransaction, len(pending)),
"queued": make(map[string]map[string]*RPCTransaction, len(queue)),
}
curHeader := api.b.CurrentHeader() curHeader := api.b.CurrentHeader()
// Flatten the pending transactions // Flatten the pending transactions
for account, txs := range pending { for account, txs := range pending {
dump := make(map[string]*RPCTransaction) dump := make(map[string]*RPCTransaction, len(txs))
for _, tx := range txs { for _, tx := range txs {
dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, api.b.ChainConfig()) dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, api.b.ChainConfig())
} }
@ -202,7 +202,7 @@ func (api *TxPoolAPI) Content() map[string]map[string]map[string]*RPCTransaction
} }
// Flatten the queued transactions // Flatten the queued transactions
for account, txs := range queue { for account, txs := range queue {
dump := make(map[string]*RPCTransaction) dump := make(map[string]*RPCTransaction, len(txs))
for _, tx := range txs { for _, tx := range txs {
dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, api.b.ChainConfig()) dump[fmt.Sprintf("%d", tx.Nonce())] = NewRPCPendingTransaction(tx, curHeader, api.b.ChainConfig())
} }
@ -246,11 +246,11 @@ func (api *TxPoolAPI) Status() map[string]hexutil.Uint {
// Inspect retrieves the content of the transaction pool and flattens it into an // Inspect retrieves the content of the transaction pool and flattens it into an
// easily inspectable list. // easily inspectable list.
func (api *TxPoolAPI) Inspect() map[string]map[string]map[string]string { func (api *TxPoolAPI) Inspect() map[string]map[string]map[string]string {
content := map[string]map[string]map[string]string{
"pending": make(map[string]map[string]string),
"queued": make(map[string]map[string]string),
}
pending, queue := api.b.TxPoolContent() pending, queue := api.b.TxPoolContent()
content := map[string]map[string]map[string]string{
"pending": make(map[string]map[string]string, len(pending)),
"queued": make(map[string]map[string]string, len(queue)),
}
// Define a formatter to flatten a transaction into a string // Define a formatter to flatten a transaction into a string
format := func(tx *types.Transaction) string { format := func(tx *types.Transaction) string {
@ -261,7 +261,7 @@ func (api *TxPoolAPI) Inspect() map[string]map[string]map[string]string {
} }
// Flatten the pending transactions // Flatten the pending transactions
for account, txs := range pending { for account, txs := range pending {
dump := make(map[string]string) dump := make(map[string]string, len(txs))
for _, tx := range txs { for _, tx := range txs {
dump[fmt.Sprintf("%d", tx.Nonce())] = format(tx) dump[fmt.Sprintf("%d", tx.Nonce())] = format(tx)
} }
@ -269,7 +269,7 @@ func (api *TxPoolAPI) Inspect() map[string]map[string]map[string]string {
} }
// Flatten the queued transactions // Flatten the queued transactions
for account, txs := range queue { for account, txs := range queue {
dump := make(map[string]string) dump := make(map[string]string, len(txs))
for _, tx := range txs { for _, tx := range txs {
dump[fmt.Sprintf("%d", tx.Nonce())] = format(tx) dump[fmt.Sprintf("%d", tx.Nonce())] = format(tx)
} }
@ -1334,7 +1334,7 @@ func (api *TransactionAPI) GetTransactionCount(ctx context.Context, address comm
// GetTransactionByHash returns the transaction for the given hash // GetTransactionByHash returns the transaction for the given hash
func (api *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) { func (api *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) {
// Try to return an already finalized transaction // Try to return an already finalized transaction
found, tx, blockHash, blockNumber, index := api.b.GetTransaction(hash) found, tx, blockHash, blockNumber, index := api.b.GetCanonicalTransaction(hash)
if !found { if !found {
// No finalized transaction, try to retrieve it from the pool // No finalized transaction, try to retrieve it from the pool
if tx := api.b.GetPoolTransaction(hash); tx != nil { if tx := api.b.GetPoolTransaction(hash); tx != nil {
@ -1357,7 +1357,7 @@ func (api *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common
// GetRawTransactionByHash returns the bytes of the transaction for the given hash. // 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 // Retrieve a finalized transaction, or a pooled otherwise
found, tx, _, _, _ := api.b.GetTransaction(hash) found, tx, _, _, _ := api.b.GetCanonicalTransaction(hash)
if !found { if !found {
if tx = api.b.GetPoolTransaction(hash); tx != nil { if tx = api.b.GetPoolTransaction(hash); tx != nil {
return tx.MarshalBinary() return tx.MarshalBinary()
@ -1374,7 +1374,7 @@ func (api *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash com
// GetTransactionReceipt returns the transaction receipt for the given transaction hash. // GetTransactionReceipt returns the transaction receipt for the given transaction hash.
func (api *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) { func (api *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
found, tx, blockHash, blockNumber, index := api.b.GetTransaction(hash) found, tx, blockHash, blockNumber, index := api.b.GetCanonicalTransaction(hash)
if !found { if !found {
// Make sure indexer is done. // Make sure indexer is done.
if !api.b.TxIndexDone() { if !api.b.TxIndexDone() {
@ -1383,22 +1383,12 @@ func (api *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash commo
// No such tx. // No such tx.
return nil, nil return nil, nil
} }
header, err := api.b.HeaderByHash(ctx, blockHash) receipt, err := api.b.GetCanonicalReceipt(tx, blockHash, blockNumber, index)
if err != nil { if err != nil {
return nil, err return nil, err
} }
receipts, err := api.b.GetReceipts(ctx, blockHash)
if err != nil {
return nil, err
}
if uint64(len(receipts)) <= index {
return nil, nil
}
receipt := receipts[index]
// Derive the sender. // Derive the sender.
signer := types.MakeSigner(api.b.ChainConfig(), header.Number, header.Time) return marshalReceipt(receipt, blockHash, blockNumber, api.signer, tx, int(index)), nil
return marshalReceipt(receipt, blockHash, blockNumber, signer, tx, int(index)), nil
} }
// marshalReceipt marshals a transaction receipt into a JSON object. // marshalReceipt marshals a transaction receipt into a JSON object.
@ -1781,7 +1771,7 @@ func (api *DebugAPI) GetRawReceipts(ctx context.Context, blockNrOrHash rpc.Block
// GetRawTransaction returns the bytes of the transaction for the given hash. // 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 // Retrieve a finalized transaction, or a pooled otherwise
found, tx, _, _, _ := api.b.GetTransaction(hash) found, tx, _, _, _ := api.b.GetCanonicalTransaction(hash)
if !found { if !found {
if tx = api.b.GetPoolTransaction(hash); tx != nil { if tx = api.b.GetPoolTransaction(hash); tx != nil {
return tx.MarshalBinary() return tx.MarshalBinary()

View file

@ -586,9 +586,12 @@ func (b testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) even
func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
panic("implement me") panic("implement me")
} }
func (b testBackend) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) { func (b testBackend) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.db, txHash) tx, blockHash, blockNumber, index := rawdb.ReadCanonicalTransaction(b.db, txHash)
return true, tx, blockHash, blockNumber, index return tx != nil, tx, blockHash, blockNumber, index
}
func (b testBackend) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) {
return b.chain.GetCanonicalReceipt(tx, blockHash, blockNumber, blockIndex)
} }
func (b testBackend) TxIndexDone() bool { func (b testBackend) TxIndexDone() bool {
return true return true

View file

@ -68,13 +68,14 @@ type Backend interface {
StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error)
Pending() (*types.Block, types.Receipts, *state.StateDB) Pending() (*types.Block, types.Receipts, *state.StateDB)
GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error)
GetEVM(ctx context.Context, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM GetEVM(ctx context.Context, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
// Transaction pool API // Transaction pool API
SendTx(ctx context.Context, signedTx *types.Transaction) error SendTx(ctx context.Context, signedTx *types.Transaction) error
GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64)
TxIndexDone() bool TxIndexDone() bool
GetPoolTransactions() (types.Transactions, error) GetPoolTransactions() (types.Transactions, error)
GetPoolTransaction(txHash common.Hash) *types.Transaction GetPoolTransaction(txHash common.Hash) *types.Transaction

View file

@ -369,6 +369,9 @@ func (b *backendMock) Pending() (*types.Block, types.Receipts, *state.StateDB) {
func (b *backendMock) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { func (b *backendMock) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
return nil, nil return nil, nil
} }
func (b *backendMock) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) {
return nil, nil
}
func (b *backendMock) GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) { func (b *backendMock) GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) {
return nil, nil return nil, nil
} }
@ -380,7 +383,7 @@ func (b *backendMock) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) eve
return nil return nil
} }
func (b *backendMock) SendTx(ctx context.Context, signedTx *types.Transaction) error { return nil } func (b *backendMock) SendTx(ctx context.Context, signedTx *types.Transaction) error { return nil }
func (b *backendMock) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) { func (b *backendMock) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
return false, nil, [32]byte{}, 0, 0 return false, nil, [32]byte{}, 0, 0
} }
func (b *backendMock) TxIndexDone() bool { return true } func (b *backendMock) TxIndexDone() bool { return true }

View file

@ -520,6 +520,16 @@ func (db *Database) Enable(root common.Hash) error {
// Re-construct a new disk layer backed by persistent state // Re-construct a new disk layer backed by persistent state
// and schedule the state snapshot generation if it's permitted. // and schedule the state snapshot generation if it's permitted.
db.tree.init(generateSnapshot(db, root, db.isVerkle || db.config.SnapshotNoBuild)) db.tree.init(generateSnapshot(db, root, db.isVerkle || db.config.SnapshotNoBuild))
// After snap sync, the state of the database may have changed completely.
// To ensure the history indexer always matches the current state, we must:
// 1. Close any existing indexer
// 2. Re-initialize the indexer so it starts indexing from the new state root.
if db.indexer != nil && db.freezer != nil && db.config.EnableStateIndexing {
db.indexer.close()
db.indexer = newHistoryIndexer(db.diskdb, db.freezer, db.tree.bottom().stateID())
log.Info("Re-enabled state history indexing")
}
log.Info("Rebuilt trie database", "root", root) log.Info("Rebuilt trie database", "root", root)
return nil return nil
} }

View file

@ -494,7 +494,18 @@ func (i *indexIniter) index(done chan struct{}, interrupt *atomic.Int32, lastID
// when the state is reverted manually (chain.SetHead) or the deep reorg is // when the state is reverted manually (chain.SetHead) or the deep reorg is
// encountered. In such cases, no indexing should be scheduled. // encountered. In such cases, no indexing should be scheduled.
if beginID > lastID { if beginID > lastID {
if lastID == 0 && beginID == 1 {
// Initialize the indexing flag if the state history is empty by
// using zero as the disk layer ID. This is a common case that
// can occur after snap sync.
//
// This step is essential to avoid spinning up indexing thread
// endlessly until a history object is produced.
storeIndexMetadata(i.disk, 0)
log.Info("Initialized history indexing flag")
} else {
log.Debug("State history is fully indexed", "last", lastID) log.Debug("State history is fully indexed", "last", lastID)
}
return return
} }
log.Info("Start history indexing", "beginID", beginID, "lastID", lastID) log.Info("Start history indexing", "beginID", beginID, "lastID", lastID)