allow `getBlockIdAtSlot` to answer queries from available states (#5869)
After checkpoint sync, historical block IDs cannot yet be queried. However, they are needed to compute dependent roots of `ShufflingRef`. To allow lookup, enable `getBlockIdAtSlot` to answer from compatible states in memory; as long as they descend from the finalized checkpoint and the requested slot is sufficiently recent, `block_roots` contains everything to recover `BlockSlotId` up to `SLOTS_PER_HISTORICAL_ROOT`. This is similar to how `attester_dependent_root` etc. are computed. This accelerates the first couple minutes of checkpoint sync on Mainnet, especially the time until finality advances past the synced checkpoint.
This commit is contained in:
parent
91cf50a5ad
commit
4266e16835
|
@ -814,6 +814,11 @@ OK: 2/2 Fail: 0/2 Skip: 0/2
|
||||||
+ Starting state without block OK
|
+ Starting state without block OK
|
||||||
```
|
```
|
||||||
OK: 1/1 Fail: 0/1 Skip: 0/1
|
OK: 1/1 Fail: 0/1 Skip: 0/1
|
||||||
|
## State history
|
||||||
|
```diff
|
||||||
|
+ getBlockIdAtSlot OK
|
||||||
|
```
|
||||||
|
OK: 1/1 Fail: 0/1 Skip: 0/1
|
||||||
## Sync committee pool
|
## Sync committee pool
|
||||||
```diff
|
```diff
|
||||||
+ Aggregating votes OK
|
+ Aggregating votes OK
|
||||||
|
@ -986,4 +991,4 @@ OK: 2/2 Fail: 0/2 Skip: 0/2
|
||||||
OK: 9/9 Fail: 0/9 Skip: 0/9
|
OK: 9/9 Fail: 0/9 Skip: 0/9
|
||||||
|
|
||||||
---TOTAL---
|
---TOTAL---
|
||||||
OK: 667/672 Fail: 0/672 Skip: 5/672
|
OK: 668/673 Fail: 0/673 Skip: 5/673
|
||||||
|
|
|
@ -173,6 +173,36 @@ func getBlockRef*(dag: ChainDAGRef, root: Eth2Digest): Opt[BlockRef] =
|
||||||
else:
|
else:
|
||||||
err()
|
err()
|
||||||
|
|
||||||
|
func getBlockIdAtSlot*(
|
||||||
|
state: ForkyHashedBeaconState, slot: Slot): Opt[BlockSlotId] =
|
||||||
|
## Use given state to attempt to find a historical `BlockSlotId`.
|
||||||
|
if slot > state.data.slot:
|
||||||
|
return Opt.none(BlockSlotId) # State does not know about requested slot
|
||||||
|
if state.data.slot > slot + SLOTS_PER_HISTORICAL_ROOT:
|
||||||
|
return Opt.none(BlockSlotId) # Cache has expired
|
||||||
|
|
||||||
|
var idx = slot mod SLOTS_PER_HISTORICAL_ROOT
|
||||||
|
let root =
|
||||||
|
if slot == state.data.slot:
|
||||||
|
state.latest_block_root
|
||||||
|
else:
|
||||||
|
state.data.block_roots[idx]
|
||||||
|
var bid = BlockId(slot: slot, root: root)
|
||||||
|
|
||||||
|
let availableSlots =
|
||||||
|
min(slot.uint64, slot + SLOTS_PER_HISTORICAL_ROOT - state.data.slot)
|
||||||
|
for i in 0 ..< availableSlots:
|
||||||
|
if idx == 0:
|
||||||
|
idx = SLOTS_PER_HISTORICAL_ROOT
|
||||||
|
dec idx
|
||||||
|
if state.data.block_roots[idx] != root:
|
||||||
|
return Opt.some BlockSlotId.init(bid, slot)
|
||||||
|
dec bid.slot
|
||||||
|
|
||||||
|
if bid.slot == GENESIS_SLOT:
|
||||||
|
return Opt.some BlockSlotId.init(bid, slot)
|
||||||
|
Opt.none(BlockSlotId) # Unknown if there are more empty slots before
|
||||||
|
|
||||||
func getBlockIdAtSlot*(dag: ChainDAGRef, slot: Slot): Opt[BlockSlotId] =
|
func getBlockIdAtSlot*(dag: ChainDAGRef, slot: Slot): Opt[BlockSlotId] =
|
||||||
## Retrieve the canonical block at the given slot, or the last block that
|
## Retrieve the canonical block at the given slot, or the last block that
|
||||||
## comes before - similar to atSlot, but without the linear scan - may hit
|
## comes before - similar to atSlot, but without the linear scan - may hit
|
||||||
|
@ -188,6 +218,24 @@ func getBlockIdAtSlot*(dag: ChainDAGRef, slot: Slot): Opt[BlockSlotId] =
|
||||||
# finalized head is still in memory
|
# finalized head is still in memory
|
||||||
return dag.finalizedHead.blck.atSlot(slot).toBlockSlotId()
|
return dag.finalizedHead.blck.atSlot(slot).toBlockSlotId()
|
||||||
|
|
||||||
|
# Load from memory, if the block ID is sufficiently recent.
|
||||||
|
# For checkpoint sync, this is the only available of historical block IDs
|
||||||
|
# until sufficient blocks have been backfilled.
|
||||||
|
template tryWithState(state: ForkedHashedBeaconState) =
|
||||||
|
block:
|
||||||
|
withState(state):
|
||||||
|
# State must be a descendent of the finalized chain to be viable
|
||||||
|
let finBsi = forkyState.getBlockIdAtSlot(dag.finalizedHead.slot)
|
||||||
|
if finBsi.isSome and # DAG finalized bid slot wrong if CP not @ epoch
|
||||||
|
finBsi.unsafeGet.bid.root == dag.finalizedHead.blck.bid.root:
|
||||||
|
let bsi = forkyState.getBlockIdAtSlot(slot)
|
||||||
|
if bsi.isSome:
|
||||||
|
return bsi
|
||||||
|
tryWithState dag.headState
|
||||||
|
tryWithState dag.epochRefState
|
||||||
|
tryWithState dag.clearanceState
|
||||||
|
|
||||||
|
# Fallback to database, this only works for backfilled blocks
|
||||||
let finlow = dag.db.finalizedBlocks.low.expect("at least tailRef written")
|
let finlow = dag.db.finalizedBlocks.low.expect("at least tailRef written")
|
||||||
if slot >= finlow:
|
if slot >= finlow:
|
||||||
var pos = slot
|
var pos = slot
|
||||||
|
|
|
@ -839,10 +839,11 @@ suite "Backfill":
|
||||||
dag.getBlockId(blocks[^2].root).isNone()
|
dag.getBlockId(blocks[^2].root).isNone()
|
||||||
|
|
||||||
dag.getBlockIdAtSlot(dag.tail.slot).get().bid == dag.tail
|
dag.getBlockIdAtSlot(dag.tail.slot).get().bid == dag.tail
|
||||||
dag.getBlockIdAtSlot(dag.tail.slot - 1).isNone()
|
dag.getBlockIdAtSlot(dag.tail.slot - 1).get().bid ==
|
||||||
|
blocks[^2].toBlockId() # recovered from tailState
|
||||||
|
|
||||||
dag.getBlockIdAtSlot(Slot(0)).isSome() # genesis stored in db
|
dag.getBlockIdAtSlot(Slot(0)).isSome() # genesis stored in db
|
||||||
dag.getBlockIdAtSlot(Slot(1)).isNone()
|
dag.getBlockIdAtSlot(Slot(1)).isSome() # recovered from tailState
|
||||||
|
|
||||||
# No EpochRef for pre-tail epochs
|
# No EpochRef for pre-tail epochs
|
||||||
dag.getEpochRef(dag.tail, dag.tail.slot.epoch - 1, true).isErr()
|
dag.getEpochRef(dag.tail, dag.tail.slot.epoch - 1, true).isErr()
|
||||||
|
@ -853,7 +854,7 @@ suite "Backfill":
|
||||||
|
|
||||||
# Should not get EpochRef for random block
|
# Should not get EpochRef for random block
|
||||||
dag.getEpochRef(
|
dag.getEpochRef(
|
||||||
BlockId(root: blocks[^2].root, slot: dag.tail.slot), # root/slot mismatch
|
BlockId(root: blocks[^2].root, slot: dag.tail.slot), # incorrect slot
|
||||||
dag.tail.slot.epoch, true).isErr()
|
dag.tail.slot.epoch, true).isErr()
|
||||||
|
|
||||||
dag.getEpochRef(dag.tail, dag.tail.slot.epoch + 1, true).isOk()
|
dag.getEpochRef(dag.tail, dag.tail.slot.epoch + 1, true).isOk()
|
||||||
|
@ -892,7 +893,8 @@ suite "Backfill":
|
||||||
dag.getBlockIdAtSlot(dag.tail.slot).get().bid == dag.tail
|
dag.getBlockIdAtSlot(dag.tail.slot).get().bid == dag.tail
|
||||||
dag.getBlockIdAtSlot(dag.tail.slot - 1).get() ==
|
dag.getBlockIdAtSlot(dag.tail.slot - 1).get() ==
|
||||||
blocks[^2].toBlockId().atSlot()
|
blocks[^2].toBlockId().atSlot()
|
||||||
dag.getBlockIdAtSlot(dag.tail.slot - 2).isNone
|
dag.getBlockIdAtSlot(dag.tail.slot - 2).get() ==
|
||||||
|
blocks[^3].toBlockId().atSlot() # recovered from tailState
|
||||||
|
|
||||||
dag.backfill == blocks[^2].phase0Data.message.toBeaconBlockSummary()
|
dag.backfill == blocks[^2].phase0Data.message.toBeaconBlockSummary()
|
||||||
|
|
||||||
|
@ -901,7 +903,8 @@ suite "Backfill":
|
||||||
|
|
||||||
dag.getBlockIdAtSlot(dag.tail.slot - 2).get() ==
|
dag.getBlockIdAtSlot(dag.tail.slot - 2).get() ==
|
||||||
blocks[^3].toBlockId().atSlot()
|
blocks[^3].toBlockId().atSlot()
|
||||||
dag.getBlockIdAtSlot(dag.tail.slot - 3).isNone
|
dag.getBlockIdAtSlot(dag.tail.slot - 3).get() ==
|
||||||
|
blocks[^4].toBlockId().atSlot() # recovered from tailState
|
||||||
|
|
||||||
for i in 3..<blocks.len:
|
for i in 3..<blocks.len:
|
||||||
check: dag.addBackfillBlock(blocks[blocks.len - i - 1].phase0Data).isOk()
|
check: dag.addBackfillBlock(blocks[blocks.len - i - 1].phase0Data).isOk()
|
||||||
|
@ -947,7 +950,8 @@ suite "Backfill":
|
||||||
|
|
||||||
dag2.getBlockIdAtSlot(dag.tail.slot - 1).get() ==
|
dag2.getBlockIdAtSlot(dag.tail.slot - 1).get() ==
|
||||||
blocks[^2].toBlockId().atSlot()
|
blocks[^2].toBlockId().atSlot()
|
||||||
dag2.getBlockIdAtSlot(dag.tail.slot - 2).isNone
|
dag2.getBlockIdAtSlot(dag.tail.slot - 2).get() ==
|
||||||
|
blocks[^3].toBlockId().atSlot() # recovered from tailState
|
||||||
dag2.backfill == blocks[^2].phase0Data.message.toBeaconBlockSummary()
|
dag2.backfill == blocks[^2].phase0Data.message.toBeaconBlockSummary()
|
||||||
|
|
||||||
test "Init without genesis / block":
|
test "Init without genesis / block":
|
||||||
|
@ -1039,8 +1043,8 @@ suite "Starting states":
|
||||||
dag.getBlockId(tailBlock.root).get() == dag.tail
|
dag.getBlockId(tailBlock.root).get() == dag.tail
|
||||||
dag.getBlockId(blocks[^2].root).isNone()
|
dag.getBlockId(blocks[^2].root).isNone()
|
||||||
|
|
||||||
dag.getBlockIdAtSlot(Slot(0)).isNone() # no genesis stored in db
|
dag.getBlockIdAtSlot(Slot(0)).isSome() # recovered from tailState
|
||||||
dag.getBlockIdAtSlot(Slot(1)).isNone()
|
dag.getBlockIdAtSlot(Slot(1)).isSome() # recovered from tailState
|
||||||
|
|
||||||
# Should get EpochRef for the tail however
|
# Should get EpochRef for the tail however
|
||||||
# dag.getEpochRef(dag.tail, dag.tail.slot.epoch, true).isOk()
|
# dag.getEpochRef(dag.tail, dag.tail.slot.epoch, true).isOk()
|
||||||
|
@ -1048,7 +1052,7 @@ suite "Starting states":
|
||||||
|
|
||||||
# Should not get EpochRef for random block
|
# Should not get EpochRef for random block
|
||||||
dag.getEpochRef(
|
dag.getEpochRef(
|
||||||
BlockId(root: blocks[^2].root, slot: dag.tail.slot), # root/slot mismatch
|
BlockId(root: blocks[^2].root, slot: dag.tail.slot), # incorrect slot
|
||||||
dag.tail.slot.epoch, true).isErr()
|
dag.tail.slot.epoch, true).isErr()
|
||||||
|
|
||||||
dag.getEpochRef(dag.tail, dag.tail.slot.epoch + 1, true).isOk()
|
dag.getEpochRef(dag.tail, dag.tail.slot.epoch + 1, true).isOk()
|
||||||
|
@ -1092,7 +1096,8 @@ suite "Starting states":
|
||||||
|
|
||||||
dag.getBlockIdAtSlot(dag.tail.slot - 2).get() ==
|
dag.getBlockIdAtSlot(dag.tail.slot - 2).get() ==
|
||||||
blocks[^3].toBlockId().atSlot()
|
blocks[^3].toBlockId().atSlot()
|
||||||
dag.getBlockIdAtSlot(dag.tail.slot - 3).isNone
|
dag.getBlockIdAtSlot(dag.tail.slot - 3).get() ==
|
||||||
|
blocks[^4].toBlockId().atSlot() # recovered from tailState
|
||||||
|
|
||||||
for i in 3..<blocks.len:
|
for i in 3..<blocks.len:
|
||||||
check: dag.addBackfillBlock(blocks[blocks.len - i - 1].phase0Data).isOk()
|
check: dag.addBackfillBlock(blocks[blocks.len - i - 1].phase0Data).isOk()
|
||||||
|
@ -1230,6 +1235,128 @@ suite "Pruning":
|
||||||
dag.tail.slot == Epoch(EPOCHS_PER_STATE_SNAPSHOT).start_slot - 1
|
dag.tail.slot == Epoch(EPOCHS_PER_STATE_SNAPSHOT).start_slot - 1
|
||||||
not db.containsBlock(blocks[1].root)
|
not db.containsBlock(blocks[1].root)
|
||||||
|
|
||||||
|
suite "State history":
|
||||||
|
test "getBlockIdAtSlot":
|
||||||
|
const numValidators = SLOTS_PER_EPOCH
|
||||||
|
let
|
||||||
|
cfg = defaultRuntimeConfig
|
||||||
|
validatorMonitor = newClone(ValidatorMonitor.init())
|
||||||
|
dag = ChainDAGRef.init(
|
||||||
|
cfg, makeTestDB(numValidators, cfg = cfg),
|
||||||
|
validatorMonitor, {})
|
||||||
|
quarantine = newClone(Quarantine.init())
|
||||||
|
rng = HmacDrbgContext.new()
|
||||||
|
taskpool = Taskpool.new()
|
||||||
|
var verifier = BatchVerifier.init(rng, taskpool)
|
||||||
|
|
||||||
|
var
|
||||||
|
cache: StateCache
|
||||||
|
info: ForkedEpochInfo
|
||||||
|
res: Result[void, cstring]
|
||||||
|
template state: untyped = dag.headState.phase0Data
|
||||||
|
|
||||||
|
let gen = get_initial_beacon_block(dag.headState).toBlockId()
|
||||||
|
check:
|
||||||
|
state.getBlockIdAtSlot(0.Slot) ==
|
||||||
|
Opt.some BlockSlotId.init(gen, 0.Slot)
|
||||||
|
state.getBlockIdAtSlot(1.Slot).isNone
|
||||||
|
|
||||||
|
# Miss 5 slots
|
||||||
|
res = process_slots(cfg, dag.headState, 5.Slot, cache, info, flags = {})
|
||||||
|
check res.isOk
|
||||||
|
for i in 0.Slot .. 5.Slot:
|
||||||
|
check state.getBlockIdAtSlot(i) ==
|
||||||
|
Opt.some BlockSlotId.init(gen, i.Slot)
|
||||||
|
check state.getBlockIdAtSlot(6.Slot).isNone
|
||||||
|
|
||||||
|
# Fill 5 slots
|
||||||
|
var bids: seq[BlockId]
|
||||||
|
for i in 0 ..< 5:
|
||||||
|
let blck = dag.headState.addTestBlock(cache, cfg = cfg)
|
||||||
|
bids.add blck.toBlockId()
|
||||||
|
let added = dag.addHeadBlock(verifier, blck.phase0Data, nilPhase0Callback)
|
||||||
|
check added.isOk()
|
||||||
|
dag.updateHead(added[], quarantine[], [])
|
||||||
|
for i in 0.Slot .. 5.Slot:
|
||||||
|
check state.getBlockIdAtSlot(i) ==
|
||||||
|
Opt.some BlockSlotId.init(gen, i)
|
||||||
|
for i in 6.Slot .. 10.Slot:
|
||||||
|
check state.getBlockIdAtSlot(i) ==
|
||||||
|
Opt.some BlockSlotId.init(bids[(i - 6).int], i)
|
||||||
|
check state.getBlockIdAtSlot(11.Slot).isNone
|
||||||
|
|
||||||
|
# Jump to SLOTS_PER_HISTORICAL_ROOT
|
||||||
|
let periodSlot = SLOTS_PER_HISTORICAL_ROOT.Slot
|
||||||
|
res = process_slots(cfg, dag.headState, periodSlot, cache, info, flags = {})
|
||||||
|
for i in 0.Slot .. 5.Slot:
|
||||||
|
check state.getBlockIdAtSlot(i) ==
|
||||||
|
Opt.some BlockSlotId.init(gen, i)
|
||||||
|
for i in 6.Slot .. 10.Slot:
|
||||||
|
check state.getBlockIdAtSlot(i) ==
|
||||||
|
Opt.some BlockSlotId.init(bids[(i - 6).int], i)
|
||||||
|
check:
|
||||||
|
state.getBlockIdAtSlot(11.Slot) ==
|
||||||
|
Opt.some BlockSlotId.init(bids[^1], 11.Slot)
|
||||||
|
state.getBlockIdAtSlot(periodSlot) ==
|
||||||
|
Opt.some BlockSlotId.init(bids[^1], periodSlot)
|
||||||
|
state.getBlockIdAtSlot(periodSlot + 1).isNone
|
||||||
|
|
||||||
|
# Create a block at periodSlot + 1
|
||||||
|
let
|
||||||
|
blck = dag.headState.addTestBlock(cache, cfg = cfg)
|
||||||
|
added = dag.addHeadBlock(verifier, blck.phase0Data, nilPhase0Callback)
|
||||||
|
check added.isOk()
|
||||||
|
dag.updateHead(added[], quarantine[], [])
|
||||||
|
for i in 0.Slot .. 5.Slot:
|
||||||
|
check state.getBlockIdAtSlot(i).isNone
|
||||||
|
for i in 6.Slot .. 10.Slot:
|
||||||
|
check state.getBlockIdAtSlot(i) ==
|
||||||
|
Opt.some BlockSlotId.init(bids[(i - 6).int], i)
|
||||||
|
check:
|
||||||
|
state.getBlockIdAtSlot(11.Slot) ==
|
||||||
|
Opt.some BlockSlotId.init(bids[^1], 11.Slot)
|
||||||
|
state.getBlockIdAtSlot(periodSlot) ==
|
||||||
|
Opt.some BlockSlotId.init(bids[^1], periodSlot)
|
||||||
|
state.getBlockIdAtSlot(periodSlot + 1) ==
|
||||||
|
Opt.some BlockSlotId.init(blck.toBlockId(), periodSlot + 1)
|
||||||
|
state.getBlockIdAtSlot(periodSlot + 2).isNone
|
||||||
|
|
||||||
|
# Go to periodSlot + 5
|
||||||
|
let plusFive = periodSlot + 5
|
||||||
|
res = process_slots(cfg, dag.headState, plusFive, cache, info, flags = {})
|
||||||
|
for i in 0.Slot .. 5.Slot:
|
||||||
|
check state.getBlockIdAtSlot(i).isNone
|
||||||
|
for i in 6.Slot .. 10.Slot:
|
||||||
|
check state.getBlockIdAtSlot(i) ==
|
||||||
|
Opt.some BlockSlotId.init(bids[(i - 6).int], i)
|
||||||
|
check:
|
||||||
|
state.getBlockIdAtSlot(11.Slot) ==
|
||||||
|
Opt.some BlockSlotId.init(bids[^1], 11.Slot)
|
||||||
|
state.getBlockIdAtSlot(periodSlot) ==
|
||||||
|
Opt.some BlockSlotId.init(bids[^1], periodSlot)
|
||||||
|
for i in periodSlot + 1 .. plusFive:
|
||||||
|
check state.getBlockIdAtSlot(i) ==
|
||||||
|
Opt.some BlockSlotId.init(blck.toBlockId(), i)
|
||||||
|
check state.getBlockIdAtSlot(plusFive + 1).isNone
|
||||||
|
|
||||||
|
# Go to periodSlot + 6
|
||||||
|
let plusSix = periodSlot + 6
|
||||||
|
res = process_slots(cfg, dag.headState, plusSix, cache, info, flags = {})
|
||||||
|
for i in 0.Slot .. 6.Slot:
|
||||||
|
check state.getBlockIdAtSlot(i).isNone
|
||||||
|
for i in 7.Slot .. 10.Slot:
|
||||||
|
check state.getBlockIdAtSlot(i) ==
|
||||||
|
Opt.some BlockSlotId.init(bids[(i - 6).int], i)
|
||||||
|
check:
|
||||||
|
state.getBlockIdAtSlot(11.Slot) ==
|
||||||
|
Opt.some BlockSlotId.init(bids[^1], 11.Slot)
|
||||||
|
state.getBlockIdAtSlot(periodSlot) ==
|
||||||
|
Opt.some BlockSlotId.init(bids[^1], periodSlot)
|
||||||
|
for i in periodSlot + 1 .. plusSix:
|
||||||
|
check state.getBlockIdAtSlot(i) ==
|
||||||
|
Opt.some BlockSlotId.init(blck.toBlockId(), i)
|
||||||
|
check state.getBlockIdAtSlot(plusSix + 1).isNone
|
||||||
|
|
||||||
suite "Ancestry":
|
suite "Ancestry":
|
||||||
test "ancestorSlot":
|
test "ancestorSlot":
|
||||||
const numValidators = SLOTS_PER_EPOCH
|
const numValidators = SLOTS_PER_EPOCH
|
||||||
|
|
Loading…
Reference in New Issue