@@ -616,6 +616,172 @@ func (bc *BlockChain) SetSafe(header *types.Header) {
616
616
}
617
617
}
618
618
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
+
619
785
// setHeadBeyondRoot rewinds the local chain to a new head with the extra condition
620
786
// that the rewind must pass the specified state root. This method is meant to be
621
787
// 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
634
800
}
635
801
defer bc .chainmu .Unlock ()
636
802
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)
644
806
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
+ )
645
811
updateFn := func (db ethdb.KeyValueWriter , header * types.Header ) (* types.Header , bool ) {
646
812
// Rewind the blockchain, ensuring we don't end up with a stateless head
647
813
// block. Note, depth equality is permitted to allow using SetHead as a
648
814
// chain reparation mechanism without deleting any data!
649
815
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 )
694
818
rawdb .WriteHeadBlockHash (db , newHeadBlock .Hash ())
695
819
696
820
// Degrade the chain markers if they are explicitly reverted.
697
821
// In theory we should update all in-memory markers in the
698
822
// last step, however the direction of SetHead is from high
699
823
// 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 ()))
702
826
703
827
// The head state is missing, which is only possible in the path-based
704
828
// scheme. This situation occurs when the chain head is rewound below
705
829
// the pivot point. In this scenario, there is no possible recovery
706
830
// approach except for rerunning a snap sync. Do nothing here until the
707
831
// 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 ())
710
837
}
711
838
}
712
839
// 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
733
860
// intent afterwards is full block importing, delete the chain segment
734
861
// between the stateful-block and the sethead target.
735
862
var wipe bool
863
+ frozen , _ := bc .db .Ancients ()
736
864
if headNumber + 1 < frozen {
737
865
wipe = pivot == nil || headNumber >= * pivot
738
866
}
0 commit comments