Skip to content

Commit

Permalink
core, les, light: implement timestamp based sethead and genesis rewinds
Browse files Browse the repository at this point in the history
  • Loading branch information
karalabe committed Jan 3, 2023
1 parent 0848102 commit d021157
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 21 deletions.
52 changes: 38 additions & 14 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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!
Expand Down Expand Up @@ -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) {
Expand All @@ -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()
Expand Down
30 changes: 24 additions & 6 deletions core/headerchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion les/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
11 changes: 11 additions & 0 deletions light/lightchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit d021157

Please sign in to comment.