88{.push raises : [Defect ].}
99
1010import
11- std/ [options, sequtils, tables, sets],
12- stew/ [assign2, byteutils, results],
11+ std/ [algorithm, options, sequtils, tables, sets],
12+ stew/ [assign2, bitops2, byteutils, objects , results],
1313 metrics, snappy, chronicles,
1414 ../ spec/ [beaconstate, eth2_merkleization, eth2_ssz_serialization, helpers,
1515 state_transition, validator],
16- ../ spec/ datatypes/ [phase0, altair],
16+ ../ spec/ datatypes/ [phase0, altair, bellatrix ],
1717 " .." / beacon_chain_db,
1818 " ." / [block_pools_types, block_quarantine]
1919
5353 EPOCHS_PER_STATE_SNAPSHOT = 32
5454
5555proc putBlock * (
56- dag: ChainDAGRef , signedBlock: ForkyTrustedSignedBeaconBlock ) =
56+ dag: ChainDAGRef ,
57+ signedBlock: ForkyTrustedSignedBeaconBlock ) =
5758 dag.db.putBlock (signedBlock)
5859
5960proc updateStateData * (
@@ -401,11 +402,24 @@ proc getForkedBlock*(
401402 dag.getForkedBlock (blck.bid).expect (
402403 " BlockRef block should always load, database corrupt?" )
403404
405+ import blockchain_dag_light_client
406+
407+ export
408+ blockchain_dag_light_client.getBestLightClientUpdateForPeriod,
409+ blockchain_dag_light_client.getLatestLightClientUpdate,
410+ blockchain_dag_light_client.getOptimisticLightClientUpdate,
411+ blockchain_dag_light_client.getLightClientBootstrap
412+
404413proc init * (T: type ChainDAGRef , cfg: RuntimeConfig , db: BeaconChainDB ,
405414 validatorMonitor: ref ValidatorMonitor , updateFlags: UpdateFlags ,
406415 onBlockCb: OnBlockCallback = nil , onHeadCb: OnHeadCallback = nil ,
407416 onReorgCb: OnReorgCallback = nil ,
408- onFinCb: OnFinalizedCallback = nil ): ChainDAGRef =
417+ onFinCb: OnFinalizedCallback = nil ,
418+ onOptimisticLCUpdateCb: OnOptimisticLightClientUpdateCallback = nil ,
419+ createLightClientData = false ): ChainDAGRef =
420+ if onOptimisticLCUpdateCb != nil :
421+ doAssert createLightClientData
422+
409423 # TODO we require that the db contains both a head and a tail block -
410424 # asserting here doesn't seem like the right way to go about it however..
411425
@@ -543,6 +557,7 @@ proc init*(T: type ChainDAGRef, cfg: RuntimeConfig, db: BeaconChainDB,
543557 # allow skipping some validation.
544558 updateFlags: {verifyFinalization} * updateFlags,
545559 cfg: cfg,
560+ createLightClientData: createLightClientData,
546561
547562 forkDigests: newClone ForkDigests .init (
548563 cfg,
@@ -551,7 +566,8 @@ proc init*(T: type ChainDAGRef, cfg: RuntimeConfig, db: BeaconChainDB,
551566 onBlockAdded: onBlockCb,
552567 onHeadChanged: onHeadCb,
553568 onReorgHappened: onReorgCb,
554- onFinHappened: onFinCb
569+ onFinHappened: onFinCb,
570+ onOptimisticLightClientUpdate: onOptimisticLCUpdateCb
555571 )
556572
557573 let forkVersions =
@@ -618,6 +634,84 @@ proc init*(T: type ChainDAGRef, cfg: RuntimeConfig, db: BeaconChainDB,
618634 forkBlocks = dag.forkBlocks.len (),
619635 backfill = (dag.backfill.slot, shortLog (dag.backfill.parent_root))
620636
637+ # Initialize cached light client data (finalized head + non-finalized blocks).
638+ if dag.createLightClientData:
639+ let altairStartSlot = dag.cfg.ALTAIR_FORK_EPOCH .start_slot
640+ if headRef.slot >= altairStartSlot:
641+ let
642+ finalizedSlot = dag.finalizedHead.blck.slot
643+ finalizedPeriod = finalizedSlot.sync_committee_period
644+ dag.initializeBestLightClientUpdateForPeriod (
645+ finalizedPeriod, prune = false )
646+
647+ debug " Initializing cached light client data"
648+ let lightClientStartTick = Moment .now ()
649+
650+ # Build lists of block to process.
651+ # As it is slow to load states in descending order,
652+ # first build a todo list, then process them in ascending order.
653+ let earliestSlot = max (finalizedSlot, altairStartSlot)
654+ var
655+ blocksBetween = newSeqOfCap [BlockRef ](headRef.slot - earliestSlot + 1 )
656+ blockRef = headRef
657+ while blockRef.slot > earliestSlot:
658+ blocksBetween.add blockRef
659+ blockRef = blockRef.parent
660+ blocksBetween.add blockRef
661+
662+ # Process blocks.
663+ let lowSlot = max (altairStartSlot, dag.tail.slot)
664+ var
665+ oldCheckpoint: Checkpoint
666+ checkpointIndex = 0
667+ for i in countdown (blocksBetween.high, blocksBetween.low):
668+ blockRef = blocksBetween[i]
669+ dag.withUpdatedState (dag.headState, blockRef.atSlot (blockRef.slot)) do :
670+ withStateAndBlck (stateData.data, dag.getForkedBlock (blck)):
671+ when stateFork >= BeaconStateFork .Altair :
672+ # Cache data for `LightClientUpdate` of descendant blocks.
673+ dag.cacheLightClientData (state, blck, isNew = false )
674+
675+ # Cache data for the block's `finalized_checkpoint`.
676+ # The `finalized_checkpoint` may refer to:
677+ # 1. `finalizedHead.blck -> finalized_checkpoint`
678+ # This may happen when there were skipped slots.
679+ # 2. `finalizedHead -> finalized_checkpoint`
680+ # 3. One epoch boundary that got justified then finalized
681+ # between `finalizedHead -> finalized_checkpoint`
682+ # and `finalizedHead`
683+ # 4. `finalizedHead`
684+ let checkpoint = state.data.finalized_checkpoint
685+ if checkpoint != oldCheckpoint:
686+ oldCheckpoint = checkpoint
687+ doAssert checkpointIndex < dag.lightClientCheckpoints.len
688+ dag.lightClientCheckpoints[checkpointIndex] = checkpoint
689+ dag.lastLightClientCheckpointIndex = checkpointIndex
690+ inc checkpointIndex
691+ if checkpoint.root != dag.finalizedHead.blck.root:
692+ let cpRef =
693+ dag.getBlockAtSlot (checkpoint.epoch.start_slot).blck
694+ if cpRef != nil and cpRef.slot >= lowSlot:
695+ assert cpRef.bid.root == checkpoint.root
696+ dag.withUpdatedState (tmpState[],
697+ cpRef.atSlot (cpRef.slot)) do :
698+ withStateAndBlck (
699+ stateData.data, dag.getForkedBlock (blck)):
700+ when stateFork >= BeaconStateFork .Altair :
701+ dag.cacheLightClientData (state, blck, isNew = false )
702+ else : raiseAssert " Unreachable"
703+ do : raiseAssert " Unreachable"
704+
705+ # Create `LightClientUpdate` for non-finalized blocks.
706+ if blockRef.slot > earliestSlot:
707+ dag.createLightClientUpdates (state, blck, blockRef.parent)
708+ else : raiseAssert " Unreachable"
709+ do : raiseAssert " Unreachable"
710+
711+ let lightClientEndTick = Moment .now ()
712+ debug " Initialized cached light client data" ,
713+ initDur = lightClientEndTick - lightClientStartTick
714+
621715 dag
622716
623717template genesisValidatorsRoot * (dag: ChainDAGRef ): Eth2Digest =
@@ -1127,6 +1221,9 @@ proc pruneBlocksDAG(dag: ChainDAGRef) =
11271221
11281222 var cur = head.atSlot ()
11291223 while not cur.blck.isAncestorOf (dag.finalizedHead.blck):
1224+ if dag.createLightClientData:
1225+ dag.deleteLightClientData (cur.blck.bid)
1226+
11301227 dag.delState (cur) # TODO : should we move that disk I/O to `onSlotEnd`
11311228
11321229 if cur.isProposed ():
@@ -1447,6 +1544,8 @@ proc updateHead*(
14471544 # in order to clear out blocks that are no longer viable and should
14481545 # therefore no longer be considered as part of the chain we're following
14491546 dag.pruneBlocksDAG ()
1547+ if dag.createLightClientData:
1548+ dag.pruneLightClientData ()
14501549
14511550 # Send notification about new finalization point via callback.
14521551 if not (isNil (dag.onFinHappened)):
0 commit comments