Skip to content

Commit d15f2be

Browse files
authored
Merge pull request #159 from OffchainLabs/snapshot_rollback_configurable
limit unrolling when searching for a snapshot.
2 parents 2c80f37 + 6ec7c84 commit d15f2be

File tree

1 file changed

+43
-13
lines changed

1 file changed

+43
-13
lines changed

core/blockchain.go

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
524529
func (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

Comments
 (0)