mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-01-11 14:54:12 +00:00
introduce types for LC merkle proofs (#3808)
Merkle proofs tend to have long underlying type definitions, e.g., `array[log2trunc(NEXT_SYNC_COMMITTEE_INDEX), Eth2Digest]`. For the ones used in the LC sync protocol, dedicated types are introduced to improve readability. Furthermore, the `CachedLightClientBootstrap` wrapper that solely wrapped a merkle branch is eliminated.
This commit is contained in:
parent
fd2040ae04
commit
e8e9ce1aab
@ -37,20 +37,11 @@ type
|
||||
CachedLightClientData* = object
|
||||
## Cached data from historical non-finalized states to improve speed when
|
||||
## creating future `LightClientUpdate` and `LightClientBootstrap` instances.
|
||||
current_sync_committee_branch*:
|
||||
array[log2trunc(altair.CURRENT_SYNC_COMMITTEE_INDEX), Eth2Digest]
|
||||
next_sync_committee_branch*:
|
||||
array[log2trunc(altair.NEXT_SYNC_COMMITTEE_INDEX), Eth2Digest]
|
||||
current_sync_committee_branch*: altair.CurrentSyncCommitteeBranch
|
||||
next_sync_committee_branch*: altair.NextSyncCommitteeBranch
|
||||
|
||||
finalized_slot*: Slot
|
||||
finality_branch*:
|
||||
array[log2trunc(altair.FINALIZED_ROOT_INDEX), Eth2Digest]
|
||||
|
||||
CachedLightClientBootstrap* = object
|
||||
## Cached data from historical finalized epoch boundary blocks to improve
|
||||
## speed when creating future `LightClientBootstrap` instances.
|
||||
current_sync_committee_branch*:
|
||||
array[log2trunc(altair.CURRENT_SYNC_COMMITTEE_INDEX), Eth2Digest]
|
||||
finality_branch*: altair.FinalityBranch
|
||||
|
||||
LightClientDataCache* = object
|
||||
data*: Table[BlockId, CachedLightClientData]
|
||||
@ -58,19 +49,19 @@ type
|
||||
## Key is the block ID of which the post state was used to get the data.
|
||||
## Data stored for the finalized head block and all non-finalized blocks.
|
||||
|
||||
bootstrap*: Table[Slot, CachedLightClientBootstrap]
|
||||
currentBranches*: Table[Slot, altair.CurrentSyncCommitteeBranch]
|
||||
## Cached data for creating future `LightClientBootstrap` instances.
|
||||
## Key is the block slot of which the post state was used to get the data.
|
||||
## Data stored for all finalized epoch boundary blocks.
|
||||
|
||||
best*: Table[SyncCommitteePeriod, altair.LightClientUpdate]
|
||||
bestUpdates*: Table[SyncCommitteePeriod, altair.LightClientUpdate]
|
||||
## Stores the `LightClientUpdate` with the most `sync_committee_bits` per
|
||||
## `SyncCommitteePeriod`. Sync committee finality gives precedence.
|
||||
|
||||
pendingBest*:
|
||||
Table[(SyncCommitteePeriod, Eth2Digest), altair.LightClientUpdate]
|
||||
## Same as `best`, but for `SyncCommitteePeriod` with not yet finalized
|
||||
## `next_sync_committee`. Key is `(attested_period,
|
||||
## Same as `bestUpdates`, but for `SyncCommitteePeriod` with not yet
|
||||
## finalized `next_sync_committee`. Key is `(attested_period,
|
||||
## hash_tree_root(current_sync_committee | next_sync_committee)`.
|
||||
|
||||
latest*: altair.LightClientFinalityUpdate
|
||||
@ -85,7 +76,7 @@ type
|
||||
# Light client data
|
||||
|
||||
cache*: LightClientDataCache
|
||||
## Cached data to accelerate serving light client data
|
||||
## Cached data to accelerate creating light client data
|
||||
|
||||
# -----------------------------------
|
||||
# Config
|
||||
|
@ -137,13 +137,13 @@ func initLightClientDataStore*(
|
||||
onLightClientOptimisticUpdate: onLCOptimisticUpdateCb)
|
||||
|
||||
func targetLightClientTailSlot(dag: ChainDAGRef): Slot =
|
||||
## Earliest slot for which light client data is retained.
|
||||
let
|
||||
maxPeriods = dag.lcDataStore.maxPeriods
|
||||
headPeriod = dag.head.slot.sync_committee_period
|
||||
lowSlot = max(dag.tail.slot, dag.cfg.ALTAIR_FORK_EPOCH.start_slot)
|
||||
tail = max(headPeriod + 1, maxPeriods.SyncCommitteePeriod) - maxPeriods
|
||||
max(tail.start_slot, lowSlot)
|
||||
## Earliest slot for which light client data is retained.
|
||||
let
|
||||
maxPeriods = dag.lcDataStore.maxPeriods
|
||||
headPeriod = dag.head.slot.sync_committee_period
|
||||
lowSlot = max(dag.tail.slot, dag.cfg.ALTAIR_FORK_EPOCH.start_slot)
|
||||
tail = max(headPeriod + 1, maxPeriods.SyncCommitteePeriod) - maxPeriods
|
||||
max(tail.start_slot, lowSlot)
|
||||
|
||||
func handleUnexpectedLightClientError(dag: ChainDAGRef, buggedSlot: Slot) =
|
||||
## If there is an unexpected error, adjust `tailSlot` to keep track of the
|
||||
@ -188,17 +188,16 @@ proc initLightClientBootstrapForPeriod(
|
||||
bid = bsi.bid
|
||||
boundarySlot = bid.slot.nextEpochBoundarySlot
|
||||
if boundarySlot == nextBoundarySlot and bid.slot >= lowSlot and
|
||||
not dag.lcDataStore.cache.bootstrap.hasKey(bid.slot):
|
||||
not dag.lcDataStore.cache.currentBranches.hasKey(bid.slot):
|
||||
if not dag.updateExistingState(
|
||||
tmpState[], bid.atSlot, save = false, tmpCache):
|
||||
dag.handleUnexpectedLightClientError(bid.slot)
|
||||
continue
|
||||
var cachedBootstrap {.noinit.}: CachedLightClientBootstrap
|
||||
cachedBootstrap.current_sync_committee_branch = withState(tmpState[]):
|
||||
let branch = withState(tmpState[]):
|
||||
when stateFork >= BeaconStateFork.Altair:
|
||||
state.data.build_proof(altair.CURRENT_SYNC_COMMITTEE_INDEX).get
|
||||
else: raiseAssert "Unreachable"
|
||||
dag.lcDataStore.cache.bootstrap[bid.slot] = cachedBootstrap
|
||||
dag.lcDataStore.cache.currentBranches[bid.slot] = branch
|
||||
|
||||
proc initLightClientUpdateForPeriod(
|
||||
dag: ChainDAGRef, period: SyncCommitteePeriod) =
|
||||
@ -207,7 +206,7 @@ proc initLightClientUpdateForPeriod(
|
||||
## Non-finalized blocks are processed incrementally.
|
||||
if not dag.isNextSyncCommitteeFinalized(period):
|
||||
return
|
||||
if dag.lcDataStore.cache.best.hasKey(period):
|
||||
if dag.lcDataStore.cache.bestUpdates.hasKey(period):
|
||||
return
|
||||
|
||||
let startTick = Moment.now()
|
||||
@ -217,7 +216,7 @@ proc initLightClientUpdateForPeriod(
|
||||
# replicated on every `return`, and the log statement allocates another
|
||||
# copy of the arguments on the stack for each instantiation (~1 MB stack!)
|
||||
debug "Best historic LC update computed",
|
||||
period, update = dag.lcDataStore.cache.best.getOrDefault(period),
|
||||
period, update = dag.lcDataStore.cache.bestUpdates.getOrDefault(period),
|
||||
computeDur = endTick - startTick
|
||||
defer: logBest()
|
||||
|
||||
@ -271,7 +270,8 @@ proc initLightClientUpdateForPeriod(
|
||||
maxParticipantsRes = dag.maxParticipantsBlock(highBid, lowSlot)
|
||||
maxParticipantsBid = maxParticipantsRes.bid.valueOr:
|
||||
if maxParticipantsRes.ok: # No single valid block exists in the period
|
||||
dag.lcDataStore.cache.best[period] = default(altair.LightClientUpdate)
|
||||
dag.lcDataStore.cache.bestUpdates[period] =
|
||||
default(altair.LightClientUpdate)
|
||||
return
|
||||
|
||||
# The block with highest participation may refer to a `finalized_checkpoint`
|
||||
@ -360,7 +360,7 @@ proc initLightClientUpdateForPeriod(
|
||||
update.sync_aggregate = blck.asSigned().message.body.sync_aggregate
|
||||
else: raiseAssert "Unreachable"
|
||||
update.signature_slot = signatureBid.slot
|
||||
dag.lcDataStore.cache.best[period] = update
|
||||
dag.lcDataStore.cache.bestUpdates[period] = update
|
||||
|
||||
proc getLightClientData(
|
||||
dag: ChainDAGRef,
|
||||
@ -507,7 +507,7 @@ proc createLightClientUpdates(
|
||||
let isCommitteeFinalized = dag.isNextSyncCommitteeFinalized(attested_period)
|
||||
var best =
|
||||
if isCommitteeFinalized:
|
||||
dag.lcDataStore.cache.best.getOrDefault(attested_period)
|
||||
dag.lcDataStore.cache.bestUpdates.getOrDefault(attested_period)
|
||||
else:
|
||||
let key = (attested_period, state.syncCommitteeRoot)
|
||||
dag.lcDataStore.cache.pendingBest.getOrDefault(key)
|
||||
@ -543,7 +543,7 @@ proc createLightClientUpdates(
|
||||
best.signature_slot = signature_slot
|
||||
|
||||
if isCommitteeFinalized:
|
||||
dag.lcDataStore.cache.best[attested_period] = best
|
||||
dag.lcDataStore.cache.bestUpdates[attested_period] = best
|
||||
debug "Best LC update improved", period = attested_period, update = best
|
||||
else:
|
||||
let key = (attested_period, state.syncCommitteeRoot)
|
||||
@ -683,7 +683,7 @@ proc processHeadChangeForLightClient*(dag: ChainDAGRef) =
|
||||
if dag.head.slot < dag.lcDataStore.cache.tailSlot:
|
||||
return
|
||||
|
||||
# Update `best` from `pendingBest` to ensure light client data
|
||||
# Update `bestUpdates` from `pendingBest` to ensure light client data
|
||||
# only refers to sync committees as selected by fork choice
|
||||
let headPeriod = dag.head.slot.sync_committee_period
|
||||
if not dag.isNextSyncCommitteeFinalized(headPeriod):
|
||||
@ -699,12 +699,12 @@ proc processHeadChangeForLightClient*(dag: ChainDAGRef) =
|
||||
dag.handleUnexpectedLightClientError(period.start_slot)
|
||||
continue
|
||||
key = (period, syncCommitteeRoot)
|
||||
dag.lcDataStore.cache.best[period] =
|
||||
dag.lcDataStore.cache.bestUpdates[period] =
|
||||
dag.lcDataStore.cache.pendingBest.getOrDefault(key)
|
||||
withState(dag.headState): # Common case separate to avoid `tmpState` copy
|
||||
when stateFork >= BeaconStateFork.Altair:
|
||||
let key = (headPeriod, state.syncCommitteeRoot)
|
||||
dag.lcDataStore.cache.best[headPeriod] =
|
||||
dag.lcDataStore.cache.bestUpdates[headPeriod] =
|
||||
dag.lcDataStore.cache.pendingBest.getOrDefault(key)
|
||||
else: raiseAssert "Unreachable" # `tailSlot` cannot be before Altair
|
||||
|
||||
@ -731,10 +731,8 @@ proc processFinalizationForLightClient*(
|
||||
break
|
||||
bid = bsi.bid
|
||||
if bid.slot >= lowSlot:
|
||||
dag.lcDataStore.cache.bootstrap[bid.slot] =
|
||||
CachedLightClientBootstrap(
|
||||
current_sync_committee_branch:
|
||||
dag.getLightClientData(bid).current_sync_committee_branch)
|
||||
dag.lcDataStore.cache.currentBranches[bid.slot] =
|
||||
dag.getLightClientData(bid).current_sync_committee_branch
|
||||
boundarySlot = bid.slot.nextEpochBoundarySlot
|
||||
if boundarySlot < SLOTS_PER_EPOCH:
|
||||
break
|
||||
@ -755,19 +753,19 @@ proc processFinalizationForLightClient*(
|
||||
|
||||
# Prune bootstrap data that is no longer relevant
|
||||
var slotsToDelete: seq[Slot]
|
||||
for slot in dag.lcDataStore.cache.bootstrap.keys:
|
||||
for slot in dag.lcDataStore.cache.currentBranches.keys:
|
||||
if slot < targetTailSlot:
|
||||
slotsToDelete.add slot
|
||||
for slot in slotsToDelete:
|
||||
dag.lcDataStore.cache.bootstrap.del slot
|
||||
dag.lcDataStore.cache.currentBranches.del slot
|
||||
|
||||
# Prune best `LightClientUpdate` that are no longer relevant
|
||||
var periodsToDelete: seq[SyncCommitteePeriod]
|
||||
for period in dag.lcDataStore.cache.best.keys:
|
||||
for period in dag.lcDataStore.cache.bestUpdates.keys:
|
||||
if period < targetTailPeriod:
|
||||
periodsToDelete.add period
|
||||
for period in periodsToDelete:
|
||||
dag.lcDataStore.cache.best.del period
|
||||
dag.lcDataStore.cache.bestUpdates.del period
|
||||
|
||||
# Prune best `LightClientUpdate` referring to non-finalized sync committees
|
||||
# that are no longer relevant, i.e., orphaned or too old
|
||||
@ -798,18 +796,18 @@ proc getLightClientBootstrap*(
|
||||
if slot > dag.finalizedHead.blck.slot:
|
||||
debug "LC bootstrap unavailable: Not finalized", blockRoot
|
||||
return err()
|
||||
var cachedBootstrap = dag.lcDataStore.cache.bootstrap.getOrDefault(slot)
|
||||
if cachedBootstrap.current_sync_committee_branch.isZeroMemory:
|
||||
var branch = dag.lcDataStore.cache.currentBranches.getOrDefault(slot)
|
||||
if branch.isZeroMemory:
|
||||
if dag.lcDataStore.importMode == LightClientDataImportMode.OnDemand:
|
||||
let bsi = ? dag.getExistingBlockIdAtSlot(slot)
|
||||
var tmpState = assignClone(dag.headState)
|
||||
dag.withUpdatedExistingState(tmpState[], bsi) do:
|
||||
cachedBootstrap.current_sync_committee_branch = withState(state):
|
||||
branch = withState(state):
|
||||
when stateFork >= BeaconStateFork.Altair:
|
||||
state.data.build_proof(altair.CURRENT_SYNC_COMMITTEE_INDEX).get
|
||||
else: raiseAssert "Unreachable"
|
||||
do: return err()
|
||||
dag.lcDataStore.cache.bootstrap[slot] = cachedBootstrap
|
||||
dag.lcDataStore.cache.currentBranches[slot] = branch
|
||||
else:
|
||||
debug "LC bootstrap unavailable: Data not cached", slot
|
||||
return err()
|
||||
@ -822,7 +820,7 @@ proc getLightClientBootstrap*(
|
||||
bootstrap.current_sync_committee =
|
||||
? dag.existingCurrentSyncCommitteeForPeriod(tmpState[], period)
|
||||
bootstrap.current_sync_committee_branch =
|
||||
cachedBootstrap.current_sync_committee_branch
|
||||
branch
|
||||
return ok bootstrap
|
||||
else:
|
||||
debug "LC bootstrap unavailable: Block before Altair", slot
|
||||
@ -836,7 +834,7 @@ proc getLightClientUpdateForPeriod*(
|
||||
|
||||
if dag.lcDataStore.importMode == LightClientDataImportMode.OnDemand:
|
||||
dag.initLightClientUpdateForPeriod(period)
|
||||
result = some(dag.lcDataStore.cache.best.getOrDefault(period))
|
||||
result = some(dag.lcDataStore.cache.bestUpdates.getOrDefault(period))
|
||||
let numParticipants = countOnes(result.get.sync_aggregate.sync_committee_bits)
|
||||
if numParticipants < MIN_SYNC_COMMITTEE_PARTICIPANTS:
|
||||
result.reset()
|
||||
|
@ -149,6 +149,15 @@ type
|
||||
|
||||
### Modified/overloaded
|
||||
|
||||
FinalityBranch* =
|
||||
array[log2trunc(FINALIZED_ROOT_INDEX), Eth2Digest]
|
||||
|
||||
CurrentSyncCommitteeBranch* =
|
||||
array[log2trunc(CURRENT_SYNC_COMMITTEE_INDEX), Eth2Digest]
|
||||
|
||||
NextSyncCommitteeBranch* =
|
||||
array[log2trunc(NEXT_SYNC_COMMITTEE_INDEX), Eth2Digest]
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#lightclientbootstrap
|
||||
LightClientBootstrap* = object
|
||||
header*: BeaconBlockHeader
|
||||
@ -156,9 +165,7 @@ type
|
||||
|
||||
current_sync_committee*: SyncCommittee
|
||||
## Current sync committee corresponding to `header`
|
||||
|
||||
current_sync_committee_branch*:
|
||||
array[log2trunc(CURRENT_SYNC_COMMITTEE_INDEX), Eth2Digest]
|
||||
current_sync_committee_branch*: CurrentSyncCommitteeBranch
|
||||
|
||||
# https://github.com/ethereum/consensus-specs/blob/vFuture/specs/altair/sync-protocol.md#lightclientupdate
|
||||
LightClientUpdate* = object
|
||||
@ -168,13 +175,11 @@ type
|
||||
next_sync_committee*: SyncCommittee
|
||||
## Next sync committee corresponding to `attested_header`,
|
||||
## if signature is from current sync committee
|
||||
next_sync_committee_branch*:
|
||||
array[log2trunc(NEXT_SYNC_COMMITTEE_INDEX), Eth2Digest]
|
||||
next_sync_committee_branch*: NextSyncCommitteeBranch
|
||||
|
||||
# The finalized beacon block header attested to by Merkle branch
|
||||
finalized_header*: BeaconBlockHeader
|
||||
finality_branch*:
|
||||
array[log2trunc(FINALIZED_ROOT_INDEX), Eth2Digest]
|
||||
finality_branch*: FinalityBranch
|
||||
|
||||
sync_aggregate*: SyncAggregate
|
||||
signature_slot*: Slot
|
||||
@ -187,8 +192,7 @@ type
|
||||
|
||||
# The finalized beacon block header attested to by Merkle branch
|
||||
finalized_header*: BeaconBlockHeader
|
||||
finality_branch*:
|
||||
array[log2trunc(FINALIZED_ROOT_INDEX), Eth2Digest]
|
||||
finality_branch*: FinalityBranch
|
||||
|
||||
# Sync committee aggregate signature
|
||||
sync_aggregate*: SyncAggregate
|
||||
|
@ -177,16 +177,14 @@ suite "EF - Altair - Unittests - Sync protocol" & preset():
|
||||
# Sync committee signing the attested_header
|
||||
(sync_aggregate, signature_slot) = get_sync_aggregate(cfg, forked[])
|
||||
next_sync_committee = SyncCommittee()
|
||||
next_sync_committee_branch =
|
||||
default(array[log2trunc(altair.NEXT_SYNC_COMMITTEE_INDEX), Eth2Digest])
|
||||
next_sync_committee_branch = default(altair.NextSyncCommitteeBranch)
|
||||
|
||||
# Ensure that finality checkpoint is genesis
|
||||
check state.finalized_checkpoint.epoch == 0
|
||||
# Finality is unchanged
|
||||
let
|
||||
finality_header = BeaconBlockHeader()
|
||||
finality_branch =
|
||||
default(array[log2trunc(altair.FINALIZED_ROOT_INDEX), Eth2Digest])
|
||||
finality_branch = default(altair.FinalityBranch)
|
||||
|
||||
update = altair.LightClientUpdate(
|
||||
attested_header: attested_header,
|
||||
@ -235,13 +233,11 @@ suite "EF - Altair - Unittests - Sync protocol" & preset():
|
||||
# Sync committee signing the attested_header
|
||||
(sync_aggregate, signature_slot) = get_sync_aggregate(cfg, forked[])
|
||||
next_sync_committee = SyncCommittee()
|
||||
next_sync_committee_branch =
|
||||
default(array[log2trunc(altair.NEXT_SYNC_COMMITTEE_INDEX), Eth2Digest])
|
||||
next_sync_committee_branch = default(altair.NextSyncCommitteeBranch)
|
||||
|
||||
# Finality is unchanged
|
||||
finality_header = BeaconBlockHeader()
|
||||
finality_branch =
|
||||
default(array[log2trunc(altair.FINALIZED_ROOT_INDEX), Eth2Digest])
|
||||
finality_branch = default(altair.FinalityBranch)
|
||||
|
||||
update = altair.LightClientUpdate(
|
||||
attested_header: attested_header,
|
||||
@ -297,8 +293,7 @@ suite "EF - Altair - Unittests - Sync protocol" & preset():
|
||||
|
||||
# Finality is unchanged
|
||||
finality_header = BeaconBlockHeader()
|
||||
finality_branch =
|
||||
default(array[log2trunc(altair.FINALIZED_ROOT_INDEX), Eth2Digest])
|
||||
finality_branch = default(altair.FinalityBranch)
|
||||
|
||||
update = altair.LightClientUpdate(
|
||||
attested_header: attested_header,
|
||||
@ -354,8 +349,7 @@ suite "EF - Altair - Unittests - Sync protocol" & preset():
|
||||
|
||||
# Updated sync_committee and finality
|
||||
next_sync_committee = SyncCommittee()
|
||||
next_sync_committee_branch =
|
||||
default(array[log2trunc(altair.NEXT_SYNC_COMMITTEE_INDEX), Eth2Digest])
|
||||
next_sync_committee_branch = default(altair.NextSyncCommitteeBranch)
|
||||
finalized_block = blocks[SLOTS_PER_EPOCH - 1].altairData
|
||||
finalized_header = finalized_block.toBeaconBlockHeader
|
||||
check:
|
||||
|
Loading…
x
Reference in New Issue
Block a user