Skip to content

Commit 5cc18c3

Browse files
authored
clean up clearanceState access (#7518)
* clean up `clearanceState` access There are several assumptions in the code about the clearance state that lead to fragility - resolve some of these by moving things around a bit * advance clearance state together with proposal fcU * pass state in `OnBlockAdded` to emphasize the lifetime of its validity * remove some code duplication between block_processor / sync_overseer (there's a lot more to do but it's scheduled for dedicated refactoring so do just the minimum) * move blob/column validation to its own proc shared between backfill/head blocks * avoid pointless block lookup from dag when processing block attestations * push sidecar style selection up the call chain
1 parent 685d47f commit 5cc18c3

18 files changed

+345
-487
lines changed

beacon_chain/consensus_object_pools/block_clearance.nim

Lines changed: 58 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,13 @@ proc addResolvedHeadBlock(
3434
trustedBlock: ForkyTrustedSignedBeaconBlock,
3535
optimisticStatus: OptimisticStatus,
3636
parent: BlockRef, cache: var StateCache,
37-
onBlockAdded: OnForkyBlockAdded,
37+
onBlockAdded: OnBlockAdded,
3838
stateDataDur, sigVerifyDur, stateVerifyDur: Duration
3939
): BlockRef =
4040
doAssert state.matches_block_slot(
4141
trustedBlock.root, trustedBlock.message.slot),
4242
"Given state must have the new block applied"
43+
const consensusFork = typeof(trustedBlock).kind
4344

4445
let
4546
blockRoot = trustedBlock.root
@@ -100,12 +101,15 @@ proc addResolvedHeadBlock(
100101
# Notify others of the new block before processing the quarantine, such that
101102
# notifications for parents happens before those of the children
102103
if onBlockAdded != nil:
103-
let unrealized = withState(state):
104+
let unrealized =
104105
when consensusFork >= ConsensusFork.Altair:
105-
forkyState.data.compute_unrealized_finality()
106+
state.forky(consensusFork).data.compute_unrealized_finality()
106107
else:
107-
forkyState.data.compute_unrealized_finality(cache)
108-
onBlockAdded(blockRef, trustedBlock, epochRef, unrealized)
108+
state.forky(consensusFork).data.compute_unrealized_finality(cache)
109+
onBlockAdded(
110+
blockRef, trustedBlock, state.forky(consensusFork).data, epochRef, unrealized
111+
)
112+
109113
if not(isNil(dag.onBlockAdded)):
110114
dag.onBlockAdded(ForkedTrustedSignedBeaconBlock.init(trustedBlock))
111115

@@ -135,29 +139,6 @@ proc checkStateTransition(
135139
else:
136140
ok()
137141

138-
proc advanceClearanceState*(dag: ChainDAGRef, nextSlot: Slot) =
139-
# When the chain is synced, the most likely block to be produced is the block
140-
# right after head - we can exploit this assumption and advance the state
141-
# to that slot before the block arrives, thus allowing us to do the expensive
142-
# epoch transition ahead of time.
143-
# Notably, we use the clearance state here because that's where the block will
144-
# first be seen - later, this state will be copied to the head state!
145-
let head = dag.head
146-
if dag.clearanceState.matches_block_slot(head.root, nextSlot):
147-
return
148-
149-
let startTick = Moment.now()
150-
var cache = StateCache()
151-
if dag.updateState(
152-
dag.clearanceState,
153-
BlockSlotId.init(head.bid, nextSlot),
154-
true,
155-
cache,
156-
dag.updateFlags,
157-
):
158-
debug "Prepared clearance state for next block",
159-
nextSlot, head, updateStateDur = Moment.now() - startTick
160-
161142
proc checkHeadBlock*(
162143
dag: ChainDAGRef, signedBlock: ForkySignedBeaconBlock):
163144
Result[BlockRef, VerifierError] =
@@ -228,7 +209,7 @@ proc checkHeadBlock*(
228209
proc addHeadBlockWithParent*(
229210
dag: ChainDAGRef, verifier: var BatchVerifier,
230211
signedBlock: ForkySignedBeaconBlock, parent: BlockRef,
231-
optimisticStatus: OptimisticStatus, onBlockAdded: OnForkyBlockAdded
212+
optimisticStatus: OptimisticStatus, onBlockAdded: OnBlockAdded
232213
): Result[BlockRef, VerifierError] =
233214
## Try adding a block to the chain, verifying first that it passes the state
234215
## transition function and contains correct cryptographic signature.
@@ -347,8 +328,7 @@ proc addBackfillBlock*(
347328
info "Invalid genesis block signature"
348329
return err(VerifierError.Invalid)
349330
else:
350-
let proposerKey = dag.validatorKey(blck.proposer_index)
351-
if proposerKey.isNone():
331+
let proposerKey = dag.validatorKey(blck.proposer_index).valueOr:
352332
# We've verified that the block root matches our expectations by following
353333
# the chain of parents all the way from checkpoint. If all those blocks
354334
# were valid, the proposer_index in this block must also be valid, and we
@@ -365,7 +345,7 @@ proc addBackfillBlock*(
365345
getStateField(dag.headState, genesis_validators_root),
366346
blck.slot,
367347
signedBlock.root,
368-
proposerKey.get(),
348+
proposerKey,
369349
signedBlock.signature):
370350
info "Block signature verification failed"
371351
return err(VerifierError.Invalid)
@@ -455,26 +435,6 @@ proc addBackfillBlock*(
455435

456436
ok()
457437

458-
template BlockAdded(kind: static ConsensusFork): untyped =
459-
when kind == ConsensusFork.Gloas:
460-
OnGloasBlockAdded
461-
elif kind == ConsensusFork.Fulu:
462-
OnFuluBlockAdded
463-
elif kind == ConsensusFork.Electra:
464-
OnElectraBlockAdded
465-
elif kind == ConsensusFork.Deneb:
466-
OnDenebBlockAdded
467-
elif kind == ConsensusFork.Capella:
468-
OnCapellaBlockAdded
469-
elif kind == ConsensusFork.Bellatrix:
470-
OnBellatrixBlockAdded
471-
elif kind == ConsensusFork.Altair:
472-
OnAltairBlockAdded
473-
elif kind == ConsensusFork.Phase0:
474-
OnPhase0BlockAdded
475-
else:
476-
static: raiseAssert "Unreachable"
477-
478438
proc verifyBlockProposer*(
479439
verifier: var BatchVerifier,
480440
fork: Fork,
@@ -494,73 +454,57 @@ proc verifyBlockProposer*(
494454

495455
proc addBackfillBlockData*(
496456
dag: ChainDAGRef,
457+
consensusFork: static ConsensusFork,
497458
bdata: BlockData,
498459
onStateUpdated: OnStateUpdated,
499-
onBlockAdded: OnForkedBlockAdded
460+
onBlockAdded: OnBlockAdded,
500461
): Result[void, VerifierError] =
501462
var cache = StateCache()
463+
template forkyBlck: untyped = bdata.blck.forky(consensusFork)
464+
let
465+
parent = checkHeadBlock(dag, forkyBlck).valueOr:
466+
if error == VerifierError.Duplicate:
467+
return ok()
468+
return err(error)
469+
startTick = Moment.now()
470+
clearanceBlock = BlockSlotId.init(parent.bid, forkyBlck.message.slot)
471+
updateFlags1 = dag.updateFlags
472+
# TODO (cheatfate): {skipLastStateRootCalculation} flag here could
473+
# improve performance by 100%, but this approach needs some
474+
# improvements, which is unclear.
475+
476+
if not updateState(dag, dag.clearanceState, clearanceBlock, true, cache,
477+
updateFlags1):
478+
error "Unable to load clearance state for parent block, " &
479+
"database corrupt?", clearanceBlock = shortLog(clearanceBlock)
480+
return err(VerifierError.MissingParent)
502481

503-
withBlck(bdata.blck):
504-
let
505-
parent = checkHeadBlock(dag, forkyBlck).valueOr:
506-
if error == VerifierError.Duplicate:
507-
return ok()
508-
return err(error)
509-
startTick = Moment.now()
510-
clearanceBlock = BlockSlotId.init(parent.bid, forkyBlck.message.slot)
511-
updateFlags1 = dag.updateFlags
512-
# TODO (cheatfate): {skipLastStateRootCalculation} flag here could
513-
# improve performance by 100%, but this approach needs some
514-
# improvements, which is unclear.
515-
516-
if not updateState(dag, dag.clearanceState, clearanceBlock, true, cache,
517-
updateFlags1):
518-
error "Unable to load clearance state for parent block, " &
519-
"database corrupt?", clearanceBlock = shortLog(clearanceBlock)
520-
return err(VerifierError.MissingParent)
482+
let proposerVerifyTick = Moment.now()
521483

522-
let proposerVerifyTick = Moment.now()
523-
524-
if not(isNil(onStateUpdated)):
525-
? onStateUpdated(forkyBlck.message.slot)
526-
527-
let
528-
stateDataTick = Moment.now()
529-
updateFlags2 =
530-
dag.updateFlags + {skipBlsValidation, skipStateRootValidation}
531-
532-
? checkStateTransition(dag, forkyBlck.asSigVerified(), cache, updateFlags2)
533-
534-
let stateVerifyTick = Moment.now()
535-
536-
if bdata.blob.isSome():
537-
for blob in bdata.blob.get():
538-
dag.db.putBlobSidecar(blob[])
539-
540-
type Trusted = typeof forkyBlck.asTrusted()
541-
542-
proc onBlockAddedHandler(
543-
blckRef: BlockRef,
544-
trustedBlock: Trusted,
545-
epochRef: EpochRef,
546-
unrealized: FinalityCheckpoints
547-
) {.gcsafe, raises: [].} =
548-
onBlockAdded(
549-
blckRef,
550-
ForkedTrustedSignedBeaconBlock.init(trustedBlock),
551-
epochRef,
552-
unrealized)
553-
554-
let blockHandler: BlockAdded(consensusFork) = onBlockAddedHandler
555-
556-
discard addResolvedHeadBlock(
557-
dag, dag.clearanceState,
558-
forkyBlck.asTrusted(),
559-
OptimisticStatus.notValidated,
560-
parent, cache,
561-
blockHandler,
562-
proposerVerifyTick - startTick,
563-
stateDataTick - proposerVerifyTick,
564-
stateVerifyTick - stateDataTick)
484+
if not(isNil(onStateUpdated)):
485+
? onStateUpdated(forkyBlck.message.slot)
486+
487+
let
488+
stateDataTick = Moment.now()
489+
updateFlags2 =
490+
dag.updateFlags + {skipBlsValidation, skipStateRootValidation}
491+
492+
? checkStateTransition(dag, forkyBlck.asSigVerified(), cache, updateFlags2)
493+
494+
let stateVerifyTick = Moment.now()
495+
496+
if bdata.blob.isSome():
497+
for blob in bdata.blob.get():
498+
dag.db.putBlobSidecar(blob[])
499+
500+
discard addResolvedHeadBlock(
501+
dag, dag.clearanceState,
502+
forkyBlck.asTrusted(),
503+
OptimisticStatus.notValidated,
504+
parent, cache,
505+
onBlockAdded,
506+
proposerVerifyTick - startTick,
507+
stateDataTick - proposerVerifyTick,
508+
stateVerifyTick - stateDataTick)
565509

566510
ok()

beacon_chain/consensus_object_pools/block_pools_types.nim

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -299,25 +299,9 @@ type
299299
blck*: ForkedSignedBeaconBlock
300300
blob*: Opt[BlobSidecars]
301301

302-
OnBlockAdded*[T: ForkyTrustedSignedBeaconBlock] = proc(
303-
blckRef: BlockRef, blck: T, epochRef: EpochRef,
304-
unrealized: FinalityCheckpoints) {.gcsafe, raises: [].}
305-
OnPhase0BlockAdded* = OnBlockAdded[phase0.TrustedSignedBeaconBlock]
306-
OnAltairBlockAdded* = OnBlockAdded[altair.TrustedSignedBeaconBlock]
307-
OnBellatrixBlockAdded* = OnBlockAdded[bellatrix.TrustedSignedBeaconBlock]
308-
OnCapellaBlockAdded* = OnBlockAdded[capella.TrustedSignedBeaconBlock]
309-
OnDenebBlockAdded* = OnBlockAdded[deneb.TrustedSignedBeaconBlock]
310-
OnElectraBlockAdded* = OnBlockAdded[electra.TrustedSignedBeaconBlock]
311-
OnFuluBlockAdded* = OnBlockAdded[fulu.TrustedSignedBeaconBlock]
312-
OnGloasBlockAdded* = OnBlockAdded[gloas.TrustedSignedBeaconBlock]
313-
314-
OnForkyBlockAdded* =
315-
OnPhase0BlockAdded | OnAltairBlockAdded | OnBellatrixBlockAdded |
316-
OnCapellaBlockAdded | OnDenebBlockAdded | OnElectraBlockAdded |
317-
OnFuluBlockAdded | OnGloasBlockAdded
318-
319-
OnForkedBlockAdded* = proc(
320-
blckRef: BlockRef, blck: ForkedTrustedSignedBeaconBlock, epochRef: EpochRef,
302+
OnBlockAdded*[consensusFork: static ConsensusFork] = proc(
303+
blckRef: BlockRef, blck: consensusFork.TrustedSignedBeaconBlock,
304+
state: consensusFork.BeaconState, epochRef: EpochRef,
321305
unrealized: FinalityCheckpoints) {.gcsafe, raises: [].}
322306

323307
OnStateUpdated* = proc(
@@ -356,26 +340,6 @@ type
356340
slot*: Slot
357341
block_root* {.serializedFieldName: "block".}: Eth2Digest
358342

359-
template OnBlockAddedCallback*(kind: static ConsensusFork): auto =
360-
when kind == ConsensusFork.Gloas:
361-
typedesc[OnGloasBlockAdded]
362-
elif kind == ConsensusFork.Fulu:
363-
typedesc[OnFuluBlockAdded]
364-
elif kind == ConsensusFork.Electra:
365-
typedesc[OnElectraBlockAdded]
366-
elif kind == ConsensusFork.Deneb:
367-
typedesc[OnDenebBlockAdded]
368-
elif kind == ConsensusFork.Capella:
369-
typedesc[OnCapellaBlockAdded]
370-
elif kind == ConsensusFork.Bellatrix:
371-
typedesc[OnBellatrixBlockAdded]
372-
elif kind == ConsensusFork.Altair:
373-
typedesc[OnAltairBlockAdded]
374-
elif kind == ConsensusFork.Phase0:
375-
typedesc[OnPhase0BlockAdded]
376-
else:
377-
static: raiseAssert "Unreachable"
378-
379343
func proposer_dependent_slot*(epochRef: EpochRef): Slot =
380344
epochRef.key.epoch.proposer_dependent_slot()
381345

beacon_chain/consensus_object_pools/blockchain_dag.nim

Lines changed: 3 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -990,53 +990,10 @@ proc applyBlock(
990990
updateFlags: UpdateFlags): Result[void, cstring] =
991991
loadStateCache(dag, cache, bid, getStateField(state, slot).epoch)
992992

993-
discard case dag.cfg.consensusForkAtEpoch(bid.slot.epoch)
994-
of ConsensusFork.Phase0:
995-
let data = getBlock(dag, bid, phase0.TrustedSignedBeaconBlock).valueOr:
993+
withConsensusFork(dag.cfg.consensusForkAtEpoch(bid.slot.epoch)):
994+
let data = getBlock(dag, bid, consensusFork.TrustedSignedBeaconBlock).valueOr:
996995
return err("Block load failed")
997-
? state_transition(
998-
dag.cfg, state, data, cache, info,
999-
updateFlags + {slotProcessed}, noRollback)
1000-
of ConsensusFork.Altair:
1001-
let data = getBlock(dag, bid, altair.TrustedSignedBeaconBlock).valueOr:
1002-
return err("Block load failed")
1003-
? state_transition(
1004-
dag.cfg, state, data, cache, info,
1005-
updateFlags + {slotProcessed}, noRollback)
1006-
of ConsensusFork.Bellatrix:
1007-
let data = getBlock(dag, bid, bellatrix.TrustedSignedBeaconBlock).valueOr:
1008-
return err("Block load failed")
1009-
? state_transition(
1010-
dag.cfg, state, data, cache, info,
1011-
updateFlags + {slotProcessed}, noRollback)
1012-
of ConsensusFork.Capella:
1013-
let data = getBlock(dag, bid, capella.TrustedSignedBeaconBlock).valueOr:
1014-
return err("Block load failed")
1015-
? state_transition(
1016-
dag.cfg, state, data, cache, info,
1017-
updateFlags + {slotProcessed}, noRollback)
1018-
of ConsensusFork.Deneb:
1019-
let data = getBlock(dag, bid, deneb.TrustedSignedBeaconBlock).valueOr:
1020-
return err("Block load failed")
1021-
? state_transition(
1022-
dag.cfg, state, data, cache, info,
1023-
updateFlags + {slotProcessed}, noRollback)
1024-
of ConsensusFork.Electra:
1025-
let data = getBlock(dag, bid, electra.TrustedSignedBeaconBlock).valueOr:
1026-
return err("Block load failed")
1027-
? state_transition(
1028-
dag.cfg, state, data, cache, info,
1029-
updateFlags + {slotProcessed}, noRollback)
1030-
of ConsensusFork.Fulu:
1031-
let data = getBlock(dag, bid, fulu.TrustedSignedBeaconBlock).valueOr:
1032-
return err("Block load failed")
1033-
? state_transition(
1034-
dag.cfg, state, data, cache, info,
1035-
updateFlags + {slotProcessed}, noRollback)
1036-
of ConsensusFork.Gloas:
1037-
let data = getBlock(dag, bid, gloas.TrustedSignedBeaconBlock).valueOr:
1038-
return err("Block load failed")
1039-
? state_transition(
996+
discard ? state_transition(
1040997
dag.cfg, state, data, cache, info,
1041998
updateFlags + {slotProcessed}, noRollback)
1042999

0 commit comments

Comments
 (0)