Skip to content

Commit d021157

Browse files
committed
core, les, light: implement timestamp based sethead and genesis rewinds
1 parent 0848102 commit d021157

File tree

4 files changed

+74
-21
lines changed

4 files changed

+74
-21
lines changed

core/blockchain.go

+38-14
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
318318
if diskRoot != (common.Hash{}) {
319319
log.Warn("Head state missing, repairing", "number", head.Number(), "hash", head.Hash(), "snaproot", diskRoot)
320320

321-
snapDisk, err := bc.setHeadBeyondRoot(head.NumberU64(), diskRoot, true)
321+
snapDisk, err := bc.setHeadBeyondRoot(head.NumberU64(), 0, diskRoot, true)
322322
if err != nil {
323323
return nil, err
324324
}
@@ -328,7 +328,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
328328
}
329329
} else {
330330
log.Warn("Head state missing, repairing", "number", head.Number(), "hash", head.Hash())
331-
if _, err := bc.setHeadBeyondRoot(head.NumberU64(), common.Hash{}, true); err != nil {
331+
if _, err := bc.setHeadBeyondRoot(head.NumberU64(), 0, common.Hash{}, true); err != nil {
332332
return nil, err
333333
}
334334
}
@@ -428,7 +428,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
428428
if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
429429
log.Warn("Rewinding chain to upgrade configuration", "err", compat)
430430
if compat.RewindToTime > 0 {
431-
log.Crit("Timestamp based rewinds not implemented yet /sad")
431+
bc.SetHeadWithTimestamp(compat.RewindToTime)
432432
} else {
433433
bc.SetHead(compat.RewindToBlock)
434434
}
@@ -536,7 +536,20 @@ func (bc *BlockChain) loadLastState() error {
536536
// was fast synced or full synced and in which state, the method will try to
537537
// delete minimal data from disk whilst retaining chain consistency.
538538
func (bc *BlockChain) SetHead(head uint64) error {
539-
if _, err := bc.setHeadBeyondRoot(head, common.Hash{}, false); err != nil {
539+
if _, err := bc.setHeadBeyondRoot(head, 0, common.Hash{}, false); err != nil {
540+
return err
541+
}
542+
// Send chain head event to update the transaction pool
543+
bc.chainHeadFeed.Send(ChainHeadEvent{Block: bc.CurrentBlock()})
544+
return nil
545+
}
546+
547+
// SetHeadWithTimestamp rewinds the local chain to a new head that has at max
548+
// the given timestamp. Depending on whether the node was fast synced or full
549+
// synced and in which state, the method will try to delete minimal data from
550+
// disk whilst retaining chain consistency.
551+
func (bc *BlockChain) SetHeadWithTimestamp(timestamp uint64) error {
552+
if _, err := bc.setHeadBeyondRoot(0, timestamp, common.Hash{}, false); err != nil {
540553
return err
541554
}
542555
// Send chain head event to update the transaction pool
@@ -573,8 +586,12 @@ func (bc *BlockChain) SetSafe(block *types.Block) {
573586
// in which state, the method will try to delete minimal data from disk whilst
574587
// retaining chain consistency.
575588
//
589+
// The method also works in timestamp mode if `head == 0` but `time != 0`. In that
590+
// case blocks are rolled back until the new head becomes older or equal to the
591+
// requested time. If both `head` and `time` is 0, the chain is rewound to genesis.
592+
//
576593
// The method returns the block number where the requested root cap was found.
577-
func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bool) (uint64, error) {
594+
func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Hash, repair bool) (uint64, error) {
578595
if !bc.chainmu.TryLock() {
579596
return 0, errChainStopped
580597
}
@@ -588,7 +605,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
588605
pivot := rawdb.ReadLastPivotNumber(bc.db)
589606
frozen, _ := bc.db.Ancients()
590607

591-
updateFn := func(db ethdb.KeyValueWriter, header *types.Header) (uint64, bool) {
608+
updateFn := func(db ethdb.KeyValueWriter, header *types.Header) (*types.Header, bool) {
592609
// Rewind the blockchain, ensuring we don't end up with a stateless head
593610
// block. Note, depth equality is permitted to allow using SetHead as a
594611
// chain reparation mechanism without deleting any data!
@@ -669,16 +686,18 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
669686
bc.currentFastBlock.Store(newHeadFastBlock)
670687
headFastBlockGauge.Update(int64(newHeadFastBlock.NumberU64()))
671688
}
672-
head := bc.CurrentBlock().NumberU64()
673-
689+
var (
690+
headHeader = bc.CurrentBlock().Header()
691+
headNumber = headHeader.Number.Uint64()
692+
)
674693
// If setHead underflown the freezer threshold and the block processing
675694
// intent afterwards is full block importing, delete the chain segment
676695
// between the stateful-block and the sethead target.
677696
var wipe bool
678-
if head+1 < frozen {
679-
wipe = pivot == nil || head >= *pivot
697+
if headNumber+1 < frozen {
698+
wipe = pivot == nil || headNumber >= *pivot
680699
}
681-
return head, wipe // Only force wipe if full synced
700+
return headHeader, wipe // Only force wipe if full synced
682701
}
683702
// Rewind the header chain, deleting all block bodies until then
684703
delFn := func(db ethdb.KeyValueWriter, hash common.Hash, num uint64) {
@@ -705,13 +724,18 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
705724
// touching the header chain altogether, unless the freezer is broken
706725
if repair {
707726
if target, force := updateFn(bc.db, bc.CurrentBlock().Header()); force {
708-
bc.hc.SetHead(target, updateFn, delFn)
727+
bc.hc.SetHead(target.Number.Uint64(), updateFn, delFn)
709728
}
710729
} else {
711730
// Rewind the chain to the requested head and keep going backwards until a
712731
// block with a state is found or fast sync pivot is passed
713-
log.Warn("Rewinding blockchain", "target", head)
714-
bc.hc.SetHead(head, updateFn, delFn)
732+
if time > 0 {
733+
log.Warn("Rewinding blockchain to timestamp", "target", time)
734+
bc.hc.SetHeadWithTimestamp(time, updateFn, delFn)
735+
} else {
736+
log.Warn("Rewinding blockchain to block", "target", head)
737+
bc.hc.SetHead(head, updateFn, delFn)
738+
}
715739
}
716740
// Clear out any stale content from the caches
717741
bc.bodyCache.Purge()

core/headerchain.go

+24-6
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,7 @@ type (
556556
// before head header is updated. The method will return the actual block it
557557
// updated the head to (missing state) and a flag if setHead should continue
558558
// rewinding till that forcefully (exceeded ancient limits)
559-
UpdateHeadBlocksCallback func(ethdb.KeyValueWriter, *types.Header) (uint64, bool)
559+
UpdateHeadBlocksCallback func(ethdb.KeyValueWriter, *types.Header) (*types.Header, bool)
560560

561561
// DeleteBlockContentCallback is a callback function that is called by SetHead
562562
// before each header is deleted.
@@ -566,15 +566,33 @@ type (
566566
// SetHead rewinds the local chain to a new head. Everything above the new head
567567
// will be deleted and the new one set.
568568
func (hc *HeaderChain) SetHead(head uint64, updateFn UpdateHeadBlocksCallback, delFn DeleteBlockContentCallback) {
569+
hc.setHead(head, 0, updateFn, delFn)
570+
}
571+
572+
// SetHeadWithTimestamp rewinds the local chain to a new head timestamp. Everything
573+
// above the new head will be deleted and the new one set.
574+
func (hc *HeaderChain) SetHeadWithTimestamp(time uint64, updateFn UpdateHeadBlocksCallback, delFn DeleteBlockContentCallback) {
575+
hc.setHead(0, time, updateFn, delFn)
576+
}
577+
578+
// setHead rewinds the local chain to a new head block or a head timestamp.
579+
// Everything above the new head will be deleted and the new one set.
580+
func (hc *HeaderChain) setHead(headBlock uint64, headTime uint64, updateFn UpdateHeadBlocksCallback, delFn DeleteBlockContentCallback) {
569581
var (
570582
parentHash common.Hash
571583
batch = hc.chainDb.NewBatch()
572584
origin = true
573585
)
574-
for hdr := hc.CurrentHeader(); hdr != nil && hdr.Number.Uint64() > head; hdr = hc.CurrentHeader() {
586+
done := func(header *types.Header) bool {
587+
if headBlock != 0 || headTime == 0 {
588+
return header.Number.Uint64() <= headBlock
589+
}
590+
return header.Time <= headTime
591+
}
592+
for hdr := hc.CurrentHeader(); hdr != nil && !done(hdr); hdr = hc.CurrentHeader() {
575593
num := hdr.Number.Uint64()
576594

577-
// Rewind block chain to new head.
595+
// Rewind chain to new head
578596
parent := hc.GetHeader(hdr.ParentHash, num-1)
579597
if parent == nil {
580598
parent = hc.genesisHeader
@@ -591,9 +609,9 @@ func (hc *HeaderChain) SetHead(head uint64, updateFn UpdateHeadBlocksCallback, d
591609
markerBatch := hc.chainDb.NewBatch()
592610
if updateFn != nil {
593611
newHead, force := updateFn(markerBatch, parent)
594-
if force && newHead < head {
595-
log.Warn("Force rewinding till ancient limit", "head", newHead)
596-
head = newHead
612+
if force && ((headTime > 0 && newHead.Time < headTime) || (headTime == 0 && newHead.Number.Uint64() < headBlock)) {
613+
log.Warn("Force rewinding till ancient limit", "head", newHead.Number.Uint64())
614+
headBlock, headTime = newHead.Number.Uint64(), 0
597615
}
598616
}
599617
// Update head header then.

les/client.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) {
180180
if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
181181
log.Warn("Rewinding chain to upgrade configuration", "err", compat)
182182
if compat.RewindToTime > 0 {
183-
log.Crit("Timestamp based rewinds not implemented yet /sad")
183+
leth.blockchain.SetHeadWithTimestamp(compat.RewindToTime)
184184
} else {
185185
leth.blockchain.SetHead(compat.RewindToBlock)
186186
}

light/lightchain.go

+11
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,17 @@ func (lc *LightChain) SetHead(head uint64) error {
178178
return lc.loadLastState()
179179
}
180180

181+
// SetHeadWithTimestamp rewinds the local chain to a new head that has at max
182+
// the given timestamp. Everything above the new head will be deleted and the
183+
// new one set.
184+
func (lc *LightChain) SetHeadWithTimestamp(timestamp uint64) error {
185+
lc.chainmu.Lock()
186+
defer lc.chainmu.Unlock()
187+
188+
lc.hc.SetHeadWithTimestamp(timestamp, nil, nil)
189+
return lc.loadLastState()
190+
}
191+
181192
// GasLimit returns the gas limit of the current HEAD block.
182193
func (lc *LightChain) GasLimit() uint64 {
183194
return lc.hc.CurrentHeader().GasLimit

0 commit comments

Comments
 (0)