From 4266e16835db4977ee8606cc557d7bdc411ef163 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Fri, 9 Feb 2024 11:13:00 +0100 Subject: [PATCH] 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. --- AllTests-mainnet.md | 7 +- .../consensus_object_pools/blockchain_dag.nim | 48 ++++++ tests/test_blockchain_dag.nim | 149 ++++++++++++++++-- 3 files changed, 192 insertions(+), 12 deletions(-) diff --git a/AllTests-mainnet.md b/AllTests-mainnet.md index 4357e69d7..8a4f2f76c 100644 --- a/AllTests-mainnet.md +++ b/AllTests-mainnet.md @@ -814,6 +814,11 @@ OK: 2/2 Fail: 0/2 Skip: 0/2 + Starting state without block OK ``` 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 ```diff + 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 ---TOTAL--- -OK: 667/672 Fail: 0/672 Skip: 5/672 +OK: 668/673 Fail: 0/673 Skip: 5/673 diff --git a/beacon_chain/consensus_object_pools/blockchain_dag.nim b/beacon_chain/consensus_object_pools/blockchain_dag.nim index 8a92dab87..3a07b847c 100644 --- a/beacon_chain/consensus_object_pools/blockchain_dag.nim +++ b/beacon_chain/consensus_object_pools/blockchain_dag.nim @@ -173,6 +173,36 @@ func getBlockRef*(dag: ChainDAGRef, root: Eth2Digest): Opt[BlockRef] = else: 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] = ## 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 @@ -188,6 +218,24 @@ func getBlockIdAtSlot*(dag: ChainDAGRef, slot: Slot): Opt[BlockSlotId] = # finalized head is still in memory 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") if slot >= finlow: var pos = slot diff --git a/tests/test_blockchain_dag.nim b/tests/test_blockchain_dag.nim index dde5dab31..cf83228f1 100644 --- a/tests/test_blockchain_dag.nim +++ b/tests/test_blockchain_dag.nim @@ -839,10 +839,11 @@ suite "Backfill": dag.getBlockId(blocks[^2].root).isNone() 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(1)).isNone() + dag.getBlockIdAtSlot(Slot(0)).isSome() # genesis stored in db + dag.getBlockIdAtSlot(Slot(1)).isSome() # recovered from tailState # No EpochRef for pre-tail epochs dag.getEpochRef(dag.tail, dag.tail.slot.epoch - 1, true).isErr() @@ -853,7 +854,7 @@ suite "Backfill": # Should not get EpochRef for random block 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.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 - 1).get() == 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() @@ -901,7 +903,8 @@ suite "Backfill": dag.getBlockIdAtSlot(dag.tail.slot - 2).get() == 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..