Merge branch 'ethereum:master' into gethintegration

This commit is contained in:
Chen Kai 2025-04-17 17:00:03 +08:00 committed by GitHub
commit cec238db0e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 546 additions and 194 deletions

19
.github/CODEOWNERS vendored
View file

@ -9,12 +9,11 @@ beacon/light/ @zsfelfoldi
beacon/merkle/ @zsfelfoldi beacon/merkle/ @zsfelfoldi
beacon/types/ @zsfelfoldi @fjl beacon/types/ @zsfelfoldi @fjl
beacon/params/ @zsfelfoldi @fjl beacon/params/ @zsfelfoldi @fjl
cmd/clef/ @holiman cmd/evm/ @MariusVanDerWijden @lightclient
cmd/evm/ @holiman @MariusVanDerWijden @lightclient core/state/ @rjl493456442
core/state/ @rjl493456442 @holiman crypto/ @gballet @jwasinger @fjl
crypto/ @gballet @jwasinger @holiman @fjl core/ @rjl493456442
core/ @holiman @rjl493456442 eth/ @rjl493456442
eth/ @holiman @rjl493456442
eth/catalyst/ @MariusVanDerWijden @lightclient @fjl @jwasinger eth/catalyst/ @MariusVanDerWijden @lightclient @fjl @jwasinger
eth/tracers/ @s1na eth/tracers/ @s1na
ethclient/ @fjl ethclient/ @fjl
@ -26,11 +25,9 @@ core/tracing/ @s1na
graphql/ @s1na graphql/ @s1na
internal/ethapi/ @fjl @s1na @lightclient internal/ethapi/ @fjl @s1na @lightclient
internal/era/ @lightclient internal/era/ @lightclient
metrics/ @holiman miner/ @MariusVanDerWijden @fjl @rjl493456442
miner/ @MariusVanDerWijden @holiman @fjl @rjl493456442
node/ @fjl node/ @fjl
p2p/ @fjl @zsfelfoldi p2p/ @fjl @zsfelfoldi
rlp/ @fjl rlp/ @fjl
params/ @fjl @holiman @karalabe @gballet @rjl493456442 @zsfelfoldi params/ @fjl @karalabe @gballet @rjl493456442 @zsfelfoldi
rpc/ @fjl @holiman rpc/ @fjl
signer/ @holiman

View file

@ -31,11 +31,11 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/history"
"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/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/internal/era" "github.com/ethereum/go-ethereum/internal/era"
@ -625,7 +625,7 @@ func pruneHistory(ctx *cli.Context) error {
defer chain.Stop() defer chain.Stop()
// Determine the prune point. This will be the first PoS block. // Determine the prune point. This will be the first PoS block.
prunePoint, ok := ethconfig.HistoryPrunePoints[chain.Genesis().Hash()] prunePoint, ok := history.PrunePoints[chain.Genesis().Hash()]
if !ok || prunePoint == nil { if !ok || prunePoint == nil {
return errors.New("prune point not found") return errors.New("prune point not found")
} }

View file

@ -100,7 +100,6 @@ var (
utils.LightNoSyncServeFlag, // deprecated utils.LightNoSyncServeFlag, // deprecated
utils.EthRequiredBlocksFlag, utils.EthRequiredBlocksFlag,
utils.LegacyWhitelistFlag, // deprecated utils.LegacyWhitelistFlag, // deprecated
utils.BloomFilterSizeFlag,
utils.CacheFlag, utils.CacheFlag,
utils.CacheDatabaseFlag, utils.CacheDatabaseFlag,
utils.CacheTrieFlag, utils.CacheTrieFlag,

View file

@ -23,7 +23,7 @@ import (
"os" "os"
"slices" "slices"
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/core/history"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/internal/utesting"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -124,13 +124,13 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
cfg.filterQueryFile = "queries/filter_queries_mainnet.json" cfg.filterQueryFile = "queries/filter_queries_mainnet.json"
cfg.historyTestFile = "queries/history_mainnet.json" cfg.historyTestFile = "queries/history_mainnet.json"
cfg.historyPruneBlock = new(uint64) cfg.historyPruneBlock = new(uint64)
*cfg.historyPruneBlock = ethconfig.HistoryPrunePoints[params.MainnetGenesisHash].BlockNumber *cfg.historyPruneBlock = history.PrunePoints[params.MainnetGenesisHash].BlockNumber
case ctx.Bool(testSepoliaFlag.Name): case ctx.Bool(testSepoliaFlag.Name):
cfg.fsys = builtinTestFiles cfg.fsys = builtinTestFiles
cfg.filterQueryFile = "queries/filter_queries_sepolia.json" cfg.filterQueryFile = "queries/filter_queries_sepolia.json"
cfg.historyTestFile = "queries/history_sepolia.json" cfg.historyTestFile = "queries/history_sepolia.json"
cfg.historyPruneBlock = new(uint64) cfg.historyPruneBlock = new(uint64)
*cfg.historyPruneBlock = ethconfig.HistoryPrunePoints[params.SepoliaGenesisHash].BlockNumber *cfg.historyPruneBlock = history.PrunePoints[params.SepoliaGenesisHash].BlockNumber
default: default:
cfg.fsys = os.DirFS(".") cfg.fsys = os.DirFS(".")
cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name) cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)

View file

@ -50,8 +50,11 @@ func testHeaderVerification(t *testing.T, scheme string) {
headers[i] = block.Header() headers[i] = block.Header()
} }
// Run the header checker for blocks one-by-one, checking for both valid and invalid nonces // Run the header checker for blocks one-by-one, checking for both valid and invalid nonces
chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil) chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil)
defer chain.Stop() defer chain.Stop()
if err != nil {
t.Fatal(err)
}
for i := 0; i < len(blocks); i++ { for i := 0; i < len(blocks); i++ {
for j, valid := range []bool{true, false} { for j, valid := range []bool{true, false} {
@ -163,8 +166,11 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
postHeaders[i] = block.Header() postHeaders[i] = block.Header()
} }
// Run the header checker for blocks one-by-one, checking for both valid and invalid nonces // Run the header checker for blocks one-by-one, checking for both valid and invalid nonces
chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil) chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil)
defer chain.Stop() defer chain.Stop()
if err != nil {
t.Fatal(err)
}
// Verify the blocks before the merging // Verify the blocks before the merging
for i := 0; i < len(preBlocks); i++ { for i := 0; i < len(preBlocks); i++ {

View file

@ -21,6 +21,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math"
"math/big" "math/big"
"runtime" "runtime"
"slices" "slices"
@ -36,6 +37,7 @@ import (
"github.com/ethereum/go-ethereum/common/prque" "github.com/ethereum/go-ethereum/common/prque"
"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/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/history"
"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"
@ -99,6 +101,10 @@ var (
errInvalidNewChain = errors.New("invalid new chain") errInvalidNewChain = errors.New("invalid new chain")
) )
var (
forkReadyInterval = 3 * time.Minute
)
const ( const (
bodyCacheLimit = 256 bodyCacheLimit = 256
blockCacheLimit = 256 blockCacheLimit = 256
@ -158,8 +164,7 @@ type CacheConfig struct {
// This defines the cutoff block for history expiry. // This defines the cutoff block for history expiry.
// Blocks before this number may be unavailable in the chain database. // Blocks before this number may be unavailable in the chain database.
HistoryPruningCutoffNumber uint64 ChainHistoryMode history.HistoryMode
HistoryPruningCutoffHash common.Hash
} }
// triedbConfig derives the configures for trie database. // triedbConfig derives the configures for trie database.
@ -255,6 +260,7 @@ type BlockChain struct {
currentSnapBlock atomic.Pointer[types.Header] // Current head of snap-sync currentSnapBlock atomic.Pointer[types.Header] // Current head of snap-sync
currentFinalBlock atomic.Pointer[types.Header] // Latest (consensus) finalized block currentFinalBlock atomic.Pointer[types.Header] // Latest (consensus) finalized block
currentSafeBlock atomic.Pointer[types.Header] // Latest (consensus) safe block currentSafeBlock atomic.Pointer[types.Header] // Latest (consensus) safe block
historyPrunePoint atomic.Pointer[history.PrunePoint]
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]
@ -274,6 +280,8 @@ type BlockChain struct {
processor Processor // Block transaction processor interface processor Processor // Block transaction processor interface
vmConfig vm.Config vmConfig vm.Config
logger *tracing.Hooks logger *tracing.Hooks
lastForkReadyAlert time.Time // Last time there was a fork readiness print out
} }
// NewBlockChain returns a fully initialised block chain using information // NewBlockChain returns a fully initialised block chain using information
@ -513,19 +521,33 @@ func (bc *BlockChain) loadLastState() error {
log.Warn("Empty database, resetting chain") log.Warn("Empty database, resetting chain")
return bc.Reset() return bc.Reset()
} }
// Make sure the entire head block is available headHeader := bc.GetHeaderByHash(head)
headBlock := bc.GetBlockByHash(head) if headHeader == nil {
// Corrupt or empty database, init from scratch
log.Warn("Head header missing, resetting chain", "hash", head)
return bc.Reset()
}
var headBlock *types.Block
if cmp := headHeader.Number.Cmp(new(big.Int)); cmp == 1 {
// Make sure the entire head block is available.
headBlock = bc.GetBlockByHash(head)
} else if cmp == 0 {
// On a pruned node the block body might not be available. But a pruned
// block should never be the head block. The only exception is when, as
// a last resort, chain is reset to genesis.
headBlock = bc.genesisBlock
}
if headBlock == nil { if headBlock == nil {
// Corrupt or empty database, init from scratch // Corrupt or empty database, init from scratch
log.Warn("Head block missing, resetting chain", "hash", head) log.Warn("Head block missing, resetting chain", "hash", head)
return bc.Reset() return bc.Reset()
} }
// Everything seems to be fine, set as the head block // Everything seems to be fine, set as the head block
bc.currentBlock.Store(headBlock.Header()) bc.currentBlock.Store(headHeader)
headBlockGauge.Update(int64(headBlock.NumberU64())) headBlockGauge.Update(int64(headBlock.NumberU64()))
// Restore the last known head header // Restore the last known head header
headHeader := headBlock.Header()
if head := rawdb.ReadHeadHeaderHash(bc.db); head != (common.Hash{}) { if head := rawdb.ReadHeadHeaderHash(bc.db); head != (common.Hash{}) {
if header := bc.GetHeaderByHash(head); header != nil { if header := bc.GetHeaderByHash(head); header != nil {
headHeader = header headHeader = header
@ -533,6 +555,12 @@ func (bc *BlockChain) loadLastState() error {
} }
bc.hc.SetCurrentHeader(headHeader) bc.hc.SetCurrentHeader(headHeader)
// Initialize history pruning.
latest := max(headBlock.NumberU64(), headHeader.Number.Uint64())
if err := bc.initializeHistoryPruning(latest); err != nil {
return err
}
// Restore the last known head snap block // Restore the last known head snap block
bc.currentSnapBlock.Store(headBlock.Header()) bc.currentSnapBlock.Store(headBlock.Header())
headFastBlockGauge.Update(int64(headBlock.NumberU64())) headFastBlockGauge.Update(int64(headBlock.NumberU64()))
@ -555,6 +583,7 @@ func (bc *BlockChain) loadLastState() error {
headSafeBlockGauge.Update(int64(block.NumberU64())) headSafeBlockGauge.Update(int64(block.NumberU64()))
} }
} }
// Issue a status log for the user // Issue a status log for the user
var ( var (
currentSnapBlock = bc.CurrentSnapBlock() currentSnapBlock = bc.CurrentSnapBlock()
@ -573,9 +602,57 @@ func (bc *BlockChain) loadLastState() error {
if pivot := rawdb.ReadLastPivotNumber(bc.db); pivot != nil { if pivot := rawdb.ReadLastPivotNumber(bc.db); pivot != nil {
log.Info("Loaded last snap-sync pivot marker", "number", *pivot) log.Info("Loaded last snap-sync pivot marker", "number", *pivot)
} }
if pruning := bc.historyPrunePoint.Load(); pruning != nil {
log.Info("Chain history is pruned", "earliest", pruning.BlockNumber, "hash", pruning.BlockHash)
}
return nil return nil
} }
// initializeHistoryPruning sets bc.historyPrunePoint.
func (bc *BlockChain) initializeHistoryPruning(latest uint64) error {
freezerTail, _ := bc.db.Tail()
switch bc.cacheConfig.ChainHistoryMode {
case history.KeepAll:
if freezerTail == 0 {
return nil
}
// The database was pruned somehow, so we need to figure out if it's a known
// configuration or an error.
predefinedPoint := history.PrunePoints[bc.genesisBlock.Hash()]
if predefinedPoint == nil || freezerTail != predefinedPoint.BlockNumber {
log.Error("Chain history database is pruned with unknown configuration", "tail", freezerTail)
return fmt.Errorf("unexpected database tail")
}
bc.historyPrunePoint.Store(predefinedPoint)
return nil
case history.KeepPostMerge:
if freezerTail == 0 && latest != 0 {
// This is the case where a user is trying to run with --history.chain
// postmerge directly on an existing DB. We could just trigger the pruning
// here, but it'd be a bit dangerous since they may not have intended this
// action to happen. So just tell them how to do it.
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is not pruned.", bc.cacheConfig.ChainHistoryMode.String()))
log.Error(fmt.Sprintf("Run 'geth prune-history' to prune pre-merge history."))
return fmt.Errorf("history pruning requested via configuration")
}
predefinedPoint := history.PrunePoints[bc.genesisBlock.Hash()]
if predefinedPoint == nil {
log.Error("Chain history pruning is not supported for this network", "genesis", bc.genesisBlock.Hash())
return fmt.Errorf("history pruning requested for unknown network")
} else if freezerTail > 0 && freezerTail != predefinedPoint.BlockNumber {
log.Error("Chain history database is pruned to unknown block", "tail", freezerTail)
return fmt.Errorf("unexpected database tail")
}
bc.historyPrunePoint.Store(predefinedPoint)
return nil
default:
return fmt.Errorf("invalid history mode: %d", bc.cacheConfig.ChainHistoryMode)
}
}
// SetHead rewinds the local chain to a new head. Depending on whether the node // SetHead rewinds the local chain to a new head. Depending on whether the node
// was snap synced or full synced and in which state, the method will try to // was snap synced or full synced and in which state, the method will try to
// delete minimal data from disk whilst retaining chain consistency. // delete minimal data from disk whilst retaining chain consistency.
@ -586,11 +663,15 @@ func (bc *BlockChain) SetHead(head uint64) error {
// Send chain head event to update the transaction pool // Send chain head event to update the transaction pool
header := bc.CurrentBlock() header := bc.CurrentBlock()
if block := bc.GetBlock(header.Hash(), header.Number.Uint64()); block == nil { if block := bc.GetBlock(header.Hash(), header.Number.Uint64()); block == nil {
// This should never happen. In practice, previously currentBlock // In a pruned node the genesis block will not exist in the freezer.
// contained the entire block whereas now only a "marker", so there // It should not happen that we set head to any other pruned block.
// is an ever so slight chance for a race we should handle. if header.Number.Uint64() > 0 {
log.Error("Current block not found in database", "block", header.Number, "hash", header.Hash()) // This should never happen. In practice, previously currentBlock
return fmt.Errorf("current block missing: #%d [%x..]", header.Number, header.Hash().Bytes()[:4]) // contained the entire block whereas now only a "marker", so there
// is an ever so slight chance for a race we should handle.
log.Error("Current block not found in database", "block", header.Number, "hash", header.Hash())
return fmt.Errorf("current block missing: #%d [%x..]", header.Number, header.Hash().Bytes()[:4])
}
} }
bc.chainHeadFeed.Send(ChainHeadEvent{Header: header}) bc.chainHeadFeed.Send(ChainHeadEvent{Header: header})
return nil return nil
@ -607,11 +688,15 @@ func (bc *BlockChain) SetHeadWithTimestamp(timestamp uint64) error {
// Send chain head event to update the transaction pool // Send chain head event to update the transaction pool
header := bc.CurrentBlock() header := bc.CurrentBlock()
if block := bc.GetBlock(header.Hash(), header.Number.Uint64()); block == nil { if block := bc.GetBlock(header.Hash(), header.Number.Uint64()); block == nil {
// This should never happen. In practice, previously currentBlock // In a pruned node the genesis block will not exist in the freezer.
// contained the entire block whereas now only a "marker", so there // It should not happen that we set head to any other pruned block.
// is an ever so slight chance for a race we should handle. if header.Number.Uint64() > 0 {
log.Error("Current block not found in database", "block", header.Number, "hash", header.Hash()) // This should never happen. In practice, previously currentBlock
return fmt.Errorf("current block missing: #%d [%x..]", header.Number, header.Hash().Bytes()[:4]) // contained the entire block whereas now only a "marker", so there
// is an ever so slight chance for a race we should handle.
log.Error("Current block not found in database", "block", header.Number, "hash", header.Hash())
return fmt.Errorf("current block missing: #%d [%x..]", header.Number, header.Hash().Bytes()[:4])
}
} }
bc.chainHeadFeed.Send(ChainHeadEvent{Header: header}) bc.chainHeadFeed.Send(ChainHeadEvent{Header: header})
return nil return nil
@ -1014,7 +1099,9 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {
bc.hc.SetCurrentHeader(bc.genesisBlock.Header()) bc.hc.SetCurrentHeader(bc.genesisBlock.Header())
bc.currentSnapBlock.Store(bc.genesisBlock.Header()) bc.currentSnapBlock.Store(bc.genesisBlock.Header())
headFastBlockGauge.Update(int64(bc.genesisBlock.NumberU64())) headFastBlockGauge.Update(int64(bc.genesisBlock.NumberU64()))
return nil
// Reset history pruning status.
return bc.initializeHistoryPruning(0)
} }
// Export writes the active chain to the given writer. // Export writes the active chain to the given writer.
@ -1804,6 +1891,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
trieDiffNodes, trieBufNodes, _ := bc.triedb.Size() trieDiffNodes, trieBufNodes, _ := bc.triedb.Size()
stats.report(chain, it.index, snapDiffItems, snapBufItems, trieDiffNodes, trieBufNodes, setHead) stats.report(chain, it.index, snapDiffItems, snapBufItems, trieDiffNodes, trieBufNodes, setHead)
// Print confirmation that a future fork is scheduled, but not yet active.
bc.logForkReadiness(block)
if !setHead { if !setHead {
// After merge we expect few side chains. Simply count // After merge we expect few side chains. Simply count
// all blocks the CL gives us for GC processing time // all blocks the CL gives us for GC processing time
@ -1837,6 +1927,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
"root", block.Root()) "root", block.Root())
} }
} }
stats.ignored += it.remaining() stats.ignored += it.remaining()
return witness, it.index, err return witness, it.index, err
} }
@ -2434,6 +2525,23 @@ func (bc *BlockChain) reportBlock(block *types.Block, res *ProcessResult, err er
log.Error(summarizeBadBlock(block, receipts, bc.Config(), err)) log.Error(summarizeBadBlock(block, receipts, bc.Config(), err))
} }
// logForkReadiness will write a log when a future fork is scheduled, but not
// active. This is useful so operators know their client is ready for the fork.
func (bc *BlockChain) logForkReadiness(block *types.Block) {
c := bc.Config()
current, last := c.LatestFork(block.Time()), c.LatestFork(math.MaxUint64)
t := c.Timestamp(last)
if t == nil {
return
}
at := time.Unix(int64(*t), 0)
if current < last && time.Now().After(bc.lastForkReadyAlert.Add(forkReadyInterval)) {
log.Info("Ready for fork activation", "fork", last, "date", at.Format(time.RFC822),
"remaining", time.Until(at).Round(time.Second), "timestamp", at.Unix())
bc.lastForkReadyAlert = time.Now()
}
}
// summarizeBadBlock returns a string summarizing the bad block and other // summarizeBadBlock returns a string summarizing the bad block and other
// relevant information. // relevant information.
func summarizeBadBlock(block *types.Block, receipts []*types.Receipt, config *params.ChainConfig, err error) string { func summarizeBadBlock(block *types.Block, receipts []*types.Receipt, config *params.ChainConfig, err error) string {

View file

@ -410,7 +410,11 @@ func (bc *BlockChain) TxIndexProgress() (TxIndexProgress, error) {
// HistoryPruningCutoff returns the configured history pruning point. // HistoryPruningCutoff returns the configured history pruning point.
// Blocks before this might not be available in the database. // Blocks before this might not be available in the database.
func (bc *BlockChain) HistoryPruningCutoff() (uint64, common.Hash) { func (bc *BlockChain) HistoryPruningCutoff() (uint64, common.Hash) {
return bc.cacheConfig.HistoryPruningCutoffNumber, bc.cacheConfig.HistoryPruningCutoffHash pt := bc.historyPrunePoint.Load()
if pt == nil {
return 0, bc.genesisBlock.Hash()
}
return pt.BlockNumber, pt.BlockHash
} }
// TrieDB retrieves the low level trie database used for data storage. // TrieDB retrieves the low level trie database used for data storage.

View file

@ -33,6 +33,7 @@ import (
"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"
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core/history"
"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/types" "github.com/ethereum/go-ethereum/core/types"
@ -4257,13 +4258,7 @@ func testChainReorgSnapSync(t *testing.T, ancientLimit uint64) {
// be persisted without the receipts and bodies; chain after should be persisted // be persisted without the receipts and bodies; chain after should be persisted
// normally. // normally.
func TestInsertChainWithCutoff(t *testing.T) { func TestInsertChainWithCutoff(t *testing.T) {
testInsertChainWithCutoff(t, 32, 32) // cutoff = 32, ancientLimit = 32 const chainLength = 64
testInsertChainWithCutoff(t, 32, 64) // cutoff = 32, ancientLimit = 64 (entire chain in ancient)
testInsertChainWithCutoff(t, 32, 65) // cutoff = 32, ancientLimit = 65 (64 blocks in ancient, 1 block in live)
}
func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64) {
// log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelDebug, true)))
// Configure and generate a sample block chain // Configure and generate a sample block chain
var ( var (
@ -4278,24 +4273,51 @@ func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64)
signer = types.LatestSigner(gspec.Config) signer = types.LatestSigner(gspec.Config)
engine = beacon.New(ethash.NewFaker()) engine = beacon.New(ethash.NewFaker())
) )
_, blocks, receipts := GenerateChainWithGenesis(gspec, engine, int(2*cutoff), func(i int, block *BlockGen) { _, blocks, receipts := GenerateChainWithGenesis(gspec, engine, chainLength, func(i int, block *BlockGen) {
block.SetCoinbase(common.Address{0x00}) block.SetCoinbase(common.Address{0x00})
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, block.header.BaseFee, nil), signer, key) tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, block.header.BaseFee, nil), signer, key)
if err != nil { if err != nil {
panic(err) panic(err)
} }
block.AddTx(tx) block.AddTx(tx)
}) })
// Run the actual tests.
t.Run("cutoff-32/ancientLimit-32", func(t *testing.T) {
// cutoff = 32, ancientLimit = 32
testInsertChainWithCutoff(t, 32, 32, gspec, blocks, receipts)
})
t.Run("cutoff-32/ancientLimit-64", func(t *testing.T) {
// cutoff = 32, ancientLimit = 64 (entire chain in ancient)
testInsertChainWithCutoff(t, 32, 64, gspec, blocks, receipts)
})
t.Run("cutoff-32/ancientLimit-64", func(t *testing.T) {
// cutoff = 32, ancientLimit = 65 (64 blocks in ancient, 1 block in live)
testInsertChainWithCutoff(t, 32, 65, gspec, blocks, receipts)
})
}
func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64, genesis *Genesis, blocks []*types.Block, receipts []types.Receipts) {
// log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelDebug, true)))
// Add a known pruning point for the duration of the test.
ghash := genesis.ToBlock().Hash()
cutoffBlock := blocks[cutoff-1]
history.PrunePoints[ghash] = &history.PrunePoint{
BlockNumber: cutoffBlock.NumberU64(),
BlockHash: cutoffBlock.Hash(),
}
defer func() {
delete(history.PrunePoints, ghash)
}()
// Enable pruning in cache config.
config := DefaultCacheConfigWithScheme(rawdb.PathScheme)
config.ChainHistoryMode = history.KeepPostMerge
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false) db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
defer db.Close() defer db.Close()
chain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.PathScheme), genesis, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil)
cutoffBlock := blocks[cutoff-1]
config := DefaultCacheConfigWithScheme(rawdb.PathScheme)
config.HistoryPruningCutoffNumber = cutoffBlock.NumberU64()
config.HistoryPruningCutoffHash = cutoffBlock.Hash()
chain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.PathScheme), gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil)
defer chain.Stop() defer chain.Stop()
var ( var (
@ -4326,8 +4348,8 @@ func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64)
t.Errorf("head header #%d: header mismatch: want: %v, got: %v", headHeader.Number, blocks[len(blocks)-1].Hash(), headHeader.Hash()) t.Errorf("head header #%d: header mismatch: want: %v, got: %v", headHeader.Number, blocks[len(blocks)-1].Hash(), headHeader.Hash())
} }
headBlock := chain.CurrentBlock() headBlock := chain.CurrentBlock()
if headBlock.Hash() != gspec.ToBlock().Hash() { if headBlock.Hash() != ghash {
t.Errorf("head block #%d: header mismatch: want: %v, got: %v", headBlock.Number, gspec.ToBlock().Hash(), headBlock.Hash()) t.Errorf("head block #%d: header mismatch: want: %v, got: %v", headBlock.Number, ghash, headBlock.Hash())
} }
// Iterate over all chain data components, and cross reference // Iterate over all chain data components, and cross reference

View file

@ -50,6 +50,7 @@ var (
) )
const ( const (
databaseVersion = 1 // reindexed if database version does not match
cachedLastBlocks = 1000 // last block of map pointers cachedLastBlocks = 1000 // last block of map pointers
cachedLvPointers = 1000 // first log value pointer of block pointers cachedLvPointers = 1000 // first log value pointer of block pointers
cachedBaseRows = 100 // groups of base layer filter row data cachedBaseRows = 100 // groups of base layer filter row data
@ -138,13 +139,25 @@ type FilterMaps struct {
// as transparent (uncached/unchanged). // as transparent (uncached/unchanged).
type filterMap []FilterRow type filterMap []FilterRow
// copy returns a copy of the given filter map. Note that the row slices are // fastCopy returns a copy of the given filter map. Note that the row slices are
// copied but their contents are not. This permits extending the rows further // copied but their contents are not. This permits appending to the rows further
// (which happens during map rendering) without affecting the validity of // (which happens during map rendering) without affecting the validity of
// copies made for snapshots during rendering. // copies made for snapshots during rendering.
func (fm filterMap) copy() filterMap { // Appending to the rows of both the original map and the fast copy, or two fast
// copies of the same map would result in data corruption, therefore a fast copy
// should always be used in a read only way.
func (fm filterMap) fastCopy() filterMap {
return slices.Clone(fm)
}
// fullCopy returns a copy of the given filter map, also making a copy of each
// individual filter row, ensuring that a modification to either one will never
// affect the other.
func (fm filterMap) fullCopy() filterMap {
c := make(filterMap, len(fm)) c := make(filterMap, len(fm))
copy(c, fm) for i, row := range fm {
c[i] = slices.Clone(row)
}
return c return c
} }
@ -207,8 +220,9 @@ type Config struct {
// NewFilterMaps creates a new FilterMaps and starts the indexer. // NewFilterMaps creates a new FilterMaps and starts the indexer.
func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, finalBlock uint64, params Params, config Config) *FilterMaps { func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, finalBlock uint64, params Params, config Config) *FilterMaps {
rs, initialized, err := rawdb.ReadFilterMapsRange(db) rs, initialized, err := rawdb.ReadFilterMapsRange(db)
if err != nil { if err != nil || rs.Version != databaseVersion {
log.Error("Error reading log index range", "error", err) rs, initialized = rawdb.FilterMapsRange{}, false
log.Warn("Invalid log index database version; resetting log index")
} }
params.deriveFields() params.deriveFields()
f := &FilterMaps{ f := &FilterMaps{
@ -437,6 +451,7 @@ func (f *FilterMaps) setRange(batch ethdb.KeyValueWriter, newView *ChainView, ne
f.updateMatchersValidRange() f.updateMatchersValidRange()
if newRange.initialized { if newRange.initialized {
rs := rawdb.FilterMapsRange{ rs := rawdb.FilterMapsRange{
Version: databaseVersion,
HeadIndexed: newRange.headIndexed, HeadIndexed: newRange.headIndexed,
HeadDelimiter: newRange.headDelimiter, HeadDelimiter: newRange.headDelimiter,
BlocksFirst: newRange.blocks.First(), BlocksFirst: newRange.blocks.First(),

View file

@ -17,8 +17,10 @@
package filtermaps package filtermaps
import ( import (
"context"
crand "crypto/rand" crand "crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/binary"
"math/big" "math/big"
"math/rand" "math/rand"
"sync" "sync"
@ -31,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
) )
var testParams = Params{ var testParams = Params{
@ -104,6 +107,7 @@ func TestIndexerRandomRange(t *testing.T) {
fork, head = rand.Intn(len(forks)), rand.Intn(1001) fork, head = rand.Intn(len(forks)), rand.Intn(1001)
ts.chain.setCanonicalChain(forks[fork][:head+1]) ts.chain.setCanonicalChain(forks[fork][:head+1])
case 2: case 2:
checkSnapshot = false
if head < 1000 { if head < 1000 {
checkSnapshot = !noHistory && head != 0 // no snapshot generated for block 0 checkSnapshot = !noHistory && head != 0 // no snapshot generated for block 0
// add blocks after the current head // add blocks after the current head
@ -158,6 +162,63 @@ func TestIndexerRandomRange(t *testing.T) {
} }
} }
func TestIndexerMatcherView(t *testing.T) {
testIndexerMatcherView(t, false)
}
func TestIndexerMatcherViewWithConcurrentRead(t *testing.T) {
testIndexerMatcherView(t, true)
}
func testIndexerMatcherView(t *testing.T, concurrentRead bool) {
ts := newTestSetup(t)
defer ts.close()
forks := make([][]common.Hash, 20)
hashes := make([]common.Hash, 20)
ts.chain.addBlocks(100, 5, 2, 4, true)
ts.setHistory(0, false)
for i := range forks {
if i != 0 {
ts.chain.setHead(100 - i)
ts.chain.addBlocks(i, 5, 2, 4, true)
}
ts.fm.WaitIdle()
forks[i] = ts.chain.getCanonicalChain()
hashes[i] = ts.matcherViewHash()
}
fork := len(forks) - 1
for i := 0; i < 5000; i++ {
oldFork := fork
fork = rand.Intn(len(forks))
stopCh := make(chan chan struct{})
if concurrentRead {
go func() {
for {
ts.matcherViewHash()
select {
case ch := <-stopCh:
close(ch)
return
default:
}
}
}()
}
ts.chain.setCanonicalChain(forks[fork])
ts.fm.WaitIdle()
if concurrentRead {
ch := make(chan struct{})
stopCh <- ch
<-ch
}
hash := ts.matcherViewHash()
if hash != hashes[fork] {
t.Fatalf("Matcher view hash mismatch when switching from for %d to %d", oldFork, fork)
}
}
}
func TestIndexerCompareDb(t *testing.T) { func TestIndexerCompareDb(t *testing.T) {
ts := newTestSetup(t) ts := newTestSetup(t)
defer ts.close() defer ts.close()
@ -291,6 +352,55 @@ func (ts *testSetup) fmDbHash() common.Hash {
return result return result
} }
func (ts *testSetup) matcherViewHash() common.Hash {
mb := ts.fm.NewMatcherBackend()
defer mb.Close()
ctx := context.Background()
params := mb.GetParams()
hasher := sha256.New()
var headPtr uint64
for b := uint64(0); ; b++ {
lvptr, err := mb.GetBlockLvPointer(ctx, b)
if err != nil || (b > 0 && lvptr == headPtr) {
break
}
var enc [8]byte
binary.LittleEndian.PutUint64(enc[:], lvptr)
hasher.Write(enc[:])
headPtr = lvptr
}
headMap := uint32(headPtr >> params.logValuesPerMap)
var enc [12]byte
for r := uint32(0); r < params.mapHeight; r++ {
binary.LittleEndian.PutUint32(enc[:4], r)
for m := uint32(0); m <= headMap; m++ {
binary.LittleEndian.PutUint32(enc[4:8], m)
row, _ := mb.GetFilterMapRow(ctx, m, r, false)
for _, v := range row {
binary.LittleEndian.PutUint32(enc[8:], v)
hasher.Write(enc[:])
}
}
}
var hash common.Hash
hasher.Sum(hash[:0])
for i := 0; i < 50; i++ {
hasher.Reset()
hasher.Write(hash[:])
lvptr := binary.LittleEndian.Uint64(hash[:8]) % headPtr
if log, _ := mb.GetLogByLvIndex(ctx, lvptr); log != nil {
enc, err := rlp.EncodeToBytes(log)
if err != nil {
panic(err)
}
hasher.Write(enc)
}
hasher.Sum(hash[:0])
}
return hash
}
func (ts *testSetup) close() { func (ts *testSetup) close() {
if ts.fm != nil { if ts.fm != nil {
ts.fm.Stop() ts.fm.Stop()

View file

@ -84,7 +84,7 @@ func (f *FilterMaps) renderMapsBefore(renderBefore uint32) (*mapRenderer, error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if snapshot := f.lastCanonicalSnapshotBefore(renderBefore); snapshot != nil && snapshot.mapIndex >= nextMap { if snapshot := f.lastCanonicalSnapshotOfMap(nextMap); snapshot != nil {
return f.renderMapsFromSnapshot(snapshot) return f.renderMapsFromSnapshot(snapshot)
} }
if nextMap >= renderBefore { if nextMap >= renderBefore {
@ -97,14 +97,14 @@ func (f *FilterMaps) renderMapsBefore(renderBefore uint32) (*mapRenderer, error)
// snapshot made at a block boundary. // snapshot made at a block boundary.
func (f *FilterMaps) renderMapsFromSnapshot(cp *renderedMap) (*mapRenderer, error) { func (f *FilterMaps) renderMapsFromSnapshot(cp *renderedMap) (*mapRenderer, error) {
f.testSnapshotUsed = true f.testSnapshotUsed = true
iter, err := f.newLogIteratorFromBlockDelimiter(cp.lastBlock) iter, err := f.newLogIteratorFromBlockDelimiter(cp.lastBlock, cp.headDelimiter)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create log iterator from block delimiter %d: %v", cp.lastBlock, err) return nil, fmt.Errorf("failed to create log iterator from block delimiter %d: %v", cp.lastBlock, err)
} }
return &mapRenderer{ return &mapRenderer{
f: f, f: f,
currentMap: &renderedMap{ currentMap: &renderedMap{
filterMap: cp.filterMap.copy(), filterMap: cp.filterMap.fullCopy(),
mapIndex: cp.mapIndex, mapIndex: cp.mapIndex,
lastBlock: cp.lastBlock, lastBlock: cp.lastBlock,
blockLvPtrs: cp.blockLvPtrs, blockLvPtrs: cp.blockLvPtrs,
@ -137,14 +137,14 @@ func (f *FilterMaps) renderMapsFromMapBoundary(firstMap, renderBefore uint32, st
}, nil }, nil
} }
// lastCanonicalSnapshotBefore returns the latest cached snapshot that matches // lastCanonicalSnapshotOfMap returns the latest cached snapshot of the given map
// the current targetView. // that is also consistent with the current targetView.
func (f *FilterMaps) lastCanonicalSnapshotBefore(renderBefore uint32) *renderedMap { func (f *FilterMaps) lastCanonicalSnapshotOfMap(mapIndex uint32) *renderedMap {
var best *renderedMap var best *renderedMap
for _, blockNumber := range f.renderSnapshots.Keys() { for _, blockNumber := range f.renderSnapshots.Keys() {
if cp, _ := f.renderSnapshots.Get(blockNumber); cp != nil && blockNumber < f.indexedRange.blocks.AfterLast() && if cp, _ := f.renderSnapshots.Get(blockNumber); cp != nil && blockNumber < f.indexedRange.blocks.AfterLast() &&
blockNumber <= f.targetView.headNumber && f.targetView.getBlockId(blockNumber) == cp.lastBlockId && blockNumber <= f.targetView.headNumber && f.targetView.getBlockId(blockNumber) == cp.lastBlockId &&
cp.mapIndex < renderBefore && (best == nil || blockNumber > best.lastBlock) { cp.mapIndex == mapIndex && (best == nil || blockNumber > best.lastBlock) {
best = cp best = cp
} }
} }
@ -171,10 +171,9 @@ func (f *FilterMaps) lastCanonicalMapBoundaryBefore(renderBefore uint32) (nextMa
if err != nil { if err != nil {
return 0, 0, 0, fmt.Errorf("failed to retrieve last block of reverse iterated map %d: %v", mapIndex, err) return 0, 0, 0, fmt.Errorf("failed to retrieve last block of reverse iterated map %d: %v", mapIndex, err)
} }
if lastBlock >= f.indexedView.headNumber || lastBlock >= f.targetView.headNumber || if (f.indexedRange.headIndexed && mapIndex >= f.indexedRange.maps.Last()) ||
lastBlockId != f.targetView.getBlockId(lastBlock) { lastBlock >= f.targetView.headNumber || lastBlockId != f.targetView.getBlockId(lastBlock) {
// map is not full or inconsistent with targetView; roll back continue // map is not full or inconsistent with targetView; roll back
continue
} }
lvPtr, err := f.getBlockLvPointer(lastBlock) lvPtr, err := f.getBlockLvPointer(lastBlock)
if err != nil { if err != nil {
@ -257,11 +256,14 @@ func (f *FilterMaps) loadHeadSnapshot() error {
// makeSnapshot creates a snapshot of the current state of the rendered map. // makeSnapshot creates a snapshot of the current state of the rendered map.
func (r *mapRenderer) makeSnapshot() { func (r *mapRenderer) makeSnapshot() {
r.f.renderSnapshots.Add(r.iterator.blockNumber, &renderedMap{ if r.iterator.blockNumber != r.currentMap.lastBlock || r.iterator.chainView != r.f.targetView {
filterMap: r.currentMap.filterMap.copy(), panic("iterator state inconsistent with current rendered map")
}
r.f.renderSnapshots.Add(r.currentMap.lastBlock, &renderedMap{
filterMap: r.currentMap.filterMap.fastCopy(),
mapIndex: r.currentMap.mapIndex, mapIndex: r.currentMap.mapIndex,
lastBlock: r.iterator.blockNumber, lastBlock: r.currentMap.lastBlock,
lastBlockId: r.f.targetView.getBlockId(r.currentMap.lastBlock), lastBlockId: r.iterator.chainView.getBlockId(r.currentMap.lastBlock),
blockLvPtrs: r.currentMap.blockLvPtrs, blockLvPtrs: r.currentMap.blockLvPtrs,
finished: true, finished: true,
headDelimiter: r.iterator.lvIndex, headDelimiter: r.iterator.lvIndex,
@ -661,24 +663,13 @@ var errUnindexedRange = errors.New("unindexed range")
// newLogIteratorFromBlockDelimiter creates a logIterator starting at the // newLogIteratorFromBlockDelimiter creates a logIterator starting at the
// given block's first log value entry (the block delimiter), according to the // given block's first log value entry (the block delimiter), according to the
// current targetView. // current targetView.
func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber uint64) (*logIterator, error) { func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber, lvIndex uint64) (*logIterator, error) {
if blockNumber > f.targetView.headNumber { if blockNumber > f.targetView.headNumber {
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", blockNumber, f.targetView.headNumber) return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", blockNumber, f.targetView.headNumber)
} }
if !f.indexedRange.blocks.Includes(blockNumber) { if !f.indexedRange.blocks.Includes(blockNumber) {
return nil, errUnindexedRange return nil, errUnindexedRange
} }
var lvIndex uint64
if f.indexedRange.headIndexed && blockNumber+1 == f.indexedRange.blocks.AfterLast() {
lvIndex = f.indexedRange.headDelimiter
} else {
var err error
lvIndex, err = f.getBlockLvPointer(blockNumber + 1)
if err != nil {
return nil, fmt.Errorf("failed to retrieve log value pointer of block %d after delimiter: %v", blockNumber+1, err)
}
lvIndex--
}
finished := blockNumber == f.targetView.headNumber finished := blockNumber == f.targetView.headNumber
l := &logIterator{ l := &logIterator{
chainView: f.targetView, chainView: f.targetView,

View file

@ -75,6 +75,9 @@ func (fm *FilterMapsMatcherBackend) Close() {
// on write. // on write.
// GetFilterMapRow implements MatcherBackend. // GetFilterMapRow implements MatcherBackend.
func (fm *FilterMapsMatcherBackend) GetFilterMapRow(ctx context.Context, mapIndex, rowIndex uint32, baseLayerOnly bool) (FilterRow, error) { func (fm *FilterMapsMatcherBackend) GetFilterMapRow(ctx context.Context, mapIndex, rowIndex uint32, baseLayerOnly bool) (FilterRow, error) {
fm.f.indexLock.RLock()
defer fm.f.indexLock.RUnlock()
return fm.f.getFilterMapRow(mapIndex, rowIndex, baseLayerOnly) return fm.f.getFilterMapRow(mapIndex, rowIndex, baseLayerOnly)
} }

View file

@ -76,8 +76,10 @@ func TestCreation(t *testing.T) {
{20000000, 1681338454, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1681338455}}, // Last Gray Glacier block {20000000, 1681338454, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1681338455}}, // Last Gray Glacier block
{20000000, 1681338455, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}}, // First Shanghai block {20000000, 1681338455, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}}, // First Shanghai block
{30000000, 1710338134, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}}, // Last Shanghai block {30000000, 1710338134, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}}, // Last Shanghai block
{40000000, 1710338135, ID{Hash: checksumToBytes(0x9f3d2254), Next: 0}}, // First Cancun block {40000000, 1710338135, ID{Hash: checksumToBytes(0x9f3d2254), Next: 1746612311}}, // First Cancun block
{50000000, 2000000000, ID{Hash: checksumToBytes(0x9f3d2254), Next: 0}}, // Future Cancun block {30000000, 1746022486, ID{Hash: checksumToBytes(0x9f3d2254), Next: 1746612311}}, // Last Cancun block
{30000000, 1746612311, ID{Hash: checksumToBytes(0xc376cf8b), Next: 0}}, // First Prague block
{50000000, 2000000000, ID{Hash: checksumToBytes(0xc376cf8b), Next: 0}}, // Future Prague block
}, },
}, },
// Sepolia test cases // Sepolia test cases
@ -137,9 +139,11 @@ func TestCreation(t *testing.T) {
// fork ID. // fork ID.
func TestValidation(t *testing.T) { func TestValidation(t *testing.T) {
// Config that has not timestamp enabled // Config that has not timestamp enabled
// TODO(lightclient): this always needs to be updated when a mainnet timestamp is set.
legacyConfig := *params.MainnetChainConfig legacyConfig := *params.MainnetChainConfig
legacyConfig.ShanghaiTime = nil legacyConfig.ShanghaiTime = nil
legacyConfig.CancunTime = nil legacyConfig.CancunTime = nil
legacyConfig.PragueTime = nil
tests := []struct { tests := []struct {
config *params.ChainConfig config *params.ChainConfig
@ -314,9 +318,7 @@ func TestValidation(t *testing.T) {
// Local is mainnet Prague, remote announces Shanghai + knowledge about Cancun. Remote // Local is mainnet Prague, remote announces Shanghai + knowledge about Cancun. Remote
// is definitely out of sync. It may or may not need the Prague update, we don't know yet. // is definitely out of sync. It may or may not need the Prague update, we don't know yet.
// {params.MainnetChainConfig, 0, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 1710338135}, nil},
// TODO(karalabe): Enable this when Cancun **and** Prague is specced, update all the numbers
//{params.MainnetChainConfig, 0, 0, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}, nil},
// Local is mainnet Shanghai, remote announces Cancun. Local is out of sync, accept. // Local is mainnet Shanghai, remote announces Cancun. Local is out of sync, accept.
{params.MainnetChainConfig, 21000000, 1700000000, ID{Hash: checksumToBytes(0x9f3d2254), Next: 0}, nil}, {params.MainnetChainConfig, 21000000, 1700000000, ID{Hash: checksumToBytes(0x9f3d2254), Next: 0}, nil},
@ -324,8 +326,7 @@ func TestValidation(t *testing.T) {
// Local is mainnet Shanghai, remote announces Cancun, but is not aware of Prague. Local // Local is mainnet Shanghai, remote announces Cancun, but is not aware of Prague. Local
// out of sync. Local also knows about a future fork, but that is uncertain yet. // out of sync. Local also knows about a future fork, but that is uncertain yet.
// //
// TODO(karalabe): Enable this when Cancun **and** Prague is specced, update remote checksum {params.MainnetChainConfig, 21000000, 1678000000, ID{Hash: checksumToBytes(0xc376cf8b), Next: 0}, nil},
//{params.MainnetChainConfig, 21000000, 1678000000, ID{Hash: checksumToBytes(0x00000000), Next: 0}, nil},
// Local is mainnet Cancun. remote announces Shanghai but is not aware of further forks. // Local is mainnet Cancun. remote announces Shanghai but is not aware of further forks.
// Remote needs software update. // Remote needs software update.
@ -342,11 +343,11 @@ func TestValidation(t *testing.T) {
// Local is mainnet Shanghai, remote is random Shanghai. // Local is mainnet Shanghai, remote is random Shanghai.
{params.MainnetChainConfig, 20000000, 1681338455, ID{Hash: checksumToBytes(0x12345678), Next: 0}, ErrLocalIncompatibleOrStale}, {params.MainnetChainConfig, 20000000, 1681338455, ID{Hash: checksumToBytes(0x12345678), Next: 0}, ErrLocalIncompatibleOrStale},
// Local is mainnet Cancun, far in the future. Remote announces Gopherium (non existing fork) // Local is mainnet Prague, far in the future. Remote announces Gopherium (non existing fork)
// at some future timestamp 8888888888, for itself, but past block for local. Local is incompatible. // at some future timestamp 8888888888, for itself, but past block for local. Local is incompatible.
// //
// This case detects non-upgraded nodes with majority hash power (typical Ropsten mess). // This case detects non-upgraded nodes with majority hash power (typical Ropsten mess).
{params.MainnetChainConfig, 88888888, 8888888888, ID{Hash: checksumToBytes(0x9f3d2254), Next: 8888888888}, ErrLocalIncompatibleOrStale}, {params.MainnetChainConfig, 88888888, 8888888888, ID{Hash: checksumToBytes(0xc376cf8b), Next: 8888888888}, ErrLocalIncompatibleOrStale},
// Local is mainnet Shanghai. Remote is also in Shanghai, but announces Gopherium (non existing // Local is mainnet Shanghai. Remote is also in Shanghai, but announces Gopherium (non existing
// fork) at timestamp 1668000000, before Cancun. Local is incompatible. // fork) at timestamp 1668000000, before Cancun. Local is incompatible.

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package ethconfig package history
import ( import (
"fmt" "fmt"
@ -27,22 +27,22 @@ import (
type HistoryMode uint32 type HistoryMode uint32
const ( const (
// AllHistory (default) means that all chain history down to genesis block will be kept. // KeepAll (default) means that all chain history down to genesis block will be kept.
AllHistory HistoryMode = iota KeepAll HistoryMode = iota
// PostMergeHistory sets the history pruning point to the merge activation block. // KeepPostMerge sets the history pruning point to the merge activation block.
PostMergeHistory KeepPostMerge
) )
func (m HistoryMode) IsValid() bool { func (m HistoryMode) IsValid() bool {
return m <= PostMergeHistory return m <= KeepPostMerge
} }
func (m HistoryMode) String() string { func (m HistoryMode) String() string {
switch m { switch m {
case AllHistory: case KeepAll:
return "all" return "all"
case PostMergeHistory: case KeepPostMerge:
return "postmerge" return "postmerge"
default: default:
return fmt.Sprintf("invalid HistoryMode(%d)", m) return fmt.Sprintf("invalid HistoryMode(%d)", m)
@ -61,24 +61,24 @@ func (m HistoryMode) MarshalText() ([]byte, error) {
func (m *HistoryMode) UnmarshalText(text []byte) error { func (m *HistoryMode) UnmarshalText(text []byte) error {
switch string(text) { switch string(text) {
case "all": case "all":
*m = AllHistory *m = KeepAll
case "postmerge": case "postmerge":
*m = PostMergeHistory *m = KeepPostMerge
default: default:
return fmt.Errorf(`unknown sync mode %q, want "all" or "postmerge"`, text) return fmt.Errorf(`unknown sync mode %q, want "all" or "postmerge"`, text)
} }
return nil return nil
} }
type HistoryPrunePoint struct { type PrunePoint struct {
BlockNumber uint64 BlockNumber uint64
BlockHash common.Hash BlockHash common.Hash
} }
// HistoryPrunePoints contains the pre-defined history pruning cutoff blocks for known networks. // PrunePoints the pre-defined history pruning cutoff blocks for known networks.
// They point to the first post-merge block. Any pruning should truncate *up to* but excluding // They point to the first post-merge block. Any pruning should truncate *up to* but excluding
// given block. // given block.
var HistoryPrunePoints = map[common.Hash]*HistoryPrunePoint{ var PrunePoints = map[common.Hash]*PrunePoint{
// mainnet // mainnet
params.MainnetGenesisHash: { params.MainnetGenesisHash: {
BlockNumber: 15537393, BlockNumber: 15537393,
@ -91,7 +91,7 @@ var HistoryPrunePoints = map[common.Hash]*HistoryPrunePoint{
}, },
} }
// PrunedHistoryError is returned when the requested history is pruned. // PrunedHistoryError is returned by APIs when the requested history is pruned.
type PrunedHistoryError struct{} type PrunedHistoryError struct{}
func (e *PrunedHistoryError) Error() string { return "pruned history unavailable" } func (e *PrunedHistoryError) Error() string { return "pruned history unavailable" }

View file

@ -434,6 +434,7 @@ func DeleteBlockLvPointers(db ethdb.KeyValueStore, blocks common.Range[uint64],
// FilterMapsRange is a storage representation of the block range covered by the // FilterMapsRange is a storage representation of the block range covered by the
// filter maps structure and the corresponting log value index range. // filter maps structure and the corresponting log value index range.
type FilterMapsRange struct { type FilterMapsRange struct {
Version uint32
HeadIndexed bool HeadIndexed bool
HeadDelimiter uint64 HeadDelimiter uint64
BlocksFirst, BlocksAfterLast uint64 BlocksFirst, BlocksAfterLast uint64

View file

@ -188,7 +188,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
c.OnAccount(address, account) c.OnAccount(address, account)
accounts++ accounts++
if time.Since(logged) > 8*time.Second { if time.Since(logged) > 8*time.Second {
log.Info("Trie dumping in progress", "at", it.Key, "accounts", accounts, log.Info("Trie dumping in progress", "at", common.Bytes2Hex(it.Key), "accounts", accounts,
"elapsed", common.PrettyDuration(time.Since(start))) "elapsed", common.PrettyDuration(time.Since(start)))
logged = time.Now() logged = time.Now()
} }

View file

@ -196,6 +196,19 @@ func (indexer *txIndexer) repair(head uint64) {
} }
} }
// resolveHead resolves the block number of the current chain head.
func (indexer *txIndexer) resolveHead() uint64 {
headBlockHash := rawdb.ReadHeadBlockHash(indexer.db)
if headBlockHash == (common.Hash{}) {
return 0
}
headBlockNumber := rawdb.ReadHeaderNumber(indexer.db, headBlockHash)
if headBlockNumber == nil {
return 0
}
return *headBlockNumber
}
// loop is the scheduler of the indexer, assigning indexing/unindexing tasks depending // loop is the scheduler of the indexer, assigning indexing/unindexing tasks depending
// on the received chain event. // on the received chain event.
func (indexer *txIndexer) loop(chain *BlockChain) { func (indexer *txIndexer) loop(chain *BlockChain) {
@ -203,9 +216,9 @@ func (indexer *txIndexer) loop(chain *BlockChain) {
// Listening to chain events and manipulate the transaction indexes. // Listening to chain events and manipulate the transaction indexes.
var ( var (
stop chan struct{} // Non-nil if background routine is active stop chan struct{} // Non-nil if background routine is active
done chan struct{} // Non-nil if background routine is active done chan struct{} // Non-nil if background routine is active
head = rawdb.ReadHeadBlock(indexer.db).NumberU64() // The latest announced chain head head = indexer.resolveHead() // The latest announced chain head
headCh = make(chan ChainHeadEvent) headCh = make(chan ChainHeadEvent)
sub = chain.SubscribeChainHeadEvent(headCh) sub = chain.SubscribeChainHeadEvent(headCh)

View file

@ -1827,6 +1827,16 @@ func (t *lookup) Remove(hash common.Hash) {
delete(t.txs, hash) delete(t.txs, hash)
} }
// Clear resets the lookup structure, removing all stored entries.
func (t *lookup) Clear() {
t.lock.Lock()
defer t.lock.Unlock()
t.slots = 0
t.txs = make(map[common.Hash]*types.Transaction)
t.auths = make(map[common.Address][]common.Hash)
}
// TxsBelowTip finds all remote transactions below the given tip threshold. // TxsBelowTip finds all remote transactions below the given tip threshold.
func (t *lookup) TxsBelowTip(threshold *big.Int) types.Transactions { func (t *lookup) TxsBelowTip(threshold *big.Int) types.Transactions {
found := make(types.Transactions, 0, 128) found := make(types.Transactions, 0, 128)
@ -1923,7 +1933,7 @@ func (pool *LegacyPool) Clear() {
for addr := range pool.queue { for addr := range pool.queue {
pool.reserver.Release(addr) pool.reserver.Release(addr)
} }
pool.all = newLookup() pool.all.Clear()
pool.priced = newPricedList(pool.all) pool.priced = newPricedList(pool.all)
pool.pending = make(map[common.Address]*list) pool.pending = make(map[common.Address]*list)
pool.queue = make(map[common.Address]*list) pool.queue = make(map[common.Address]*list)

View file

@ -186,13 +186,15 @@ func (p *TxPool) loop(head *types.Header) {
// Try to inject a busy marker and start a reset if successful // Try to inject a busy marker and start a reset if successful
select { select {
case resetBusy <- struct{}{}: case resetBusy <- struct{}{}:
statedb, err := p.chain.StateAt(newHead.Root) // Updates the statedb with the new chain head. The head state may be
if err != nil { // unavailable if the initial state sync has not yet completed.
log.Crit("Failed to reset txpool state", "err", err) if statedb, err := p.chain.StateAt(newHead.Root); err != nil {
log.Error("Failed to reset txpool state", "err", err)
} else {
p.stateLock.Lock()
p.state = statedb
p.stateLock.Unlock()
} }
p.stateLock.Lock()
p.state = statedb
p.stateLock.Unlock()
// Busy marker injected, start a new subpool reset // Busy marker injected, start a new subpool reset
go func(oldHead, newHead *types.Header) { go func(oldHead, newHead *types.Header) {

View file

@ -29,12 +29,12 @@ import (
"github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/filtermaps" "github.com/ethereum/go-ethereum/core/filtermaps"
"github.com/ethereum/go-ethereum/core/history"
"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/txpool" "github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
@ -156,7 +156,7 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe
} }
block := b.eth.blockchain.GetBlockByNumber(bn) block := b.eth.blockchain.GetBlockByNumber(bn)
if block == nil && bn < b.HistoryPruningCutoff() { if block == nil && bn < b.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{} return nil, &history.PrunedHistoryError{}
} }
return block, nil return block, nil
} }
@ -168,7 +168,7 @@ func (b *EthAPIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*typ
} }
block := b.eth.blockchain.GetBlock(hash, *number) block := b.eth.blockchain.GetBlock(hash, *number)
if block == nil && *number < b.HistoryPruningCutoff() { if block == nil && *number < b.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{} return nil, &history.PrunedHistoryError{}
} }
return block, nil return block, nil
} }
@ -181,7 +181,7 @@ func (b *EthAPIBackend) GetBody(ctx context.Context, hash common.Hash, number rp
body := b.eth.blockchain.GetBody(hash) body := b.eth.blockchain.GetBody(hash)
if body == nil { if body == nil {
if uint64(number) < b.HistoryPruningCutoff() { if uint64(number) < b.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{} return nil, &history.PrunedHistoryError{}
} }
return nil, errors.New("block body not found") return nil, errors.New("block body not found")
} }
@ -203,7 +203,7 @@ func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r
block := b.eth.blockchain.GetBlock(hash, header.Number.Uint64()) block := b.eth.blockchain.GetBlock(hash, header.Number.Uint64())
if block == nil { if block == nil {
if header.Number.Uint64() < b.HistoryPruningCutoff() { if header.Number.Uint64() < b.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{} return nil, &history.PrunedHistoryError{}
} }
return nil, errors.New("header found, but block body is missing") return nil, errors.New("header found, but block body is missing")
} }

View file

@ -145,7 +145,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
// Here we determine genesis hash and active ChainConfig. // Here we determine genesis hash and active ChainConfig.
// We need these to figure out the consensus parameters and to set up history pruning. // We need these to figure out the consensus parameters and to set up history pruning.
chainConfig, genesisHash, err := core.LoadChainConfig(chainDb, config.Genesis) chainConfig, _, err := core.LoadChainConfig(chainDb, config.Genesis)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -153,22 +153,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Validate history pruning configuration.
var (
cutoffNumber uint64
cutoffHash common.Hash
)
if config.HistoryMode == ethconfig.PostMergeHistory {
prunecfg, ok := ethconfig.HistoryPrunePoints[genesisHash]
if !ok {
return nil, fmt.Errorf("no history pruning point is defined for genesis %x", genesisHash)
}
cutoffNumber = prunecfg.BlockNumber
cutoffHash = prunecfg.BlockHash
log.Info("Chain cutoff configured", "number", cutoffNumber, "hash", cutoffHash)
}
// Set networkID to chainID by default. // Set networkID to chainID by default.
networkID := config.NetworkId networkID := config.NetworkId
if networkID == 0 { if networkID == 0 {
@ -195,6 +179,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
} }
log.Info("Initialising Ethereum protocol", "network", networkID, "dbversion", dbVer) log.Info("Initialising Ethereum protocol", "network", networkID, "dbversion", dbVer)
// Create BlockChain object.
if !config.SkipBcVersionCheck { if !config.SkipBcVersionCheck {
if bcVersion != nil && *bcVersion > core.BlockChainVersion { if bcVersion != nil && *bcVersion > core.BlockChainVersion {
return nil, fmt.Errorf("database version is v%d, Geth %s only supports v%d", *bcVersion, version.WithMeta, core.BlockChainVersion) return nil, fmt.Errorf("database version is v%d, Geth %s only supports v%d", *bcVersion, version.WithMeta, core.BlockChainVersion)
@ -210,17 +195,16 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
EnablePreimageRecording: config.EnablePreimageRecording, EnablePreimageRecording: config.EnablePreimageRecording,
} }
cacheConfig = &core.CacheConfig{ cacheConfig = &core.CacheConfig{
TrieCleanLimit: config.TrieCleanCache, TrieCleanLimit: config.TrieCleanCache,
TrieCleanNoPrefetch: config.NoPrefetch, TrieCleanNoPrefetch: config.NoPrefetch,
TrieDirtyLimit: config.TrieDirtyCache, TrieDirtyLimit: config.TrieDirtyCache,
TrieDirtyDisabled: config.NoPruning, TrieDirtyDisabled: config.NoPruning,
TrieTimeLimit: config.TrieTimeout, TrieTimeLimit: config.TrieTimeout,
SnapshotLimit: config.SnapshotCache, SnapshotLimit: config.SnapshotCache,
Preimages: config.Preimages, Preimages: config.Preimages,
StateHistory: config.StateHistory, StateHistory: config.StateHistory,
StateScheme: scheme, StateScheme: scheme,
HistoryPruningCutoffNumber: cutoffNumber, ChainHistoryMode: config.HistoryMode,
HistoryPruningCutoffHash: cutoffHash,
} }
) )
if config.VMTrace != "" { if config.VMTrace != "" {
@ -246,6 +230,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Initialize filtermaps log index.
fmConfig := filtermaps.Config{ fmConfig := filtermaps.Config{
History: config.LogHistory, History: config.LogHistory,
Disabled: config.LogNoHistory, Disabled: config.LogNoHistory,
@ -261,6 +247,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
eth.filterMaps = filtermaps.NewFilterMaps(chainDb, chainView, historyCutoff, finalBlock, filtermaps.DefaultParams, fmConfig) eth.filterMaps = filtermaps.NewFilterMaps(chainDb, chainView, historyCutoff, finalBlock, filtermaps.DefaultParams, fmConfig)
eth.closeFilterMaps = make(chan chan struct{}) eth.closeFilterMaps = make(chan chan struct{})
// TxPool
if config.TxPool.Journal != "" { if config.TxPool.Journal != "" {
config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal) config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal)
} }
@ -285,6 +272,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
eth.localTxTracker = locals.New(config.TxPool.Journal, rejournal, eth.blockchain.Config(), eth.txPool) eth.localTxTracker = locals.New(config.TxPool.Journal, rejournal, eth.blockchain.Config(), eth.txPool)
stack.RegisterLifecycle(eth.localTxTracker) stack.RegisterLifecycle(eth.localTxTracker)
} }
// Permit the downloader to use the trie cache allowance during fast sync // Permit the downloader to use the trie cache allowance during fast sync
cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit
if eth.handler, err = newHandler(&handlerConfig{ if eth.handler, err = newHandler(&handlerConfig{

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/history"
"github.com/ethereum/go-ethereum/core/txpool/blobpool" "github.com/ethereum/go-ethereum/core/txpool/blobpool"
"github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/core/txpool/legacypool"
"github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/gasprice"
@ -48,7 +49,7 @@ var FullNodeGPO = gasprice.Config{
// Defaults contains default settings for use on the Ethereum main net. // Defaults contains default settings for use on the Ethereum main net.
var Defaults = Config{ var Defaults = Config{
HistoryMode: AllHistory, HistoryMode: history.KeepAll,
SyncMode: SnapSync, SyncMode: SnapSync,
NetworkId: 0, // enable auto configuration of networkID == chainID NetworkId: 0, // enable auto configuration of networkID == chainID
TxLookupLimit: 2350000, TxLookupLimit: 2350000,
@ -84,7 +85,7 @@ type Config struct {
SyncMode SyncMode SyncMode SyncMode
// HistoryMode configures chain history retention. // HistoryMode configures chain history retention.
HistoryMode HistoryMode HistoryMode history.HistoryMode
// This can be set to list of enrtree:// URLs which will be queried for // This can be set to list of enrtree:// URLs which will be queried for
// nodes to connect to. // nodes to connect to.

View file

@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/history"
"github.com/ethereum/go-ethereum/core/txpool/blobpool" "github.com/ethereum/go-ethereum/core/txpool/blobpool"
"github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/core/txpool/legacypool"
"github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/gasprice"
@ -19,13 +20,16 @@ func (c Config) MarshalTOML() (interface{}, error) {
Genesis *core.Genesis `toml:",omitempty"` Genesis *core.Genesis `toml:",omitempty"`
NetworkId uint64 NetworkId uint64
SyncMode SyncMode SyncMode SyncMode
HistoryMode HistoryMode HistoryMode history.HistoryMode
EthDiscoveryURLs []string EthDiscoveryURLs []string
SnapDiscoveryURLs []string SnapDiscoveryURLs []string
NoPruning bool NoPruning bool
NoPrefetch bool NoPrefetch bool
TxLookupLimit uint64 `toml:",omitempty"` TxLookupLimit uint64 `toml:",omitempty"`
TransactionHistory uint64 `toml:",omitempty"` TransactionHistory uint64 `toml:",omitempty"`
LogHistory uint64 `toml:",omitempty"`
LogNoHistory bool `toml:",omitempty"`
LogExportCheckpoints string
StateHistory uint64 `toml:",omitempty"` StateHistory uint64 `toml:",omitempty"`
StateScheme string `toml:",omitempty"` StateScheme string `toml:",omitempty"`
RequiredBlocks map[uint64]common.Hash `toml:"-"` RequiredBlocks map[uint64]common.Hash `toml:"-"`
@ -63,6 +67,9 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.NoPrefetch = c.NoPrefetch enc.NoPrefetch = c.NoPrefetch
enc.TxLookupLimit = c.TxLookupLimit enc.TxLookupLimit = c.TxLookupLimit
enc.TransactionHistory = c.TransactionHistory enc.TransactionHistory = c.TransactionHistory
enc.LogHistory = c.LogHistory
enc.LogNoHistory = c.LogNoHistory
enc.LogExportCheckpoints = c.LogExportCheckpoints
enc.StateHistory = c.StateHistory enc.StateHistory = c.StateHistory
enc.StateScheme = c.StateScheme enc.StateScheme = c.StateScheme
enc.RequiredBlocks = c.RequiredBlocks enc.RequiredBlocks = c.RequiredBlocks
@ -97,13 +104,16 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
Genesis *core.Genesis `toml:",omitempty"` Genesis *core.Genesis `toml:",omitempty"`
NetworkId *uint64 NetworkId *uint64
SyncMode *SyncMode SyncMode *SyncMode
HistoryMode *HistoryMode HistoryMode *history.HistoryMode
EthDiscoveryURLs []string EthDiscoveryURLs []string
SnapDiscoveryURLs []string SnapDiscoveryURLs []string
NoPruning *bool NoPruning *bool
NoPrefetch *bool NoPrefetch *bool
TxLookupLimit *uint64 `toml:",omitempty"` TxLookupLimit *uint64 `toml:",omitempty"`
TransactionHistory *uint64 `toml:",omitempty"` TransactionHistory *uint64 `toml:",omitempty"`
LogHistory *uint64 `toml:",omitempty"`
LogNoHistory *bool `toml:",omitempty"`
LogExportCheckpoints *string
StateHistory *uint64 `toml:",omitempty"` StateHistory *uint64 `toml:",omitempty"`
StateScheme *string `toml:",omitempty"` StateScheme *string `toml:",omitempty"`
RequiredBlocks map[uint64]common.Hash `toml:"-"` RequiredBlocks map[uint64]common.Hash `toml:"-"`
@ -164,6 +174,15 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.TransactionHistory != nil { if dec.TransactionHistory != nil {
c.TransactionHistory = *dec.TransactionHistory c.TransactionHistory = *dec.TransactionHistory
} }
if dec.LogHistory != nil {
c.LogHistory = *dec.LogHistory
}
if dec.LogNoHistory != nil {
c.LogNoHistory = *dec.LogNoHistory
}
if dec.LogExportCheckpoints != nil {
c.LogExportCheckpoints = *dec.LogExportCheckpoints
}
if dec.StateHistory != nil { if dec.StateHistory != nil {
c.StateHistory = *dec.StateHistory c.StateHistory = *dec.StateHistory
} }

View file

@ -28,8 +28,8 @@ import (
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/history"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
@ -360,7 +360,7 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type
return nil, errInvalidBlockRange return nil, errInvalidBlockRange
} }
if begin > 0 && begin < int64(api.events.backend.HistoryPruningCutoff()) { if begin > 0 && begin < int64(api.events.backend.HistoryPruningCutoff()) {
return nil, &ethconfig.PrunedHistoryError{} return nil, &history.PrunedHistoryError{}
} }
// Construct the range filter // Construct the range filter
filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics) filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics)

View file

@ -26,8 +26,8 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/filtermaps" "github.com/ethereum/go-ethereum/core/filtermaps"
"github.com/ethereum/go-ethereum/core/history"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
@ -88,7 +88,7 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
return nil, errors.New("unknown block") return nil, errors.New("unknown block")
} }
if header.Number.Uint64() < f.sys.backend.HistoryPruningCutoff() { if header.Number.Uint64() < f.sys.backend.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{} return nil, &history.PrunedHistoryError{}
} }
return f.blockLogs(ctx, header) return f.blockLogs(ctx, header)
} }

View file

@ -30,8 +30,8 @@ import (
"github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/filtermaps" "github.com/ethereum/go-ethereum/core/filtermaps"
"github.com/ethereum/go-ethereum/core/history"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -311,7 +311,7 @@ func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*typ
} }
// Queries beyond the pruning cutoff are not supported. // Queries beyond the pruning cutoff are not supported.
if uint64(from) < es.backend.HistoryPruningCutoff() { if uint64(from) < es.backend.HistoryPruningCutoff() {
return nil, &ethconfig.PrunedHistoryError{} return nil, &history.PrunedHistoryError{}
} }
// only interested in new mined logs // only interested in new mined logs

View file

@ -484,7 +484,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) {
total := new(big.Int).Exp(direct, big.NewInt(2), nil) // Stabilise total peer count a bit based on sqrt peers total := new(big.Int).Exp(direct, big.NewInt(2), nil) // Stabilise total peer count a bit based on sqrt peers
var ( var (
signer = types.LatestSignerForChainID(h.chain.Config().ChainID) // Don't care about chain status, we just need *a* sender signer = types.LatestSigner(h.chain.Config()) // Don't care about chain status, we just need *a* sender
hasher = crypto.NewKeccakState() hasher = crypto.NewKeccakState()
hash = make([]byte, 32) hash = make([]byte, 32)
) )

View file

@ -49,7 +49,7 @@ var (
serveSuccessMeter = metrics.NewRegisteredMeter("p2p/serves/success", nil) serveSuccessMeter = metrics.NewRegisteredMeter("p2p/serves/success", nil)
dialMeter = metrics.NewRegisteredMeter("p2p/dials", nil) dialMeter = metrics.NewRegisteredMeter("p2p/dials", nil)
dialSuccessMeter = metrics.NewRegisteredMeter("p2p/dials/success", nil) dialSuccessMeter = metrics.NewRegisteredMeter("p2p/dials/success", nil)
dialConnectionError = metrics.NewRegisteredMeter("p2p/dials/error/connection", nil) dialConnectionError = metrics.NewRegisteredMeter("p2p/dials/error/connection", nil) // dial timeout; no route to host; connection refused; network is unreachable
// count peers that stayed connected for at least 1 min // count peers that stayed connected for at least 1 min
serve1MinSuccessMeter = metrics.NewRegisteredMeter("p2p/serves/success/1min", nil) serve1MinSuccessMeter = metrics.NewRegisteredMeter("p2p/serves/success/1min", nil)
@ -61,34 +61,41 @@ var (
dialSelf = metrics.NewRegisteredMeter("p2p/dials/error/self", nil) dialSelf = metrics.NewRegisteredMeter("p2p/dials/error/self", nil)
dialUselessPeer = metrics.NewRegisteredMeter("p2p/dials/error/useless", nil) dialUselessPeer = metrics.NewRegisteredMeter("p2p/dials/error/useless", nil)
dialUnexpectedIdentity = metrics.NewRegisteredMeter("p2p/dials/error/id/unexpected", nil) dialUnexpectedIdentity = metrics.NewRegisteredMeter("p2p/dials/error/id/unexpected", nil)
dialEncHandshakeError = metrics.NewRegisteredMeter("p2p/dials/error/rlpx/enc", nil) dialEncHandshakeError = metrics.NewRegisteredMeter("p2p/dials/error/rlpx/enc", nil) // EOF; connection reset during handshake; message too big; i/o timeout
dialProtoHandshakeError = metrics.NewRegisteredMeter("p2p/dials/error/rlpx/proto", nil) dialProtoHandshakeError = metrics.NewRegisteredMeter("p2p/dials/error/rlpx/proto", nil) // EOF
// capture the rest of errors that are not handled by the above meters
dialOtherError = metrics.NewRegisteredMeter("p2p/dials/error/other", nil)
) )
// markDialError matches errors that occur while setting up a dial connection // markDialError matches errors that occur while setting up a dial connection to the
// to the corresponding meter. // corresponding meter. We don't maintain meters for evert possible error, just for
// the most interesting ones.
func markDialError(err error) { func markDialError(err error) {
if !metrics.Enabled() { if !metrics.Enabled() {
return return
} }
if err2 := errors.Unwrap(err); err2 != nil {
err = err2 var reason DiscReason
} var handshakeErr *protoHandshakeError
switch err { d := errors.As(err, &reason)
case DiscTooManyPeers: switch {
case d && reason == DiscTooManyPeers:
dialTooManyPeers.Mark(1) dialTooManyPeers.Mark(1)
case DiscAlreadyConnected: case d && reason == DiscAlreadyConnected:
dialAlreadyConnected.Mark(1) dialAlreadyConnected.Mark(1)
case DiscSelf: case d && reason == DiscSelf:
dialSelf.Mark(1) dialSelf.Mark(1)
case DiscUselessPeer: case d && reason == DiscUselessPeer:
dialUselessPeer.Mark(1) dialUselessPeer.Mark(1)
case DiscUnexpectedIdentity: case d && reason == DiscUnexpectedIdentity:
dialUnexpectedIdentity.Mark(1) dialUnexpectedIdentity.Mark(1)
case errEncHandshakeError: case errors.As(err, &handshakeErr):
dialEncHandshakeError.Mark(1)
case errProtoHandshakeError:
dialProtoHandshakeError.Mark(1) dialProtoHandshakeError.Mark(1)
case errors.Is(err, errEncHandshakeError):
dialEncHandshakeError.Mark(1)
default:
dialOtherError.Mark(1)
} }
} }

View file

@ -66,11 +66,15 @@ const (
) )
var ( var (
errServerStopped = errors.New("server stopped") errServerStopped = errors.New("server stopped")
errEncHandshakeError = errors.New("rlpx enc error") errEncHandshakeError = errors.New("rlpx enc error")
errProtoHandshakeError = errors.New("rlpx proto error")
) )
type protoHandshakeError struct{ err error }
func (e *protoHandshakeError) Error() string { return fmt.Sprintf("rlpx proto error: %v", e.err) }
func (e *protoHandshakeError) Unwrap() error { return e.err }
// Server manages all peer connections. // Server manages all peer connections.
type Server struct { type Server struct {
// Config fields may not be modified while the server is running. // Config fields may not be modified while the server is running.
@ -907,7 +911,7 @@ func (srv *Server) setupConn(c *conn, dialDest *enode.Node) error {
phs, err := c.doProtoHandshake(srv.ourHandshake) phs, err := c.doProtoHandshake(srv.ourHandshake)
if err != nil { if err != nil {
clog.Trace("Failed p2p handshake", "err", err) clog.Trace("Failed p2p handshake", "err", err)
return fmt.Errorf("%w: %v", errProtoHandshakeError, err) return &protoHandshakeError{err: err}
} }
if id := c.node.ID(); !bytes.Equal(crypto.Keccak256(phs.ID), id[:]) { if id := c.node.ID(); !bytes.Equal(crypto.Keccak256(phs.ID), id[:]) {
clog.Trace("Wrong devp2p handshake identity", "phsid", hex.EncodeToString(phs.ID)) clog.Trace("Wrong devp2p handshake identity", "phsid", hex.EncodeToString(phs.ID))

View file

@ -410,11 +410,11 @@ func TestServerSetupConn(t *testing.T) {
wantCloseErr: DiscUnexpectedIdentity, wantCloseErr: DiscUnexpectedIdentity,
}, },
{ {
tt: &setupTransport{pubkey: clientpub, protoHandshakeErr: errProtoHandshakeError}, tt: &setupTransport{pubkey: clientpub, protoHandshakeErr: DiscTooManyPeers},
dialDest: enode.NewV4(clientpub, nil, 0, 0), dialDest: enode.NewV4(clientpub, nil, 0, 0),
flags: dynDialedConn, flags: dynDialedConn,
wantCalls: "doEncHandshake,doProtoHandshake,close,", wantCalls: "doEncHandshake,doProtoHandshake,close,",
wantCloseErr: errProtoHandshakeError, wantCloseErr: DiscTooManyPeers,
}, },
{ {
tt: &setupTransport{pubkey: srvpub, phs: protoHandshake{ID: crypto.FromECDSAPub(srvpub)[1:]}}, tt: &setupTransport{pubkey: srvpub, phs: protoHandshake{ID: crypto.FromECDSAPub(srvpub)[1:]}},

View file

@ -60,10 +60,12 @@ var (
TerminalTotalDifficulty: MainnetTerminalTotalDifficulty, // 58_750_000_000_000_000_000_000 TerminalTotalDifficulty: MainnetTerminalTotalDifficulty, // 58_750_000_000_000_000_000_000
ShanghaiTime: newUint64(1681338455), ShanghaiTime: newUint64(1681338455),
CancunTime: newUint64(1710338135), CancunTime: newUint64(1710338135),
PragueTime: newUint64(1746612311),
DepositContractAddress: common.HexToAddress("0x00000000219ab540356cbb839cbe05303d7705fa"), DepositContractAddress: common.HexToAddress("0x00000000219ab540356cbb839cbe05303d7705fa"),
Ethash: new(EthashConfig), Ethash: new(EthashConfig),
BlobScheduleConfig: &BlobScheduleConfig{ BlobScheduleConfig: &BlobScheduleConfig{
Cancun: DefaultCancunBlobConfig, Cancun: DefaultCancunBlobConfig,
Prague: DefaultPragueBlobConfig,
}, },
} }
// HoleskyChainConfig contains the chain parameters to run a node on the Holesky test network. // HoleskyChainConfig contains the chain parameters to run a node on the Holesky test network.
@ -907,6 +909,23 @@ func (c *ChainConfig) LatestFork(time uint64) forks.Fork {
} }
} }
// Timestamp returns the timestamp associated with the fork or returns nil if
// the fork isn't defined or isn't a time-based fork.
func (c *ChainConfig) Timestamp(fork forks.Fork) *uint64 {
switch {
case fork == forks.Osaka:
return c.OsakaTime
case fork == forks.Prague:
return c.PragueTime
case fork == forks.Cancun:
return c.CancunTime
case fork == forks.Shanghai:
return c.ShanghaiTime
default:
return nil
}
}
// isForkBlockIncompatible returns true if a fork scheduled at block s1 cannot be // isForkBlockIncompatible returns true if a fork scheduled at block s1 cannot be
// rescheduled to block s2 because head is already past the fork. // rescheduled to block s2 because head is already past the fork.
func isForkBlockIncompatible(s1, s2, head *big.Int) bool { func isForkBlockIncompatible(s1, s2, head *big.Int) bool {

View file

@ -20,7 +20,7 @@ package forks
type Fork int type Fork int
const ( const (
Frontier = iota Frontier Fork = iota
FrontierThawing FrontierThawing
Homestead Homestead
DAO DAO
@ -41,3 +41,35 @@ const (
Prague Prague
Osaka Osaka
) )
// String implements fmt.Stringer.
func (f Fork) String() string {
s, ok := forkToString[f]
if !ok {
return "Unknown fork"
}
return s
}
var forkToString = map[Fork]string{
Frontier: "Frontier",
FrontierThawing: "Frontier Thawing",
Homestead: "Homestead",
DAO: "DAO",
TangerineWhistle: "Tangerine Whistle",
SpuriousDragon: "Spurious Dragon",
Byzantium: "Byzantium",
Constantinople: "Constantinople",
Petersburg: "Petersburg",
Istanbul: "Istanbul",
MuirGlacier: "Muir Glacier",
Berlin: "Berlin",
London: "London",
ArrowGlacier: "Arrow Glacier",
GrayGlacier: "Gray Glacier",
Paris: "Paris",
Shanghai: "Shanghai",
Cancun: "Cancun",
Prague: "Prague",
Osaka: "Osaka",
}