diff --git a/core/blockchain.go b/core/blockchain.go index 62f435060..ebb985e9b 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -318,7 +318,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis if diskRoot != (common.Hash{}) { log.Warn("Head state missing, repairing", "number", head.Number(), "hash", head.Hash(), "snaproot", diskRoot) - snapDisk, err := bc.setHeadBeyondRoot(head.NumberU64(), diskRoot, true) + snapDisk, err := bc.setHeadBeyondRoot(head.NumberU64(), 0, diskRoot, true) if err != nil { return nil, err } @@ -328,7 +328,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis } } else { log.Warn("Head state missing, repairing", "number", head.Number(), "hash", head.Hash()) - if _, err := bc.setHeadBeyondRoot(head.NumberU64(), common.Hash{}, true); err != nil { + if _, err := bc.setHeadBeyondRoot(head.NumberU64(), 0, common.Hash{}, true); err != nil { return nil, err } } @@ -428,7 +428,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis if compat, ok := genesisErr.(*params.ConfigCompatError); ok { log.Warn("Rewinding chain to upgrade configuration", "err", compat) if compat.RewindToTime > 0 { - log.Crit("Timestamp based rewinds not implemented yet /sad") + bc.SetHeadWithTimestamp(compat.RewindToTime) } else { bc.SetHead(compat.RewindToBlock) } @@ -536,7 +536,20 @@ func (bc *BlockChain) loadLastState() error { // was fast synced or full synced and in which state, the method will try to // delete minimal data from disk whilst retaining chain consistency. func (bc *BlockChain) SetHead(head uint64) error { - if _, err := bc.setHeadBeyondRoot(head, common.Hash{}, false); err != nil { + if _, err := bc.setHeadBeyondRoot(head, 0, common.Hash{}, false); err != nil { + return err + } + // Send chain head event to update the transaction pool + bc.chainHeadFeed.Send(ChainHeadEvent{Block: bc.CurrentBlock()}) + return nil +} + +// SetHeadWithTimestamp rewinds the local chain to a new head that has at max +// the given timestamp. Depending on whether the node was fast synced or full +// synced and in which state, the method will try to delete minimal data from +// disk whilst retaining chain consistency. +func (bc *BlockChain) SetHeadWithTimestamp(timestamp uint64) error { + if _, err := bc.setHeadBeyondRoot(0, timestamp, common.Hash{}, false); err != nil { return err } // Send chain head event to update the transaction pool @@ -573,8 +586,12 @@ func (bc *BlockChain) SetSafe(block *types.Block) { // in which state, the method will try to delete minimal data from disk whilst // retaining chain consistency. // +// The method also works in timestamp mode if `head == 0` but `time != 0`. In that +// case blocks are rolled back until the new head becomes older or equal to the +// requested time. If both `head` and `time` is 0, the chain is rewound to genesis. +// // The method returns the block number where the requested root cap was found. -func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bool) (uint64, error) { +func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Hash, repair bool) (uint64, error) { if !bc.chainmu.TryLock() { return 0, errChainStopped } @@ -588,7 +605,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo pivot := rawdb.ReadLastPivotNumber(bc.db) frozen, _ := bc.db.Ancients() - updateFn := func(db ethdb.KeyValueWriter, header *types.Header) (uint64, bool) { + updateFn := func(db ethdb.KeyValueWriter, header *types.Header) (*types.Header, bool) { // Rewind the blockchain, ensuring we don't end up with a stateless head // block. Note, depth equality is permitted to allow using SetHead as a // chain reparation mechanism without deleting any data! @@ -669,16 +686,18 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo bc.currentFastBlock.Store(newHeadFastBlock) headFastBlockGauge.Update(int64(newHeadFastBlock.NumberU64())) } - head := bc.CurrentBlock().NumberU64() - + var ( + headHeader = bc.CurrentBlock().Header() + headNumber = headHeader.Number.Uint64() + ) // If setHead underflown the freezer threshold and the block processing // intent afterwards is full block importing, delete the chain segment // between the stateful-block and the sethead target. var wipe bool - if head+1 < frozen { - wipe = pivot == nil || head >= *pivot + if headNumber+1 < frozen { + wipe = pivot == nil || headNumber >= *pivot } - return head, wipe // Only force wipe if full synced + return headHeader, wipe // Only force wipe if full synced } // Rewind the header chain, deleting all block bodies until then 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 // touching the header chain altogether, unless the freezer is broken if repair { if target, force := updateFn(bc.db, bc.CurrentBlock().Header()); force { - bc.hc.SetHead(target, updateFn, delFn) + bc.hc.SetHead(target.Number.Uint64(), updateFn, delFn) } } else { // Rewind the chain to the requested head and keep going backwards until a // block with a state is found or fast sync pivot is passed - log.Warn("Rewinding blockchain", "target", head) - bc.hc.SetHead(head, updateFn, delFn) + if time > 0 { + log.Warn("Rewinding blockchain to timestamp", "target", time) + bc.hc.SetHeadWithTimestamp(time, updateFn, delFn) + } else { + log.Warn("Rewinding blockchain to block", "target", head) + bc.hc.SetHead(head, updateFn, delFn) + } } // Clear out any stale content from the caches bc.bodyCache.Purge() diff --git a/core/headerchain.go b/core/headerchain.go index 482b5f6fb..ba8f550a8 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -556,7 +556,7 @@ type ( // before head header is updated. The method will return the actual block it // updated the head to (missing state) and a flag if setHead should continue // rewinding till that forcefully (exceeded ancient limits) - UpdateHeadBlocksCallback func(ethdb.KeyValueWriter, *types.Header) (uint64, bool) + UpdateHeadBlocksCallback func(ethdb.KeyValueWriter, *types.Header) (*types.Header, bool) // DeleteBlockContentCallback is a callback function that is called by SetHead // before each header is deleted. @@ -566,15 +566,33 @@ type ( // SetHead rewinds the local chain to a new head. Everything above the new head // will be deleted and the new one set. func (hc *HeaderChain) SetHead(head uint64, updateFn UpdateHeadBlocksCallback, delFn DeleteBlockContentCallback) { + hc.setHead(head, 0, updateFn, delFn) +} + +// SetHeadWithTimestamp rewinds the local chain to a new head timestamp. Everything +// above the new head will be deleted and the new one set. +func (hc *HeaderChain) SetHeadWithTimestamp(time uint64, updateFn UpdateHeadBlocksCallback, delFn DeleteBlockContentCallback) { + hc.setHead(0, time, updateFn, delFn) +} + +// setHead rewinds the local chain to a new head block or a head timestamp. +// Everything above the new head will be deleted and the new one set. +func (hc *HeaderChain) setHead(headBlock uint64, headTime uint64, updateFn UpdateHeadBlocksCallback, delFn DeleteBlockContentCallback) { var ( parentHash common.Hash batch = hc.chainDb.NewBatch() origin = true ) - for hdr := hc.CurrentHeader(); hdr != nil && hdr.Number.Uint64() > head; hdr = hc.CurrentHeader() { + done := func(header *types.Header) bool { + if headBlock != 0 || headTime == 0 { + return header.Number.Uint64() <= headBlock + } + return header.Time <= headTime + } + for hdr := hc.CurrentHeader(); hdr != nil && !done(hdr); hdr = hc.CurrentHeader() { num := hdr.Number.Uint64() - // Rewind block chain to new head. + // Rewind chain to new head parent := hc.GetHeader(hdr.ParentHash, num-1) if parent == nil { parent = hc.genesisHeader @@ -591,9 +609,9 @@ func (hc *HeaderChain) SetHead(head uint64, updateFn UpdateHeadBlocksCallback, d markerBatch := hc.chainDb.NewBatch() if updateFn != nil { newHead, force := updateFn(markerBatch, parent) - if force && newHead < head { - log.Warn("Force rewinding till ancient limit", "head", newHead) - head = newHead + if force && ((headTime > 0 && newHead.Time < headTime) || (headTime == 0 && newHead.Number.Uint64() < headBlock)) { + log.Warn("Force rewinding till ancient limit", "head", newHead.Number.Uint64()) + headBlock, headTime = newHead.Number.Uint64(), 0 } } // Update head header then. diff --git a/les/client.go b/les/client.go index 9f5822e08..3c222f819 100644 --- a/les/client.go +++ b/les/client.go @@ -180,7 +180,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { if compat, ok := genesisErr.(*params.ConfigCompatError); ok { log.Warn("Rewinding chain to upgrade configuration", "err", compat) if compat.RewindToTime > 0 { - log.Crit("Timestamp based rewinds not implemented yet /sad") + leth.blockchain.SetHeadWithTimestamp(compat.RewindToTime) } else { leth.blockchain.SetHead(compat.RewindToBlock) } diff --git a/light/lightchain.go b/light/lightchain.go index 155f7b0c0..f42c904f5 100644 --- a/light/lightchain.go +++ b/light/lightchain.go @@ -178,6 +178,17 @@ func (lc *LightChain) SetHead(head uint64) error { return lc.loadLastState() } +// SetHeadWithTimestamp rewinds the local chain to a new head that has at max +// the given timestamp. Everything above the new head will be deleted and the +// new one set. +func (lc *LightChain) SetHeadWithTimestamp(timestamp uint64) error { + lc.chainmu.Lock() + defer lc.chainmu.Unlock() + + lc.hc.SetHeadWithTimestamp(timestamp, nil, nil) + return lc.loadLastState() +} + // GasLimit returns the gas limit of the current HEAD block. func (lc *LightChain) GasLimit() uint64 { return lc.hc.CurrentHeader().GasLimit