@@ -132,6 +132,8 @@ type CacheConfig struct {
132132	SnapshotLimit        int            // Memory allowance (MB) to use for caching snapshot entries in memory 
133133	Preimages            bool           // Whether to store preimage of trie key to the disk 
134134
135+ 	SnapshotRestoreMaxGas  uint64  // Rollback up to this much gas to restore snapshot (otherwise snapshot recalculated from nothing) 
136+ 
135137	// Arbitrum: configure GC window 
136138	TriesInMemory  uint64         // Height difference before which a trie may not be garbage-collected 
137139	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
318320		if  diskRoot  !=  (common.Hash {}) {
319321			log .Warn ("Head state missing, repairing" , "number" , head .Number (), "hash" , head .Hash (), "snaproot" , diskRoot )
320322
321- 			snapDisk , err  :=  bc .setHeadBeyondRoot (head .NumberU64 (), diskRoot , true )
323+ 			snapDisk , diskRootFound ,  err  :=  bc .setHeadBeyondRoot (head .NumberU64 (), diskRoot , true ,  bc . cacheConfig . SnapshotRestoreMaxGas )
322324			if  err  !=  nil  {
323325				return  nil , err 
324326			}
325327			// Chain rewound, persist old snapshot number to indicate recovery procedure 
326- 			if  snapDisk   !=   0  {
328+ 			if  diskRootFound  {
327329				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 )
328333			}
329334		} else  {
330335			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  {
332337				return  nil , err 
333338			}
334339		}
@@ -522,7 +527,7 @@ func (bc *BlockChain) loadLastState() error {
522527// was fast synced or full synced and in which state, the method will try to 
523528// delete minimal data from disk whilst retaining chain consistency. 
524529func  (bc  * BlockChain ) SetHead (head  uint64 ) error  {
525- 	_ , err  :=  bc .setHeadBeyondRoot (head , common.Hash {}, false )
530+ 	_ , _ ,  err  :=  bc .setHeadBeyondRoot (head , common.Hash {}, false ,  0 )
526531	return  err 
527532}
528533
@@ -549,21 +554,24 @@ func (bc *BlockChain) SetSafe(block *types.Block) {
549554}
550555
551556// 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 
553560// used when rewinding with snapshots enabled to ensure that we go back further than 
554561// persistent disk layer. Depending on whether the node was fast synced or full, and 
555562// in which state, the method will try to delete minimal data from disk whilst 
556563// retaining chain consistency. 
557564// 
558565// 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 ) {
560567	if  ! bc .chainmu .TryLock () {
561- 		return  0 , errChainStopped 
568+ 		return  0 , false ,  errChainStopped 
562569	}
563570	defer  bc .chainmu .Unlock ()
564571
565572	// 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 
567575
568576	// Retrieve the last pivot block to short circuit rollbacks beyond it and the 
569577	// current freezer limit to start nuking id underflown 
@@ -583,12 +591,15 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
583591				// Block exists, keep rewinding until we find one with state, 
584592				// keeping rewinding until we exceed the optional threshold 
585593				// 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 )
587598
588599				for  {
589600					// 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 ()
592603					}
593604					if  _ , err  :=  state .New (newHeadBlock .Root (), bc .stateCache , bc .snaps ); err  !=  nil  {
594605						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
604615							log .Trace ("Rewind passed pivot, aiming genesis" , "number" , newHeadBlock .NumberU64 (), "hash" , newHeadBlock .Hash (), "pivot" , * pivot )
605616							newHeadBlock  =  bc .genesisBlock 
606617						}
618+ 					} else  if  lastFullBlock  ==  0  {
619+ 						lastFullBlock  =  newHeadBlock .NumberU64 ()
620+ 						lastFullBlockHash  =  newHeadBlock .Hash ()
607621					}
608- 					if  beyondRoot  ||  newHeadBlock .NumberU64 () <=  bc .genesisBlock .NumberU64 () {
622+ 
623+ 					if  rootFound  ||  newHeadBlock .NumberU64 () <=  bc .genesisBlock .NumberU64 () {
609624						if  newHeadBlock .NumberU64 () <=  bc .genesisBlock .NumberU64 () {
610625							// Recommit the genesis state into disk in case the rewinding destination 
611626							// 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
623638						log .Debug ("Rewound to block with state" , "number" , newHeadBlock .NumberU64 (), "hash" , newHeadBlock .Hash ())
624639						break 
625640					}
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+ 					}
626656					log .Debug ("Skipping block with threshold state" , "number" , newHeadBlock .NumberU64 (), "hash" , newHeadBlock .Hash (), "root" , newHeadBlock .Root ())
627657					newHeadBlock  =  bc .GetBlock (newHeadBlock .ParentHash (), newHeadBlock .NumberU64 ()- 1 ) // Keep rewinding 
628658				}
@@ -714,7 +744,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
714744		bc .SetFinalized (nil )
715745	}
716746
717- 	return  rootNumber , bc .loadLastState ()
747+ 	return  blockNumber ,  rootFound , bc .loadLastState ()
718748}
719749
720750// SnapSyncCommitHead sets the current head block to the one defined by the hash 
0 commit comments