@@ -21,6 +21,7 @@ import (
2121 "errors"
2222 "fmt"
2323 "io"
24+ "math"
2425 "math/big"
2526 "runtime"
2627 "slices"
@@ -36,6 +37,7 @@ import (
3637 "github.com/ethereum/go-ethereum/common/prque"
3738 "github.com/ethereum/go-ethereum/consensus"
3839 "github.com/ethereum/go-ethereum/consensus/misc/eip4844"
40+ "github.com/ethereum/go-ethereum/core/history"
3941 "github.com/ethereum/go-ethereum/core/rawdb"
4042 "github.com/ethereum/go-ethereum/core/state"
4143 "github.com/ethereum/go-ethereum/core/state/snapshot"
@@ -99,6 +101,10 @@ var (
99101 errInvalidNewChain = errors .New ("invalid new chain" )
100102)
101103
104+ var (
105+ forkReadyInterval = 3 * time .Minute
106+ )
107+
102108const (
103109 bodyCacheLimit = 256
104110 blockCacheLimit = 256
@@ -158,8 +164,7 @@ type CacheConfig struct {
158164
159165 // This defines the cutoff block for history expiry.
160166 // Blocks before this number may be unavailable in the chain database.
161- HistoryPruningCutoffNumber uint64
162- HistoryPruningCutoffHash common.Hash
167+ ChainHistoryMode history.HistoryMode
163168}
164169
165170// triedbConfig derives the configures for trie database.
@@ -255,6 +260,7 @@ type BlockChain struct {
255260 currentSnapBlock atomic.Pointer [types.Header ] // Current head of snap-sync
256261 currentFinalBlock atomic.Pointer [types.Header ] // Latest (consensus) finalized block
257262 currentSafeBlock atomic.Pointer [types.Header ] // Latest (consensus) safe block
263+ historyPrunePoint atomic.Pointer [history.PrunePoint ]
258264
259265 bodyCache * lru.Cache [common.Hash , * types.Body ]
260266 bodyRLPCache * lru.Cache [common.Hash , rlp.RawValue ]
@@ -274,6 +280,8 @@ type BlockChain struct {
274280 processor Processor // Block transaction processor interface
275281 vmConfig vm.Config
276282 logger * tracing.Hooks
283+
284+ lastForkReadyAlert time.Time // Last time there was a fork readiness print out
277285}
278286
279287// NewBlockChain returns a fully initialised block chain using information
@@ -513,26 +521,46 @@ func (bc *BlockChain) loadLastState() error {
513521 log .Warn ("Empty database, resetting chain" )
514522 return bc .Reset ()
515523 }
516- // Make sure the entire head block is available
517- headBlock := bc .GetBlockByHash (head )
524+ headHeader := bc .GetHeaderByHash (head )
525+ if headHeader == nil {
526+ // Corrupt or empty database, init from scratch
527+ log .Warn ("Head header missing, resetting chain" , "hash" , head )
528+ return bc .Reset ()
529+ }
530+
531+ var headBlock * types.Block
532+ if cmp := headHeader .Number .Cmp (new (big.Int )); cmp == 1 {
533+ // Make sure the entire head block is available.
534+ headBlock = bc .GetBlockByHash (head )
535+ } else if cmp == 0 {
536+ // On a pruned node the block body might not be available. But a pruned
537+ // block should never be the head block. The only exception is when, as
538+ // a last resort, chain is reset to genesis.
539+ headBlock = bc .genesisBlock
540+ }
518541 if headBlock == nil {
519542 // Corrupt or empty database, init from scratch
520543 log .Warn ("Head block missing, resetting chain" , "hash" , head )
521544 return bc .Reset ()
522545 }
523546 // Everything seems to be fine, set as the head block
524- bc .currentBlock .Store (headBlock . Header () )
547+ bc .currentBlock .Store (headHeader )
525548 headBlockGauge .Update (int64 (headBlock .NumberU64 ()))
526549
527550 // Restore the last known head header
528- headHeader := headBlock .Header ()
529551 if head := rawdb .ReadHeadHeaderHash (bc .db ); head != (common.Hash {}) {
530552 if header := bc .GetHeaderByHash (head ); header != nil {
531553 headHeader = header
532554 }
533555 }
534556 bc .hc .SetCurrentHeader (headHeader )
535557
558+ // Initialize history pruning.
559+ latest := max (headBlock .NumberU64 (), headHeader .Number .Uint64 ())
560+ if err := bc .initializeHistoryPruning (latest ); err != nil {
561+ return err
562+ }
563+
536564 // Restore the last known head snap block
537565 bc .currentSnapBlock .Store (headBlock .Header ())
538566 headFastBlockGauge .Update (int64 (headBlock .NumberU64 ()))
@@ -555,6 +583,7 @@ func (bc *BlockChain) loadLastState() error {
555583 headSafeBlockGauge .Update (int64 (block .NumberU64 ()))
556584 }
557585 }
586+
558587 // Issue a status log for the user
559588 var (
560589 currentSnapBlock = bc .CurrentSnapBlock ()
@@ -573,9 +602,57 @@ func (bc *BlockChain) loadLastState() error {
573602 if pivot := rawdb .ReadLastPivotNumber (bc .db ); pivot != nil {
574603 log .Info ("Loaded last snap-sync pivot marker" , "number" , * pivot )
575604 }
605+ if pruning := bc .historyPrunePoint .Load (); pruning != nil {
606+ log .Info ("Chain history is pruned" , "earliest" , pruning .BlockNumber , "hash" , pruning .BlockHash )
607+ }
576608 return nil
577609}
578610
611+ // initializeHistoryPruning sets bc.historyPrunePoint.
612+ func (bc * BlockChain ) initializeHistoryPruning (latest uint64 ) error {
613+ freezerTail , _ := bc .db .Tail ()
614+
615+ switch bc .cacheConfig .ChainHistoryMode {
616+ case history .KeepAll :
617+ if freezerTail == 0 {
618+ return nil
619+ }
620+ // The database was pruned somehow, so we need to figure out if it's a known
621+ // configuration or an error.
622+ predefinedPoint := history .PrunePoints [bc .genesisBlock .Hash ()]
623+ if predefinedPoint == nil || freezerTail != predefinedPoint .BlockNumber {
624+ log .Error ("Chain history database is pruned with unknown configuration" , "tail" , freezerTail )
625+ return fmt .Errorf ("unexpected database tail" )
626+ }
627+ bc .historyPrunePoint .Store (predefinedPoint )
628+ return nil
629+
630+ case history .KeepPostMerge :
631+ if freezerTail == 0 && latest != 0 {
632+ // This is the case where a user is trying to run with --history.chain
633+ // postmerge directly on an existing DB. We could just trigger the pruning
634+ // here, but it'd be a bit dangerous since they may not have intended this
635+ // action to happen. So just tell them how to do it.
636+ log .Error (fmt .Sprintf ("Chain history mode is configured as %q, but database is not pruned." , bc .cacheConfig .ChainHistoryMode .String ()))
637+ log .Error (fmt .Sprintf ("Run 'geth prune-history' to prune pre-merge history." ))
638+ return fmt .Errorf ("history pruning requested via configuration" )
639+ }
640+ predefinedPoint := history .PrunePoints [bc .genesisBlock .Hash ()]
641+ if predefinedPoint == nil {
642+ log .Error ("Chain history pruning is not supported for this network" , "genesis" , bc .genesisBlock .Hash ())
643+ return fmt .Errorf ("history pruning requested for unknown network" )
644+ } else if freezerTail > 0 && freezerTail != predefinedPoint .BlockNumber {
645+ log .Error ("Chain history database is pruned to unknown block" , "tail" , freezerTail )
646+ return fmt .Errorf ("unexpected database tail" )
647+ }
648+ bc .historyPrunePoint .Store (predefinedPoint )
649+ return nil
650+
651+ default :
652+ return fmt .Errorf ("invalid history mode: %d" , bc .cacheConfig .ChainHistoryMode )
653+ }
654+ }
655+
579656// SetHead rewinds the local chain to a new head. Depending on whether the node
580657// was snap synced or full synced and in which state, the method will try to
581658// delete minimal data from disk whilst retaining chain consistency.
@@ -586,11 +663,15 @@ func (bc *BlockChain) SetHead(head uint64) error {
586663 // Send chain head event to update the transaction pool
587664 header := bc .CurrentBlock ()
588665 if block := bc .GetBlock (header .Hash (), header .Number .Uint64 ()); block == nil {
589- // This should never happen. In practice, previously currentBlock
590- // contained the entire block whereas now only a "marker", so there
591- // is an ever so slight chance for a race we should handle.
592- log .Error ("Current block not found in database" , "block" , header .Number , "hash" , header .Hash ())
593- return fmt .Errorf ("current block missing: #%d [%x..]" , header .Number , header .Hash ().Bytes ()[:4 ])
666+ // In a pruned node the genesis block will not exist in the freezer.
667+ // It should not happen that we set head to any other pruned block.
668+ if header .Number .Uint64 () > 0 {
669+ // This should never happen. In practice, previously currentBlock
670+ // contained the entire block whereas now only a "marker", so there
671+ // is an ever so slight chance for a race we should handle.
672+ log .Error ("Current block not found in database" , "block" , header .Number , "hash" , header .Hash ())
673+ return fmt .Errorf ("current block missing: #%d [%x..]" , header .Number , header .Hash ().Bytes ()[:4 ])
674+ }
594675 }
595676 bc .chainHeadFeed .Send (ChainHeadEvent {Header : header })
596677 return nil
@@ -607,11 +688,15 @@ func (bc *BlockChain) SetHeadWithTimestamp(timestamp uint64) error {
607688 // Send chain head event to update the transaction pool
608689 header := bc .CurrentBlock ()
609690 if block := bc .GetBlock (header .Hash (), header .Number .Uint64 ()); block == nil {
610- // This should never happen. In practice, previously currentBlock
611- // contained the entire block whereas now only a "marker", so there
612- // is an ever so slight chance for a race we should handle.
613- log .Error ("Current block not found in database" , "block" , header .Number , "hash" , header .Hash ())
614- return fmt .Errorf ("current block missing: #%d [%x..]" , header .Number , header .Hash ().Bytes ()[:4 ])
691+ // In a pruned node the genesis block will not exist in the freezer.
692+ // It should not happen that we set head to any other pruned block.
693+ if header .Number .Uint64 () > 0 {
694+ // This should never happen. In practice, previously currentBlock
695+ // contained the entire block whereas now only a "marker", so there
696+ // is an ever so slight chance for a race we should handle.
697+ log .Error ("Current block not found in database" , "block" , header .Number , "hash" , header .Hash ())
698+ return fmt .Errorf ("current block missing: #%d [%x..]" , header .Number , header .Hash ().Bytes ()[:4 ])
699+ }
615700 }
616701 bc .chainHeadFeed .Send (ChainHeadEvent {Header : header })
617702 return nil
@@ -1014,7 +1099,9 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {
10141099 bc .hc .SetCurrentHeader (bc .genesisBlock .Header ())
10151100 bc .currentSnapBlock .Store (bc .genesisBlock .Header ())
10161101 headFastBlockGauge .Update (int64 (bc .genesisBlock .NumberU64 ()))
1017- return nil
1102+
1103+ // Reset history pruning status.
1104+ return bc .initializeHistoryPruning (0 )
10181105}
10191106
10201107// Export writes the active chain to the given writer.
@@ -1804,6 +1891,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
18041891 trieDiffNodes , trieBufNodes , _ := bc .triedb .Size ()
18051892 stats .report (chain , it .index , snapDiffItems , snapBufItems , trieDiffNodes , trieBufNodes , setHead )
18061893
1894+ // Print confirmation that a future fork is scheduled, but not yet active.
1895+ bc .logForkReadiness (block )
1896+
18071897 if ! setHead {
18081898 // After merge we expect few side chains. Simply count
18091899 // all blocks the CL gives us for GC processing time
@@ -1837,6 +1927,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
18371927 "root" , block .Root ())
18381928 }
18391929 }
1930+
18401931 stats .ignored += it .remaining ()
18411932 return witness , it .index , err
18421933}
@@ -2434,6 +2525,23 @@ func (bc *BlockChain) reportBlock(block *types.Block, res *ProcessResult, err er
24342525 log .Error (summarizeBadBlock (block , receipts , bc .Config (), err ))
24352526}
24362527
2528+ // logForkReadiness will write a log when a future fork is scheduled, but not
2529+ // active. This is useful so operators know their client is ready for the fork.
2530+ func (bc * BlockChain ) logForkReadiness (block * types.Block ) {
2531+ c := bc .Config ()
2532+ current , last := c .LatestFork (block .Time ()), c .LatestFork (math .MaxUint64 )
2533+ t := c .Timestamp (last )
2534+ if t == nil {
2535+ return
2536+ }
2537+ at := time .Unix (int64 (* t ), 0 )
2538+ if current < last && time .Now ().After (bc .lastForkReadyAlert .Add (forkReadyInterval )) {
2539+ log .Info ("Ready for fork activation" , "fork" , last , "date" , at .Format (time .RFC822 ),
2540+ "remaining" , time .Until (at ).Round (time .Second ), "timestamp" , at .Unix ())
2541+ bc .lastForkReadyAlert = time .Now ()
2542+ }
2543+ }
2544+
24372545// summarizeBadBlock returns a string summarizing the bad block and other
24382546// relevant information.
24392547func summarizeBadBlock (block * types.Block , receipts []* types.Receipt , config * params.ChainConfig , err error ) string {
0 commit comments