mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 13:21:37 +00:00
core/history: refactor pruning configuration
This commit is contained in:
parent
ab357151da
commit
3208a243a3
7 changed files with 159 additions and 137 deletions
|
|
@ -731,13 +731,16 @@ func pruneHistory(ctx *cli.Context) error {
|
|||
|
||||
// Determine the prune point based on the history mode.
|
||||
genesisHash := chain.Genesis().Hash()
|
||||
prunePoint := history.GetPrunePoint(genesisHash, mode)
|
||||
if prunePoint == nil {
|
||||
policy, err := history.NewPolicy(mode, genesisHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if policy.Target == nil {
|
||||
return fmt.Errorf("prune point for %q not found for this network", mode.String())
|
||||
}
|
||||
var (
|
||||
targetBlock = prunePoint.BlockNumber
|
||||
targetBlockHash = prunePoint.BlockHash
|
||||
targetBlock = policy.Target.BlockNumber
|
||||
targetBlockHash = policy.Target.BlockHash
|
||||
)
|
||||
|
||||
// Check the current freezer tail to see if pruning is needed/possible.
|
||||
|
|
|
|||
|
|
@ -155,7 +155,9 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
|
|||
}
|
||||
|
||||
cfg.historyPruneBlock = new(uint64)
|
||||
*cfg.historyPruneBlock = history.PrunePoints[params.MainnetGenesisHash].BlockNumber
|
||||
if p, err := history.NewPolicy(history.KeepPostMerge, params.MainnetGenesisHash); err == nil {
|
||||
*cfg.historyPruneBlock = p.Target.BlockNumber
|
||||
}
|
||||
case ctx.Bool(testSepoliaFlag.Name):
|
||||
cfg.fsys = builtinTestFiles
|
||||
if ctx.IsSet(filterQueryFileFlag.Name) {
|
||||
|
|
@ -180,7 +182,9 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
|
|||
}
|
||||
|
||||
cfg.historyPruneBlock = new(uint64)
|
||||
*cfg.historyPruneBlock = history.PrunePoints[params.SepoliaGenesisHash].BlockNumber
|
||||
if p, err := history.NewPolicy(history.KeepPostMerge, params.SepoliaGenesisHash); err == nil {
|
||||
*cfg.historyPruneBlock = p.Target.BlockNumber
|
||||
}
|
||||
default:
|
||||
cfg.fsys = os.DirFS(".")
|
||||
cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)
|
||||
|
|
|
|||
|
|
@ -194,9 +194,8 @@ type BlockChainConfig struct {
|
|||
SnapshotNoBuild bool // Whether the background generation is allowed
|
||||
SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it
|
||||
|
||||
// This defines the cutoff block for history expiry.
|
||||
// Blocks before this number may be unavailable in the chain database.
|
||||
ChainHistoryMode history.HistoryMode
|
||||
// HistoryPolicy defines the chain history pruning intent.
|
||||
HistoryPolicy history.HistoryPolicy
|
||||
|
||||
// Misc options
|
||||
NoPrefetch bool // Whether to disable heuristic state prefetching when processing blocks
|
||||
|
|
@ -227,13 +226,13 @@ type BlockChainConfig struct {
|
|||
// Note the returned object is safe to modify!
|
||||
func DefaultConfig() *BlockChainConfig {
|
||||
return &BlockChainConfig{
|
||||
TrieCleanLimit: 256,
|
||||
TrieDirtyLimit: 256,
|
||||
TrieTimeLimit: 5 * time.Minute,
|
||||
StateScheme: rawdb.HashScheme,
|
||||
SnapshotLimit: 256,
|
||||
SnapshotWait: true,
|
||||
ChainHistoryMode: history.KeepAll,
|
||||
TrieCleanLimit: 256,
|
||||
TrieDirtyLimit: 256,
|
||||
TrieTimeLimit: 5 * time.Minute,
|
||||
StateScheme: rawdb.HashScheme,
|
||||
SnapshotLimit: 256,
|
||||
SnapshotWait: true,
|
||||
HistoryPolicy: history.HistoryPolicy{Mode: history.KeepAll},
|
||||
// Transaction indexing is disabled by default.
|
||||
// This is appropriate for most unit tests.
|
||||
TxLookupLimit: -1,
|
||||
|
|
@ -715,82 +714,44 @@ func (bc *BlockChain) loadLastState() error {
|
|||
|
||||
// initializeHistoryPruning sets bc.historyPrunePoint.
|
||||
func (bc *BlockChain) initializeHistoryPruning(latest uint64) error {
|
||||
var (
|
||||
freezerTail, _ = bc.db.Tail()
|
||||
genesisHash = bc.genesisBlock.Hash()
|
||||
mergePoint = history.MergePrunePoints[genesisHash]
|
||||
praguePoint = history.PraguePrunePoints[genesisHash]
|
||||
)
|
||||
switch bc.cfg.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.
|
||||
if mergePoint != nil && freezerTail == mergePoint.BlockNumber {
|
||||
bc.historyPrunePoint.Store(mergePoint)
|
||||
return nil
|
||||
}
|
||||
if praguePoint != nil && freezerTail == praguePoint.BlockNumber {
|
||||
bc.historyPrunePoint.Store(praguePoint)
|
||||
return nil
|
||||
}
|
||||
log.Error("Chain history database is pruned with unknown configuration", "tail", freezerTail)
|
||||
return errors.New("unexpected database tail")
|
||||
freezerTail, _ := bc.db.Tail()
|
||||
policy := bc.cfg.HistoryPolicy
|
||||
|
||||
case history.KeepPostMerge:
|
||||
if mergePoint == nil {
|
||||
return errors.New("history pruning requested for unknown network")
|
||||
switch policy.Mode {
|
||||
case history.KeepAll:
|
||||
if freezerTail > 0 {
|
||||
// Database was pruned externally. Record the actual state.
|
||||
log.Warn("Chain history database is pruned", "tail", freezerTail, "mode", policy.Mode)
|
||||
bc.historyPrunePoint.Store(&history.PrunePoint{
|
||||
BlockNumber: freezerTail,
|
||||
BlockHash: bc.GetCanonicalHash(freezerTail),
|
||||
})
|
||||
}
|
||||
if freezerTail == 0 && latest != 0 {
|
||||
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is not pruned.", bc.cfg.ChainHistoryMode.String()))
|
||||
log.Error("Run 'geth prune-history --history.chain postmerge' to prune pre-merge history.")
|
||||
return errors.New("history pruning requested via configuration")
|
||||
}
|
||||
// Check if DB is pruned further than requested (to Prague).
|
||||
if praguePoint != nil && freezerTail == praguePoint.BlockNumber {
|
||||
log.Error("Chain history database is pruned to Prague block, but postmerge mode was requested.")
|
||||
log.Error("History cannot be unpruned. To restore history, use 'geth import-history'.")
|
||||
log.Error("If you intended to keep post-Prague history, use '--history.chain postprague' instead.")
|
||||
return errors.New("database pruned beyond requested history mode")
|
||||
}
|
||||
if freezerTail > 0 && freezerTail != mergePoint.BlockNumber {
|
||||
return errors.New("chain history database pruned to unknown block")
|
||||
}
|
||||
bc.historyPrunePoint.Store(mergePoint)
|
||||
return nil
|
||||
|
||||
case history.KeepPostPrague:
|
||||
if praguePoint == nil {
|
||||
return errors.New("history pruning requested for unknown network")
|
||||
}
|
||||
// Check if already at the prague prune point.
|
||||
if freezerTail == praguePoint.BlockNumber {
|
||||
bc.historyPrunePoint.Store(praguePoint)
|
||||
case history.KeepPostMerge, history.KeepPostPrague:
|
||||
target := policy.Target
|
||||
// Already at the target.
|
||||
if freezerTail == target.BlockNumber {
|
||||
bc.historyPrunePoint.Store(target)
|
||||
return nil
|
||||
}
|
||||
// Check if database needs pruning.
|
||||
if latest != 0 {
|
||||
if freezerTail == 0 {
|
||||
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is not pruned.", bc.cfg.ChainHistoryMode.String()))
|
||||
log.Error("Run 'geth prune-history --history.chain postprague' to prune pre-Prague history.")
|
||||
return errors.New("history pruning requested via configuration")
|
||||
}
|
||||
if mergePoint != nil && freezerTail == mergePoint.BlockNumber {
|
||||
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is only pruned to merge block.", bc.cfg.ChainHistoryMode.String()))
|
||||
log.Error("Run 'geth prune-history --history.chain postprague' to prune pre-Prague history.")
|
||||
return errors.New("history pruning requested via configuration")
|
||||
}
|
||||
log.Error("Chain history database is pruned to unknown block", "tail", freezerTail)
|
||||
return errors.New("unexpected database tail")
|
||||
// Database is pruned beyond the target.
|
||||
if freezerTail > target.BlockNumber {
|
||||
return fmt.Errorf("database pruned beyond requested history (tail=%d, target=%d)", freezerTail, target.BlockNumber)
|
||||
}
|
||||
// Fresh database (latest == 0), will sync from prague point.
|
||||
bc.historyPrunePoint.Store(praguePoint)
|
||||
// Database needs pruning (freezerTail < target).
|
||||
if latest != 0 {
|
||||
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is not pruned to the target block.", policy.Mode.String()))
|
||||
log.Error(fmt.Sprintf("Run 'geth prune-history --history.chain %s' to prune history.", policy.Mode.String()))
|
||||
return errors.New("history pruning required")
|
||||
}
|
||||
// Fresh database (latest == 0), will sync from target point.
|
||||
bc.historyPrunePoint.Store(target)
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid history mode: %d", bc.cfg.ChainHistoryMode)
|
||||
return fmt.Errorf("invalid history mode: %d", policy.Mode)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
"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/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
|
@ -4337,26 +4336,13 @@ func TestInsertChainWithCutoff(t *testing.T) {
|
|||
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 := DefaultConfig().WithStateScheme(rawdb.PathScheme)
|
||||
config.ChainHistoryMode = history.KeepPostMerge
|
||||
|
||||
db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||
defer db.Close()
|
||||
|
||||
options := DefaultConfig().WithStateScheme(rawdb.PathScheme)
|
||||
chain, _ := NewBlockChain(db, genesis, beacon.New(ethash.NewFaker()), options)
|
||||
chain, _ := NewBlockChain(db, genesis, beacon.New(ethash.NewFaker()), DefaultConfig().WithStateScheme(rawdb.PathScheme))
|
||||
defer chain.Stop()
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -77,57 +77,62 @@ func (m *HistoryMode) UnmarshalText(text []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// PrunePoint identifies a specific block for history pruning.
|
||||
type PrunePoint struct {
|
||||
BlockNumber uint64
|
||||
BlockHash common.Hash
|
||||
}
|
||||
|
||||
// MergePrunePoints contains 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
|
||||
// the given block.
|
||||
var MergePrunePoints = map[common.Hash]*PrunePoint{
|
||||
// mainnet
|
||||
params.MainnetGenesisHash: {
|
||||
BlockNumber: 15537393,
|
||||
BlockHash: common.HexToHash("0x55b11b918355b1ef9c5db810302ebad0bf2544255b530cdce90674d5887bb286"),
|
||||
// staticPrunePoints contains the pre-defined history pruning cutoff blocks for
|
||||
// known networks, keyed by history mode and genesis hash. They point to the first
|
||||
// block after the respective fork. Any pruning should truncate *up to* but
|
||||
// excluding the given block.
|
||||
var staticPrunePoints = map[HistoryMode]map[common.Hash]*PrunePoint{
|
||||
KeepPostMerge: {
|
||||
params.MainnetGenesisHash: {
|
||||
BlockNumber: 15537393,
|
||||
BlockHash: common.HexToHash("0x55b11b918355b1ef9c5db810302ebad0bf2544255b530cdce90674d5887bb286"),
|
||||
},
|
||||
params.SepoliaGenesisHash: {
|
||||
BlockNumber: 1450409,
|
||||
BlockHash: common.HexToHash("0x229f6b18ca1552f1d5146deceb5387333f40dc6275aebee3f2c5c4ece07d02db"),
|
||||
},
|
||||
},
|
||||
// sepolia
|
||||
params.SepoliaGenesisHash: {
|
||||
BlockNumber: 1450409,
|
||||
BlockHash: common.HexToHash("0x229f6b18ca1552f1d5146deceb5387333f40dc6275aebee3f2c5c4ece07d02db"),
|
||||
KeepPostPrague: {
|
||||
params.MainnetGenesisHash: {
|
||||
BlockNumber: 22431084,
|
||||
BlockHash: common.HexToHash("0x50c8cab760b2948349c590461b166773c45d8f4858cccf5a43025ab2960152e8"),
|
||||
},
|
||||
params.SepoliaGenesisHash: {
|
||||
BlockNumber: 7836331,
|
||||
BlockHash: common.HexToHash("0xe6571beb68bf24dbd8a6ba354518996920c55a3f8d8fdca423e391b8ad071f22"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// PraguePrunePoints contains the pre-defined history pruning cutoff blocks for the Prague
|
||||
// (Pectra) upgrade. They point to the first post-Prague block. Any pruning should truncate
|
||||
// *up to* but excluding the given block.
|
||||
var PraguePrunePoints = map[common.Hash]*PrunePoint{
|
||||
// mainnet - first Prague block (May 7, 2025)
|
||||
params.MainnetGenesisHash: {
|
||||
BlockNumber: 22431084,
|
||||
BlockHash: common.HexToHash("0x50c8cab760b2948349c590461b166773c45d8f4858cccf5a43025ab2960152e8"),
|
||||
},
|
||||
// sepolia - first Prague block (March 5, 2025)
|
||||
params.SepoliaGenesisHash: {
|
||||
BlockNumber: 7836331,
|
||||
BlockHash: common.HexToHash("0xe6571beb68bf24dbd8a6ba354518996920c55a3f8d8fdca423e391b8ad071f22"),
|
||||
},
|
||||
// HistoryPolicy describes the configured history pruning strategy. It captures
|
||||
// user intent as opposed to the actual DB state.
|
||||
type HistoryPolicy struct {
|
||||
Mode HistoryMode
|
||||
// Static prune point for PostMerge/PostPrague, nil otherwise.
|
||||
Target *PrunePoint
|
||||
}
|
||||
|
||||
// PrunePoints is an alias for MergePrunePoints for backward compatibility.
|
||||
// Deprecated: Use GetPrunePoint or MergePrunePoints directly.
|
||||
var PrunePoints = MergePrunePoints
|
||||
|
||||
// GetPrunePoint returns the prune point for the given genesis hash and history mode.
|
||||
// Returns nil if no prune point is defined for the given combination.
|
||||
func GetPrunePoint(genesisHash common.Hash, mode HistoryMode) *PrunePoint {
|
||||
// NewPolicy constructs a HistoryPolicy from the given mode and genesis hash.
|
||||
func NewPolicy(mode HistoryMode, genesisHash common.Hash) (HistoryPolicy, error) {
|
||||
switch mode {
|
||||
case KeepPostMerge:
|
||||
return MergePrunePoints[genesisHash]
|
||||
case KeepPostPrague:
|
||||
return PraguePrunePoints[genesisHash]
|
||||
case KeepAll:
|
||||
return HistoryPolicy{Mode: KeepAll}, nil
|
||||
|
||||
case KeepPostMerge, KeepPostPrague:
|
||||
point := staticPrunePoints[mode][genesisHash]
|
||||
if point == nil {
|
||||
return HistoryPolicy{}, fmt.Errorf("%s history pruning not available for network %s", mode, genesisHash.Hex())
|
||||
}
|
||||
return HistoryPolicy{Mode: mode, Target: point}, nil
|
||||
|
||||
default:
|
||||
return nil
|
||||
return HistoryPolicy{}, fmt.Errorf("invalid history mode: %d", mode)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
58
core/history/historymode_test.go
Normal file
58
core/history/historymode_test.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// 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/>.
|
||||
|
||||
package history
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
func TestNewPolicy(t *testing.T) {
|
||||
// KeepAll: no target.
|
||||
p, err := NewPolicy(KeepAll, params.MainnetGenesisHash)
|
||||
if err != nil {
|
||||
t.Fatalf("KeepAll: %v", err)
|
||||
}
|
||||
if p.Mode != KeepAll || p.Target != nil {
|
||||
t.Errorf("KeepAll: unexpected policy %+v", p)
|
||||
}
|
||||
|
||||
// PostMerge: resolves known mainnet prune point.
|
||||
p, err = NewPolicy(KeepPostMerge, params.MainnetGenesisHash)
|
||||
if err != nil {
|
||||
t.Fatalf("PostMerge: %v", err)
|
||||
}
|
||||
if p.Target == nil || p.Target.BlockNumber != 15537393 {
|
||||
t.Errorf("PostMerge: unexpected target %+v", p.Target)
|
||||
}
|
||||
|
||||
// PostPrague: resolves known mainnet prune point.
|
||||
p, err = NewPolicy(KeepPostPrague, params.MainnetGenesisHash)
|
||||
if err != nil {
|
||||
t.Fatalf("PostPrague: %v", err)
|
||||
}
|
||||
if p.Target == nil || p.Target.BlockNumber != 22431084 {
|
||||
t.Errorf("PostPrague: unexpected target %+v", p.Target)
|
||||
}
|
||||
|
||||
// PostMerge on unknown network: error.
|
||||
if _, err = NewPolicy(KeepPostMerge, common.HexToHash("0xdeadbeef")); err == nil {
|
||||
t.Fatal("PostMerge unknown network: expected error")
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"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/state/pruner"
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
|
|
@ -175,7 +176,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
|
||||
// Here we determine genesis hash and active ChainConfig.
|
||||
// We need these to figure out the consensus parameters and to set up history pruning.
|
||||
chainConfig, _, err := core.LoadChainConfig(chainDb, config.Genesis)
|
||||
chainConfig, genesisHash, err := core.LoadChainConfig(chainDb, config.Genesis)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -220,6 +221,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion)
|
||||
}
|
||||
}
|
||||
histPolicy, err := history.NewPolicy(config.HistoryMode, genesisHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
options = &core.BlockChainConfig{
|
||||
TrieCleanLimit: config.TrieCleanCache,
|
||||
|
|
@ -233,7 +238,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
TrienodeHistory: config.TrienodeHistory,
|
||||
NodeFullValueCheckpoint: config.NodeFullValueCheckpoint,
|
||||
StateScheme: scheme,
|
||||
ChainHistoryMode: config.HistoryMode,
|
||||
HistoryPolicy: histPolicy,
|
||||
TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)),
|
||||
VmConfig: vm.Config{
|
||||
EnablePreimageRecording: config.EnablePreimageRecording,
|
||||
|
|
|
|||
Loading…
Reference in a new issue