@@ -616,6 +616,172 @@ func (bc *BlockChain) SetSafe(header *types.Header) {
616616 }
617617}
618618
619+ // rewindPathHead implements the logic of rewindHead in the context of hash scheme.
620+ func (bc * BlockChain ) rewindHashHead (head * types.Header , root common.Hash ) (* types.Header , uint64 ) {
621+ var (
622+ limit uint64 // The oldest block that will be searched for this rewinding
623+ beyondRoot = root == common.Hash {} // Flag whether we're beyond the requested root (no root, always true)
624+ pivot = rawdb .ReadLastPivotNumber (bc .db ) // Associated block number of pivot point state
625+ rootNumber uint64 // Associated block number of requested root
626+
627+ start = time .Now () // Timestamp the rewinding is restarted
628+ logged = time .Now () // Timestamp last progress log was printed
629+ )
630+ // The oldest block to be searched is determined by the pivot block or a constant
631+ // searching threshold. The rationale behind this is as follows:
632+ //
633+ // - Snap sync is selected if the pivot block is available. The earliest available
634+ // state is the pivot block itself, so there is no sense in going further back.
635+ //
636+ // - Full sync is selected if the pivot block does not exist. The hash database
637+ // periodically flushes the state to disk, and the used searching threshold is
638+ // considered sufficient to find a persistent state, even for the testnet. It
639+ // might be not enough for a chain that is nearly empty. In the worst case,
640+ // the entire chain is reset to genesis, and snap sync is re-enabled on top,
641+ // which is still acceptable.
642+ if pivot != nil {
643+ limit = * pivot
644+ } else if head .Number .Uint64 () > params .FullImmutabilityThreshold {
645+ limit = head .Number .Uint64 () - params .FullImmutabilityThreshold
646+ }
647+ for {
648+ logger := log .Trace
649+ if time .Since (logged ) > time .Second * 8 {
650+ logged = time .Now ()
651+ logger = log .Info
652+ }
653+ logger ("Block state missing, rewinding further" , "number" , head .Number , "hash" , head .Hash (), "elapsed" , common .PrettyDuration (time .Since (start )))
654+
655+ // If a root threshold was requested but not yet crossed, check
656+ if ! beyondRoot && head .Root == root {
657+ beyondRoot , rootNumber = true , head .Number .Uint64 ()
658+ }
659+ // If search limit is reached, return the genesis block as the
660+ // new chain head.
661+ if head .Number .Uint64 () < limit {
662+ log .Info ("Rewinding limit reached, resetting to genesis" , "number" , head .Number , "hash" , head .Hash (), "limit" , limit )
663+ return bc .genesisBlock .Header (), rootNumber
664+ }
665+ // If the associated state is not reachable, continue searching
666+ // backwards until an available state is found.
667+ if ! bc .HasState (head .Root ) {
668+ // If the chain is gapped in the middle, return the genesis
669+ // block as the new chain head.
670+ parent := bc .GetHeader (head .ParentHash , head .Number .Uint64 ()- 1 )
671+ if parent == nil {
672+ log .Error ("Missing block in the middle, resetting to genesis" , "number" , head .Number .Uint64 ()- 1 , "hash" , head .ParentHash )
673+ return bc .genesisBlock .Header (), rootNumber
674+ }
675+ head = parent
676+
677+ // If the genesis block is reached, stop searching.
678+ if head .Number .Uint64 () == 0 {
679+ log .Info ("Genesis block reached" , "number" , head .Number , "hash" , head .Hash ())
680+ return head , rootNumber
681+ }
682+ continue // keep rewinding
683+ }
684+ // Once the available state is found, ensure that the requested root
685+ // has already been crossed. If not, continue rewinding.
686+ if beyondRoot || head .Number .Uint64 () == 0 {
687+ log .Info ("Rewound to block with state" , "number" , head .Number , "hash" , head .Hash ())
688+ return head , rootNumber
689+ }
690+ log .Debug ("Skipping block with threshold state" , "number" , head .Number , "hash" , head .Hash (), "root" , head .Root )
691+ head = bc .GetHeader (head .ParentHash , head .Number .Uint64 ()- 1 ) // Keep rewinding
692+ }
693+ }
694+
695+ // rewindPathHead implements the logic of rewindHead in the context of path scheme.
696+ func (bc * BlockChain ) rewindPathHead (head * types.Header , root common.Hash ) (* types.Header , uint64 ) {
697+ var (
698+ pivot = rawdb .ReadLastPivotNumber (bc .db ) // Associated block number of pivot block
699+ rootNumber uint64 // Associated block number of requested root
700+
701+ // BeyondRoot represents whether the requested root is already
702+ // crossed. The flag value is set to true if the root is empty.
703+ beyondRoot = root == common.Hash {}
704+
705+ // noState represents if the target state requested for search
706+ // is unavailable and impossible to be recovered.
707+ noState = ! bc .HasState (root ) && ! bc .stateRecoverable (root )
708+
709+ start = time .Now () // Timestamp the rewinding is restarted
710+ logged = time .Now () // Timestamp last progress log was printed
711+ )
712+ // Rewind the head block tag until an available state is found.
713+ for {
714+ logger := log .Trace
715+ if time .Since (logged ) > time .Second * 8 {
716+ logged = time .Now ()
717+ logger = log .Info
718+ }
719+ logger ("Block state missing, rewinding further" , "number" , head .Number , "hash" , head .Hash (), "elapsed" , common .PrettyDuration (time .Since (start )))
720+
721+ // If a root threshold was requested but not yet crossed, check
722+ if ! beyondRoot && head .Root == root {
723+ beyondRoot , rootNumber = true , head .Number .Uint64 ()
724+ }
725+ // If the root threshold hasn't been crossed but the available
726+ // state is reached, quickly determine if the target state is
727+ // possible to be reached or not.
728+ if ! beyondRoot && noState && bc .HasState (head .Root ) {
729+ beyondRoot = true
730+ log .Info ("Disable the search for unattainable state" , "root" , root )
731+ }
732+ // Check if the associated state is available or recoverable if
733+ // the requested root has already been crossed.
734+ if beyondRoot && (bc .HasState (head .Root ) || bc .stateRecoverable (head .Root )) {
735+ break
736+ }
737+ // If pivot block is reached, return the genesis block as the
738+ // new chain head. Theoretically there must be a persistent
739+ // state before or at the pivot block, prevent endless rewinding
740+ // towards the genesis just in case.
741+ if pivot != nil && * pivot >= head .Number .Uint64 () {
742+ log .Info ("Pivot block reached, resetting to genesis" , "number" , head .Number , "hash" , head .Hash ())
743+ return bc .genesisBlock .Header (), rootNumber
744+ }
745+ // If the chain is gapped in the middle, return the genesis
746+ // block as the new chain head
747+ parent := bc .GetHeader (head .ParentHash , head .Number .Uint64 ()- 1 ) // Keep rewinding
748+ if parent == nil {
749+ log .Error ("Missing block in the middle, resetting to genesis" , "number" , head .Number .Uint64 ()- 1 , "hash" , head .ParentHash )
750+ return bc .genesisBlock .Header (), rootNumber
751+ }
752+ head = parent
753+
754+ // If the genesis block is reached, stop searching.
755+ if head .Number .Uint64 () == 0 {
756+ log .Info ("Genesis block reached" , "number" , head .Number , "hash" , head .Hash ())
757+ return head , rootNumber
758+ }
759+ }
760+ // Recover if the target state if it's not available yet.
761+ if ! bc .HasState (head .Root ) {
762+ if err := bc .triedb .Recover (head .Root ); err != nil {
763+ log .Crit ("Failed to rollback state" , "err" , err )
764+ }
765+ }
766+ log .Info ("Rewound to block with state" , "number" , head .Number , "hash" , head .Hash ())
767+ return head , rootNumber
768+ }
769+
770+ // rewindHead searches the available states in the database and returns the associated
771+ // block as the new head block.
772+ //
773+ // If the given root is not empty, then the rewind should attempt to pass the specified
774+ // state root and return the associated block number as well. If the root, typically
775+ // representing the state corresponding to snapshot disk layer, is deemed impassable,
776+ // then block number zero is returned, indicating that snapshot recovery is disabled
777+ // and the whole snapshot should be auto-generated in case of head mismatch.
778+ func (bc * BlockChain ) rewindHead (head * types.Header , root common.Hash ) (* types.Header , uint64 ) {
779+ if bc .triedb .Scheme () == rawdb .PathScheme {
780+ return bc .rewindPathHead (head , root )
781+ }
782+ return bc .rewindHashHead (head , root )
783+ }
784+
619785// setHeadBeyondRoot rewinds the local chain to a new head with the extra condition
620786// that the rewind must pass the specified state root. This method is meant to be
621787// used when rewinding with snapshots enabled to ensure that we go back further than
@@ -634,79 +800,40 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
634800 }
635801 defer bc .chainmu .Unlock ()
636802
637- // Track the block number of the requested root hash
638- var rootNumber uint64 // (no root == always 0)
639-
640- // Retrieve the last pivot block to short circuit rollbacks beyond it and the
641- // current freezer limit to start nuking id underflown
642- pivot := rawdb .ReadLastPivotNumber (bc .db )
643- frozen , _ := bc .db .Ancients ()
803+ var (
804+ // Track the block number of the requested root hash
805+ rootNumber uint64 // (no root == always 0)
644806
807+ // Retrieve the last pivot block to short circuit rollbacks beyond it
808+ // and the current freezer limit to start nuking it's underflown.
809+ pivot = rawdb .ReadLastPivotNumber (bc .db )
810+ )
645811 updateFn := func (db ethdb.KeyValueWriter , header * types.Header ) (* types.Header , bool ) {
646812 // Rewind the blockchain, ensuring we don't end up with a stateless head
647813 // block. Note, depth equality is permitted to allow using SetHead as a
648814 // chain reparation mechanism without deleting any data!
649815 if currentBlock := bc .CurrentBlock (); currentBlock != nil && header .Number .Uint64 () <= currentBlock .Number .Uint64 () {
650- newHeadBlock := bc .GetBlock (header .Hash (), header .Number .Uint64 ())
651- if newHeadBlock == nil {
652- log .Error ("Gap in the chain, rewinding to genesis" , "number" , header .Number , "hash" , header .Hash ())
653- newHeadBlock = bc .genesisBlock
654- } else {
655- // Block exists. Keep rewinding until either we find one with state
656- // or until we exceed the optional threshold root hash
657- beyondRoot := (root == common.Hash {}) // Flag whether we're beyond the requested root (no root, always true)
658-
659- for {
660- // If a root threshold was requested but not yet crossed, check
661- if root != (common.Hash {}) && ! beyondRoot && newHeadBlock .Root () == root {
662- beyondRoot , rootNumber = true , newHeadBlock .NumberU64 ()
663- }
664- if ! bc .HasState (newHeadBlock .Root ()) && ! bc .stateRecoverable (newHeadBlock .Root ()) {
665- log .Trace ("Block state missing, rewinding further" , "number" , newHeadBlock .NumberU64 (), "hash" , newHeadBlock .Hash ())
666- if pivot == nil || newHeadBlock .NumberU64 () > * pivot {
667- parent := bc .GetBlock (newHeadBlock .ParentHash (), newHeadBlock .NumberU64 ()- 1 )
668- if parent != nil {
669- newHeadBlock = parent
670- continue
671- }
672- log .Error ("Missing block in the middle, aiming genesis" , "number" , newHeadBlock .NumberU64 ()- 1 , "hash" , newHeadBlock .ParentHash ())
673- newHeadBlock = bc .genesisBlock
674- } else {
675- log .Trace ("Rewind passed pivot, aiming genesis" , "number" , newHeadBlock .NumberU64 (), "hash" , newHeadBlock .Hash (), "pivot" , * pivot )
676- newHeadBlock = bc .genesisBlock
677- }
678- }
679- if beyondRoot || newHeadBlock .NumberU64 () == 0 {
680- if ! bc .HasState (newHeadBlock .Root ()) && bc .stateRecoverable (newHeadBlock .Root ()) {
681- // Rewind to a block with recoverable state. If the state is
682- // missing, run the state recovery here.
683- if err := bc .triedb .Recover (newHeadBlock .Root ()); err != nil {
684- log .Crit ("Failed to rollback state" , "err" , err ) // Shouldn't happen
685- }
686- log .Debug ("Rewound to block with state" , "number" , newHeadBlock .NumberU64 (), "hash" , newHeadBlock .Hash ())
687- }
688- break
689- }
690- log .Debug ("Skipping block with threshold state" , "number" , newHeadBlock .NumberU64 (), "hash" , newHeadBlock .Hash (), "root" , newHeadBlock .Root ())
691- newHeadBlock = bc .GetBlock (newHeadBlock .ParentHash (), newHeadBlock .NumberU64 ()- 1 ) // Keep rewinding
692- }
693- }
816+ var newHeadBlock * types.Header
817+ newHeadBlock , rootNumber = bc .rewindHead (header , root )
694818 rawdb .WriteHeadBlockHash (db , newHeadBlock .Hash ())
695819
696820 // Degrade the chain markers if they are explicitly reverted.
697821 // In theory we should update all in-memory markers in the
698822 // last step, however the direction of SetHead is from high
699823 // to low, so it's safe to update in-memory markers directly.
700- bc .currentBlock .Store (newHeadBlock . Header () )
701- headBlockGauge .Update (int64 (newHeadBlock .NumberU64 ()))
824+ bc .currentBlock .Store (newHeadBlock )
825+ headBlockGauge .Update (int64 (newHeadBlock .Number . Uint64 ()))
702826
703827 // The head state is missing, which is only possible in the path-based
704828 // scheme. This situation occurs when the chain head is rewound below
705829 // the pivot point. In this scenario, there is no possible recovery
706830 // approach except for rerunning a snap sync. Do nothing here until the
707831 // state syncer picks it up.
708- if ! bc .HasState (newHeadBlock .Root ()) {
709- log .Info ("Chain is stateless, wait state sync" , "number" , newHeadBlock .Number (), "hash" , newHeadBlock .Hash ())
832+ if ! bc .HasState (newHeadBlock .Root ) {
833+ if newHeadBlock .Number .Uint64 () != 0 {
834+ log .Crit ("Chain is stateless at a non-genesis block" )
835+ }
836+ log .Info ("Chain is stateless, wait state sync" , "number" , newHeadBlock .Number , "hash" , newHeadBlock .Hash ())
710837 }
711838 }
712839 // Rewind the snap block in a simpleton way to the target head
@@ -733,6 +860,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
733860 // intent afterwards is full block importing, delete the chain segment
734861 // between the stateful-block and the sethead target.
735862 var wipe bool
863+ frozen , _ := bc .db .Ancients ()
736864 if headNumber + 1 < frozen {
737865 wipe = pivot == nil || headNumber >= * pivot
738866 }
0 commit comments