@@ -132,6 +132,8 @@ type CacheConfig struct {
132
132
SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory
133
133
Preimages bool // Whether to store preimage of trie key to the disk
134
134
135
+ SnapshotRestoreMaxGas uint64 // Rollback up to this much gas to restore snapshot (otherwise snapshot recalculated from nothing)
136
+
135
137
// Arbitrum: configure GC window
136
138
TriesInMemory uint64 // Height difference before which a trie may not be garbage-collected
137
139
TrieRetention time.Duration // Time limit before which a trie may not be garbage-collected
@@ -318,17 +320,20 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
318
320
if diskRoot != (common.Hash {}) {
319
321
log .Warn ("Head state missing, repairing" , "number" , head .Number (), "hash" , head .Hash (), "snaproot" , diskRoot )
320
322
321
- snapDisk , err := bc .setHeadBeyondRoot (head .NumberU64 (), diskRoot , true )
323
+ snapDisk , diskRootFound , err := bc .setHeadBeyondRoot (head .NumberU64 (), diskRoot , true , bc . cacheConfig . SnapshotRestoreMaxGas )
322
324
if err != nil {
323
325
return nil , err
324
326
}
325
327
// Chain rewound, persist old snapshot number to indicate recovery procedure
326
- if snapDisk != 0 {
328
+ if diskRootFound {
327
329
rawdb .WriteSnapshotRecoveryNumber (bc .db , snapDisk )
330
+ } else {
331
+ log .Warn ("Snapshot root not found or too far back. Recreating snapshot from scratch." )
332
+ rawdb .DeleteSnapshotRecoveryNumber (bc .db )
328
333
}
329
334
} else {
330
335
log .Warn ("Head state missing, repairing" , "number" , head .Number (), "hash" , head .Hash ())
331
- if _ , err := bc .setHeadBeyondRoot (head .NumberU64 (), common.Hash {}, true ); err != nil {
336
+ if _ , _ , err := bc .setHeadBeyondRoot (head .NumberU64 (), common.Hash {}, true , 0 ); err != nil {
332
337
return nil , err
333
338
}
334
339
}
@@ -522,7 +527,7 @@ func (bc *BlockChain) loadLastState() error {
522
527
// was fast synced or full synced and in which state, the method will try to
523
528
// delete minimal data from disk whilst retaining chain consistency.
524
529
func (bc * BlockChain ) SetHead (head uint64 ) error {
525
- _ , err := bc .setHeadBeyondRoot (head , common.Hash {}, false )
530
+ _ , _ , err := bc .setHeadBeyondRoot (head , common.Hash {}, false , 0 )
526
531
return err
527
532
}
528
533
@@ -549,21 +554,24 @@ func (bc *BlockChain) SetSafe(block *types.Block) {
549
554
}
550
555
551
556
// setHeadBeyondRoot rewinds the local chain to a new head with the extra condition
552
- // that the rewind must pass the specified state root. This method is meant to be
557
+ // that the rewind must pass the specified state root. The extra condition is
558
+ // ignored if it causes rolling back more than rewindLimit Gas (0 meaning infinte).
559
+ // If the limit was hit, rewind to last block with state. This method is meant to be
553
560
// used when rewinding with snapshots enabled to ensure that we go back further than
554
561
// persistent disk layer. Depending on whether the node was fast synced or full, and
555
562
// in which state, the method will try to delete minimal data from disk whilst
556
563
// retaining chain consistency.
557
564
//
558
565
// The method returns the block number where the requested root cap was found.
559
- func (bc * BlockChain ) setHeadBeyondRoot (head uint64 , root common.Hash , repair bool ) (uint64 , error ) {
566
+ func (bc * BlockChain ) setHeadBeyondRoot (head uint64 , root common.Hash , repair bool , rewindLimit uint64 ) (uint64 , bool , error ) {
560
567
if ! bc .chainmu .TryLock () {
561
- return 0 , errChainStopped
568
+ return 0 , false , errChainStopped
562
569
}
563
570
defer bc .chainmu .Unlock ()
564
571
565
572
// Track the block number of the requested root hash
566
- var rootNumber uint64 // (no root == always 0)
573
+ var blockNumber uint64 // (no root == always 0)
574
+ var rootFound bool
567
575
568
576
// Retrieve the last pivot block to short circuit rollbacks beyond it and the
569
577
// current freezer limit to start nuking id underflown
@@ -583,12 +591,15 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
583
591
// Block exists, keep rewinding until we find one with state,
584
592
// keeping rewinding until we exceed the optional threshold
585
593
// root hash
586
- beyondRoot := (root == common.Hash {}) // Flag whether we're beyond the requested root (no root, always true)
594
+ rootFound = (root == common.Hash {}) // Flag whether we're beyond the requested root (no root, always true)
595
+ lastFullBlock := uint64 (0 )
596
+ lastFullBlockHash := common.Hash {}
597
+ gasRolledBack := uint64 (0 )
587
598
588
599
for {
589
600
// If a root threshold was requested but not yet crossed, check
590
- if root != (common.Hash {}) && ! beyondRoot && newHeadBlock .Root () == root {
591
- beyondRoot , rootNumber = true , newHeadBlock .NumberU64 ()
601
+ if root != (common.Hash {}) && ! rootFound && newHeadBlock .Root () == root {
602
+ rootFound , blockNumber = true , newHeadBlock .NumberU64 ()
592
603
}
593
604
if _ , err := state .New (newHeadBlock .Root (), bc .stateCache , bc .snaps ); err != nil {
594
605
log .Trace ("Block state missing, rewinding further" , "number" , newHeadBlock .NumberU64 (), "hash" , newHeadBlock .Hash ())
@@ -604,8 +615,12 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
604
615
log .Trace ("Rewind passed pivot, aiming genesis" , "number" , newHeadBlock .NumberU64 (), "hash" , newHeadBlock .Hash (), "pivot" , * pivot )
605
616
newHeadBlock = bc .genesisBlock
606
617
}
618
+ } else if lastFullBlock == 0 {
619
+ lastFullBlock = newHeadBlock .NumberU64 ()
620
+ lastFullBlockHash = newHeadBlock .Hash ()
607
621
}
608
- if beyondRoot || newHeadBlock .NumberU64 () <= bc .genesisBlock .NumberU64 () {
622
+
623
+ if rootFound || newHeadBlock .NumberU64 () <= bc .genesisBlock .NumberU64 () {
609
624
if newHeadBlock .NumberU64 () <= bc .genesisBlock .NumberU64 () {
610
625
// Recommit the genesis state into disk in case the rewinding destination
611
626
// is genesis block and the relevant state is gone. In the future this
@@ -623,6 +638,21 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
623
638
log .Debug ("Rewound to block with state" , "number" , newHeadBlock .NumberU64 (), "hash" , newHeadBlock .Hash ())
624
639
break
625
640
}
641
+ if lastFullBlock != 0 && rewindLimit > 0 {
642
+ gasUsedInBlock := newHeadBlock .GasUsed ()
643
+ if bc .chainConfig .IsArbitrum () {
644
+ receipts := bc .GetReceiptsByHash (newHeadBlock .Hash ())
645
+ for _ , receipt := range receipts {
646
+ gasUsedInBlock -= receipt .GasUsedForL1
647
+ }
648
+ }
649
+ gasRolledBack += gasUsedInBlock
650
+ if rewindLimit > 0 && gasRolledBack >= rewindLimit {
651
+ blockNumber = lastFullBlock
652
+ newHeadBlock = bc .GetBlock (lastFullBlockHash , lastFullBlock )
653
+ break
654
+ }
655
+ }
626
656
log .Debug ("Skipping block with threshold state" , "number" , newHeadBlock .NumberU64 (), "hash" , newHeadBlock .Hash (), "root" , newHeadBlock .Root ())
627
657
newHeadBlock = bc .GetBlock (newHeadBlock .ParentHash (), newHeadBlock .NumberU64 ()- 1 ) // Keep rewinding
628
658
}
@@ -714,7 +744,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
714
744
bc .SetFinalized (nil )
715
745
}
716
746
717
- return rootNumber , bc .loadLastState ()
747
+ return blockNumber , rootFound , bc .loadLastState ()
718
748
}
719
749
720
750
// SnapSyncCommitHead sets the current head block to the one defined by the hash
0 commit comments