restrict best LC update collection to canonical blocks (#5613)
Simplify best `LightClientUpdate` collection by tracking only canonical data instead of tracking the best update across all branches within the sync committee period. - https://github.com/ethereum/consensus-specs/pull/3553
This commit is contained in:
parent
946ebe54cf
commit
c33dd2c170
|
@ -89,20 +89,17 @@ type
|
|||
getStmt: SqliteStmt[int64, (int64, seq[byte])]
|
||||
putStmt: SqliteStmt[(int64, seq[byte]), void]
|
||||
delStmt: SqliteStmt[int64, void]
|
||||
delFromStmt: SqliteStmt[int64, void]
|
||||
keepFromStmt: SqliteStmt[int64, void]
|
||||
|
||||
BestLightClientUpdateStore = object
|
||||
getStmt: SqliteStmt[int64, (int64, seq[byte])]
|
||||
putStmt: SqliteStmt[(int64, int64, seq[byte]), void]
|
||||
delStmt: SqliteStmt[int64, void]
|
||||
delFromStmt: SqliteStmt[int64, void]
|
||||
keepFromStmt: SqliteStmt[int64, void]
|
||||
|
||||
SealedSyncCommitteePeriodStore = object
|
||||
containsStmt: SqliteStmt[int64, int64]
|
||||
putStmt: SqliteStmt[int64, void]
|
||||
delFromStmt: SqliteStmt[int64, void]
|
||||
keepFromStmt: SqliteStmt[int64, void]
|
||||
|
||||
LightClientDataDB* = ref object
|
||||
|
@ -405,10 +402,6 @@ proc initLegacyBestUpdatesStore(
|
|||
DELETE FROM `""" & name & """`
|
||||
WHERE `period` = ?;
|
||||
""", int64, void, managed = false).expect("SQL query OK")
|
||||
delFromStmt = backend.prepareStmt("""
|
||||
DELETE FROM `""" & name & """`
|
||||
WHERE `period` >= ?;
|
||||
""", int64, void, managed = false).expect("SQL query OK")
|
||||
keepFromStmt = backend.prepareStmt("""
|
||||
DELETE FROM `""" & name & """`
|
||||
WHERE `period` < ?;
|
||||
|
@ -418,14 +411,12 @@ proc initLegacyBestUpdatesStore(
|
|||
getStmt: getStmt,
|
||||
putStmt: putStmt,
|
||||
delStmt: delStmt,
|
||||
delFromStmt: delFromStmt,
|
||||
keepFromStmt: keepFromStmt)
|
||||
|
||||
func close(store: var LegacyBestLightClientUpdateStore) =
|
||||
store.getStmt.disposeSafe()
|
||||
store.putStmt.disposeSafe()
|
||||
store.delStmt.disposeSafe()
|
||||
store.delFromStmt.disposeSafe()
|
||||
store.keepFromStmt.disposeSafe()
|
||||
|
||||
proc initBestUpdatesStore(
|
||||
|
@ -470,10 +461,6 @@ proc initBestUpdatesStore(
|
|||
DELETE FROM `""" & name & """`
|
||||
WHERE `period` = ?;
|
||||
""", int64, void, managed = false).expect("SQL query OK")
|
||||
delFromStmt = backend.prepareStmt("""
|
||||
DELETE FROM `""" & name & """`
|
||||
WHERE `period` >= ?;
|
||||
""", int64, void, managed = false).expect("SQL query OK")
|
||||
keepFromStmt = backend.prepareStmt("""
|
||||
DELETE FROM `""" & name & """`
|
||||
WHERE `period` < ?;
|
||||
|
@ -483,14 +470,12 @@ proc initBestUpdatesStore(
|
|||
getStmt: getStmt,
|
||||
putStmt: putStmt,
|
||||
delStmt: delStmt,
|
||||
delFromStmt: delFromStmt,
|
||||
keepFromStmt: keepFromStmt)
|
||||
|
||||
func close(store: var BestLightClientUpdateStore) =
|
||||
store.getStmt.disposeSafe()
|
||||
store.putStmt.disposeSafe()
|
||||
store.delStmt.disposeSafe()
|
||||
store.delFromStmt.disposeSafe()
|
||||
store.keepFromStmt.disposeSafe()
|
||||
|
||||
proc getBestUpdate*(
|
||||
|
@ -559,13 +544,6 @@ func putBestUpdate*(
|
|||
let res = db.legacyBestUpdates.delStmt.exec(period.int64)
|
||||
res.expect("SQL query OK")
|
||||
|
||||
proc putUpdateIfBetter*(
|
||||
db: LightClientDataDB, period: SyncCommitteePeriod,
|
||||
update: ForkedLightClientUpdate) =
|
||||
let existing = db.getBestUpdate(period)
|
||||
if is_better_update(update, existing):
|
||||
db.putBestUpdate(period, update)
|
||||
|
||||
proc initSealedPeriodsStore(
|
||||
backend: SqStoreRef,
|
||||
name: string): KvResult[SealedSyncCommitteePeriodStore] =
|
||||
|
@ -589,10 +567,6 @@ proc initSealedPeriodsStore(
|
|||
`period`
|
||||
) VALUES (?);
|
||||
""", int64, void, managed = false).expect("SQL query OK")
|
||||
delFromStmt = backend.prepareStmt("""
|
||||
DELETE FROM `""" & name & """`
|
||||
WHERE `period` >= ?;
|
||||
""", int64, void, managed = false).expect("SQL query OK")
|
||||
keepFromStmt = backend.prepareStmt("""
|
||||
DELETE FROM `""" & name & """`
|
||||
WHERE `period` < ?;
|
||||
|
@ -601,13 +575,11 @@ proc initSealedPeriodsStore(
|
|||
ok SealedSyncCommitteePeriodStore(
|
||||
containsStmt: containsStmt,
|
||||
putStmt: putStmt,
|
||||
delFromStmt: delFromStmt,
|
||||
keepFromStmt: keepFromStmt)
|
||||
|
||||
func close(store: var SealedSyncCommitteePeriodStore) =
|
||||
store.containsStmt.disposeSafe()
|
||||
store.putStmt.disposeSafe()
|
||||
store.delFromStmt.disposeSafe()
|
||||
store.keepFromStmt.disposeSafe()
|
||||
|
||||
func isPeriodSealed*(
|
||||
|
@ -629,21 +601,6 @@ func sealPeriod*(
|
|||
let res = db.sealedPeriods.putStmt.exec(period.int64)
|
||||
res.expect("SQL query OK")
|
||||
|
||||
func delNonFinalizedPeriodsFrom*(
|
||||
db: LightClientDataDB, minPeriod: SyncCommitteePeriod) =
|
||||
doAssert not db.backend.readOnly # All `stmt` are non-nil
|
||||
doAssert minPeriod.isSupportedBySQLite
|
||||
block:
|
||||
let res = db.sealedPeriods.delFromStmt.exec(minPeriod.int64)
|
||||
res.expect("SQL query OK")
|
||||
block:
|
||||
let res = db.bestUpdates.delFromStmt.exec(minPeriod.int64)
|
||||
res.expect("SQL query OK")
|
||||
block:
|
||||
let res = db.legacyBestUpdates.delFromStmt.exec(minPeriod.int64)
|
||||
res.expect("SQL query OK")
|
||||
# `syncCommittees`, `currentBranches` and `headers` only have finalized data
|
||||
|
||||
func keepPeriodsFrom*(
|
||||
db: LightClientDataDB, minPeriod: SyncCommitteePeriod) =
|
||||
doAssert not db.backend.readOnly # All `stmt` are non-nil
|
||||
|
|
|
@ -39,18 +39,14 @@ type
|
|||
finalized_slot*: Slot
|
||||
finality_branch*: altair.FinalityBranch
|
||||
|
||||
current_period_best_update*: ref ForkedLightClientUpdate
|
||||
|
||||
LightClientDataCache* = object
|
||||
data*: Table[BlockId, CachedLightClientData]
|
||||
## Cached data for creating future `LightClientUpdate` instances.
|
||||
## 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.
|
||||
|
||||
pendingBest*:
|
||||
Table[(SyncCommitteePeriod, Eth2Digest), ForkedLightClientUpdate]
|
||||
## Same as `db.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*: ForkedLightClientFinalityUpdate
|
||||
## Tracks light client data for the latest slot that was signed by
|
||||
## at least `MIN_SYNC_COMMITTEE_PARTICIPANTS`. May be older than head.
|
||||
|
|
|
@ -799,21 +799,14 @@ proc currentSyncCommitteeForPeriod*(
|
|||
else: err()
|
||||
do: err()
|
||||
|
||||
func isNextSyncCommitteeFinalized*(
|
||||
dag: ChainDAGRef, period: SyncCommitteePeriod): bool =
|
||||
let finalizedSlot = dag.finalizedHead.slot
|
||||
if finalizedSlot < period.start_slot:
|
||||
false
|
||||
elif finalizedSlot < dag.cfg.ALTAIR_FORK_EPOCH.start_slot:
|
||||
false # Fork epoch not necessarily tied to sync committee period boundary
|
||||
proc getBlockIdAtSlot*(
|
||||
dag: ChainDAGRef, state: ForkyHashedBeaconState, slot: Slot): Opt[BlockId] =
|
||||
if slot >= state.data.slot:
|
||||
Opt.some state.latest_block_id
|
||||
elif state.data.slot <= slot + SLOTS_PER_HISTORICAL_ROOT:
|
||||
dag.getBlockId(state.data.get_block_root_at_slot(slot))
|
||||
else:
|
||||
true
|
||||
|
||||
func firstNonFinalizedPeriod*(dag: ChainDAGRef): SyncCommitteePeriod =
|
||||
if dag.finalizedHead.slot >= dag.cfg.ALTAIR_FORK_EPOCH.start_slot:
|
||||
dag.finalizedHead.slot.sync_committee_period + 1
|
||||
else:
|
||||
dag.cfg.ALTAIR_FORK_EPOCH.sync_committee_period
|
||||
Opt.none(BlockId)
|
||||
|
||||
proc updateBeaconMetrics(
|
||||
state: ForkedHashedBeaconState, bid: BlockId, cache: var StateCache) =
|
||||
|
@ -1338,15 +1331,6 @@ proc getFinalizedEpochRef*(dag: ChainDAGRef): EpochRef =
|
|||
dag.finalizedHead.blck, dag.finalizedHead.slot.epoch, false).expect(
|
||||
"getEpochRef for finalized head should always succeed")
|
||||
|
||||
proc getBlockIdAtSlot(
|
||||
dag: ChainDAGRef, state: ForkyHashedBeaconState, slot: Slot): Opt[BlockId] =
|
||||
if slot >= state.data.slot:
|
||||
Opt.some state.latest_block_id
|
||||
elif state.data.slot <= slot + SLOTS_PER_HISTORICAL_ROOT:
|
||||
dag.getBlockId(state.data.get_block_root_at_slot(slot))
|
||||
else:
|
||||
Opt.none(BlockId)
|
||||
|
||||
proc ancestorSlot*(
|
||||
dag: ChainDAGRef, state: ForkyHashedBeaconState, bid: BlockId,
|
||||
lowSlot: Slot): Opt[Slot] =
|
||||
|
|
|
@ -153,7 +153,9 @@ proc initLightClientBootstrapForPeriod(
|
|||
period: SyncCommitteePeriod): Opt[void] =
|
||||
## Compute and cache `LightClientBootstrap` data for all finalized
|
||||
## epoch boundary blocks within a given sync committee period.
|
||||
if not dag.isNextSyncCommitteeFinalized(period):
|
||||
if dag.finalizedHead.slot < period.start_slot:
|
||||
return ok()
|
||||
if dag.finalizedHead.slot < dag.cfg.ALTAIR_FORK_EPOCH.start_slot:
|
||||
return ok()
|
||||
if dag.lcDataStore.db.isPeriodSealed(period):
|
||||
return ok()
|
||||
|
@ -217,7 +219,10 @@ proc initLightClientUpdateForPeriod(
|
|||
## Compute and cache the best `LightClientUpdate` within a given
|
||||
## sync committee period up through the finalized head block.
|
||||
## Non-finalized blocks are processed incrementally by other functions.
|
||||
if not dag.isNextSyncCommitteeFinalized(period):
|
||||
## Should not be called for periods for which incremental computation started.
|
||||
if dag.finalizedHead.slot < period.start_slot:
|
||||
return ok()
|
||||
if dag.finalizedHead.slot < dag.cfg.ALTAIR_FORK_EPOCH.start_slot:
|
||||
return ok()
|
||||
if dag.lcDataStore.db.isPeriodSealed(period):
|
||||
return ok()
|
||||
|
@ -277,7 +282,6 @@ proc initLightClientUpdateForPeriod(
|
|||
tailSlot = max(dag.targetLightClientTailSlot, dag.tail.slot)
|
||||
lowSlot = max(periodStartSlot, tailSlot)
|
||||
highSlot = min(periodEndSlot, dag.finalizedHead.blck.slot)
|
||||
fullPeriodCovered = (dag.finalizedHead.slot > periodEndSlot)
|
||||
highBsi = dag.getExistingBlockIdAtSlot(highSlot).valueOr:
|
||||
dag.handleUnexpectedLightClientError(highSlot)
|
||||
return err()
|
||||
|
@ -285,10 +289,7 @@ proc initLightClientUpdateForPeriod(
|
|||
maxParticipantsRes = dag.maxParticipantsBlock(highBid, lowSlot)
|
||||
maxParticipantsBid = maxParticipantsRes.bid.valueOr:
|
||||
const update = default(ForkedLightClientUpdate)
|
||||
if fullPeriodCovered and maxParticipantsRes.res.isOk: # No block in period
|
||||
dag.lcDataStore.db.putBestUpdate(period, update)
|
||||
else:
|
||||
dag.lcDataStore.db.putUpdateIfBetter(period, update)
|
||||
dag.lcDataStore.db.putBestUpdate(period, update)
|
||||
return maxParticipantsRes.res
|
||||
|
||||
# The block with highest participation may refer to a `finalized_checkpoint`
|
||||
|
@ -392,10 +393,7 @@ proc initLightClientUpdateForPeriod(
|
|||
when lcDataFork > LightClientDataFork.None:
|
||||
forkyUpdate.signature_slot = signatureBid.slot
|
||||
|
||||
if fullPeriodCovered and res.isOk:
|
||||
dag.lcDataStore.db.putBestUpdate(period, update)
|
||||
else:
|
||||
dag.lcDataStore.db.putUpdateIfBetter(period, update)
|
||||
dag.lcDataStore.db.putBestUpdate(period, update)
|
||||
res
|
||||
|
||||
proc initLightClientDataForPeriod(
|
||||
|
@ -422,7 +420,8 @@ proc getLightClientData(
|
|||
except KeyError: raiseAssert "Unreachable"
|
||||
|
||||
proc cacheLightClientData(
|
||||
dag: ChainDAGRef, state: ForkyHashedBeaconState, bid: BlockId) =
|
||||
dag: ChainDAGRef, state: ForkyHashedBeaconState, bid: BlockId,
|
||||
current_period_best_update: ref ForkedLightClientUpdate) =
|
||||
## Cache data for a given block and its post-state to speed up creating future
|
||||
## `LightClientUpdate` and `LightClientBootstrap` instances that refer to this
|
||||
## block and state.
|
||||
|
@ -434,7 +433,9 @@ proc cacheLightClientData(
|
|||
finalized_slot:
|
||||
state.data.finalized_checkpoint.epoch.start_slot,
|
||||
finality_branch:
|
||||
state.data.build_proof(altair.FINALIZED_ROOT_GINDEX).get)
|
||||
state.data.build_proof(altair.FINALIZED_ROOT_GINDEX).get,
|
||||
current_period_best_update:
|
||||
current_period_best_update)
|
||||
if dag.lcDataStore.cache.data.hasKeyOrPut(bid, cachedData):
|
||||
doAssert false, "Redundant `cacheLightClientData` call"
|
||||
|
||||
|
@ -490,14 +491,6 @@ template lazy_header(name: untyped): untyped {.dirty.} =
|
|||
`name _ ptr` = addr obj.forky(data_fork).name
|
||||
`name _ ok`
|
||||
|
||||
template lazy_data(name: untyped): untyped {.dirty.} =
|
||||
## `createLightClientUpdates` helper to lazily load cached light client state.
|
||||
var `name` {.noinit.}: CachedLightClientData
|
||||
`name`.finalized_slot = FAR_FUTURE_SLOT
|
||||
template `load _ name`(bid: BlockId) =
|
||||
if `name`.finalized_slot == FAR_FUTURE_SLOT:
|
||||
`name` = dag.getLightClientData(bid)
|
||||
|
||||
template lazy_bid(name: untyped): untyped {.dirty.} =
|
||||
## `createLightClientUpdates` helper to lazily load a known to exist block id.
|
||||
var
|
||||
|
@ -519,26 +512,40 @@ proc createLightClientUpdates(
|
|||
state: ForkyHashedBeaconState,
|
||||
blck: ForkyTrustedSignedBeaconBlock,
|
||||
parent_bid: BlockId,
|
||||
data_fork: static LightClientDataFork) =
|
||||
data_fork: static LightClientDataFork): ref ForkedLightClientUpdate =
|
||||
## Create `LightClientUpdate` instances for a given block and its post-state,
|
||||
## and keep track of best / latest ones. Data about the parent block's
|
||||
## post-state must be cached (`cacheLightClientData`) before calling this.
|
||||
|
||||
# Verify sync committee has sufficient participants
|
||||
template sync_aggregate(): auto = blck.asSigned().message.body.sync_aggregate
|
||||
let num_active_participants = sync_aggregate.num_active_participants.uint64
|
||||
if num_active_participants < MIN_SYNC_COMMITTEE_PARTICIPANTS:
|
||||
return
|
||||
## Returns the best `LightClientUpdate` for the block's sync committee period.
|
||||
|
||||
# Verify attested block (parent) is recent enough and that state is available
|
||||
template attested_bid(): auto = parent_bid
|
||||
let attested_slot = attested_bid.slot
|
||||
if attested_slot < dag.lcDataStore.cache.tailSlot:
|
||||
return
|
||||
return (ref ForkedLightClientUpdate)()
|
||||
|
||||
# `blck` and `parent_bid` must be in the same sync committee period
|
||||
# to update the best per-period `LightClientUpdate`
|
||||
let
|
||||
attested_period = attested_slot.sync_committee_period
|
||||
signature_slot = blck.message.slot
|
||||
signature_period = signature_slot.sync_committee_period
|
||||
var
|
||||
attested_data = dag.getLightClientData(attested_bid)
|
||||
best =
|
||||
if attested_period != signature_period:
|
||||
(ref ForkedLightClientUpdate)()
|
||||
else:
|
||||
attested_data.current_period_best_update
|
||||
|
||||
# Verify sync committee has sufficient participants
|
||||
template sync_aggregate(): auto = blck.asSigned().message.body.sync_aggregate
|
||||
let num_active_participants = sync_aggregate.num_active_participants.uint64
|
||||
if num_active_participants < MIN_SYNC_COMMITTEE_PARTICIPANTS:
|
||||
return best
|
||||
|
||||
# Lazy variables to hold historic data
|
||||
lazy_header(attested_header)
|
||||
lazy_data(attested_data)
|
||||
lazy_bid(finalized_bid)
|
||||
lazy_header(finalized_header)
|
||||
|
||||
|
@ -547,19 +554,16 @@ proc createLightClientUpdates(
|
|||
var
|
||||
newFinality = false
|
||||
newOptimistic = false
|
||||
let
|
||||
signature_slot = blck.message.slot
|
||||
is_later = withForkyFinalityUpdate(latest):
|
||||
when lcDataFork > LightClientDataFork.None:
|
||||
if attested_slot != forkyFinalityUpdate.attested_header.beacon.slot:
|
||||
attested_slot > forkyFinalityUpdate.attested_header.beacon.slot
|
||||
else:
|
||||
signature_slot > forkyFinalityUpdate.signature_slot
|
||||
let is_later = withForkyFinalityUpdate(latest):
|
||||
when lcDataFork > LightClientDataFork.None:
|
||||
if attested_slot != forkyFinalityUpdate.attested_header.beacon.slot:
|
||||
attested_slot > forkyFinalityUpdate.attested_header.beacon.slot
|
||||
else:
|
||||
true
|
||||
signature_slot > forkyFinalityUpdate.signature_slot
|
||||
else:
|
||||
true
|
||||
if is_later and latest.assign_attested_header_with_migration(attested_bid):
|
||||
template forkyLatest: untyped = latest.forky(data_fork)
|
||||
load_attested_data(attested_bid)
|
||||
var finalized_slot = attested_data.finalized_slot
|
||||
if finalized_slot == forkyLatest.finalized_header.beacon.slot:
|
||||
forkyLatest.finality_branch = attested_data.finality_branch
|
||||
|
@ -593,21 +597,7 @@ proc createLightClientUpdates(
|
|||
newOptimistic = true
|
||||
|
||||
# Track best light client data for current period
|
||||
let
|
||||
attested_period = attested_slot.sync_committee_period
|
||||
signature_period = signature_slot.sync_committee_period
|
||||
if attested_period == signature_period:
|
||||
template next_sync_committee(): auto = state.data.next_sync_committee
|
||||
|
||||
let isCommitteeFinalized = dag.isNextSyncCommitteeFinalized(attested_period)
|
||||
var best =
|
||||
if isCommitteeFinalized:
|
||||
dag.lcDataStore.db.getBestUpdate(attested_period)
|
||||
else:
|
||||
let key = (attested_period, state.syncCommitteeRoot)
|
||||
dag.lcDataStore.cache.pendingBest.getOrDefault(key)
|
||||
|
||||
load_attested_data(attested_bid)
|
||||
let
|
||||
finalized_slot = attested_data.finalized_slot
|
||||
has_finality =
|
||||
|
@ -619,40 +609,38 @@ proc createLightClientUpdates(
|
|||
has_sync_committee: true,
|
||||
has_finality: has_finality,
|
||||
num_active_participants: num_active_participants)
|
||||
is_better = is_better_data(meta, best.toMeta)
|
||||
if is_better and best.assign_attested_header_with_migration(attested_bid):
|
||||
template forkyBest: untyped = best.forky(data_fork)
|
||||
forkyBest.next_sync_committee = next_sync_committee
|
||||
forkyBest.next_sync_committee_branch =
|
||||
attested_data.next_sync_committee_branch
|
||||
if finalized_slot == forkyBest.finalized_header.beacon.slot:
|
||||
forkyBest.finality_branch = attested_data.finality_branch
|
||||
elif finalized_slot == GENESIS_SLOT:
|
||||
forkyBest.finalized_header.reset()
|
||||
forkyBest.finality_branch = attested_data.finality_branch
|
||||
elif has_finality and
|
||||
forkyBest.assign_finalized_header(finalized_bid):
|
||||
forkyBest.finality_branch = attested_data.finality_branch
|
||||
is_better = is_better_data(
|
||||
meta, attested_data.current_period_best_update[].toMeta())
|
||||
if is_better:
|
||||
best = newClone attested_data.current_period_best_update[]
|
||||
if not best[].assign_attested_header_with_migration(attested_bid):
|
||||
best = attested_data.current_period_best_update
|
||||
else:
|
||||
forkyBest.finalized_header.reset()
|
||||
forkyBest.finality_branch.reset()
|
||||
forkyBest.sync_aggregate = sync_aggregate
|
||||
forkyBest.signature_slot = signature_slot
|
||||
|
||||
if isCommitteeFinalized:
|
||||
dag.lcDataStore.db.putBestUpdate(attested_period, best)
|
||||
template forkyBest: untyped = best[].forky(data_fork)
|
||||
forkyBest.next_sync_committee = state.data.next_sync_committee
|
||||
forkyBest.next_sync_committee_branch =
|
||||
attested_data.next_sync_committee_branch
|
||||
if finalized_slot == forkyBest.finalized_header.beacon.slot:
|
||||
forkyBest.finality_branch = attested_data.finality_branch
|
||||
elif finalized_slot == GENESIS_SLOT:
|
||||
forkyBest.finalized_header.reset()
|
||||
forkyBest.finality_branch = attested_data.finality_branch
|
||||
elif has_finality and
|
||||
forkyBest.assign_finalized_header(finalized_bid):
|
||||
forkyBest.finality_branch = attested_data.finality_branch
|
||||
else:
|
||||
forkyBest.finalized_header.reset()
|
||||
forkyBest.finality_branch.reset()
|
||||
forkyBest.sync_aggregate = sync_aggregate
|
||||
forkyBest.signature_slot = signature_slot
|
||||
debug "Best LC update improved",
|
||||
period = attested_period, update = forkyBest
|
||||
else:
|
||||
let key = (attested_period, state.syncCommitteeRoot)
|
||||
dag.lcDataStore.cache.pendingBest[key] = best
|
||||
debug "Best LC update improved",
|
||||
period = key, update = forkyBest
|
||||
|
||||
if newFinality and dag.lcDataStore.onLightClientFinalityUpdate != nil:
|
||||
dag.lcDataStore.onLightClientFinalityUpdate(latest)
|
||||
if newOptimistic and dag.lcDataStore.onLightClientOptimisticUpdate != nil:
|
||||
dag.lcDataStore.onLightClientOptimisticUpdate(latest.toOptimistic)
|
||||
best
|
||||
|
||||
proc createLightClientUpdates(
|
||||
dag: ChainDAGRef,
|
||||
|
@ -660,60 +648,74 @@ proc createLightClientUpdates(
|
|||
blck: ForkyTrustedSignedBeaconBlock,
|
||||
parent_bid: BlockId) =
|
||||
# Attested block (parent) determines `LightClientUpdate` fork
|
||||
withLcDataFork(dag.cfg.lcDataForkAtEpoch(parent_bid.slot.epoch)):
|
||||
let best = withLcDataFork(dag.cfg.lcDataForkAtEpoch(parent_bid.slot.epoch)):
|
||||
when lcDataFork > LightClientDataFork.None:
|
||||
dag.createLightClientUpdates(state, blck, parent_bid, lcDataFork)
|
||||
else:
|
||||
(ref ForkedLightClientUpdate)()
|
||||
dag.cacheLightClientData(state, blck.toBlockId(), best)
|
||||
|
||||
proc initLightClientDataCache*(dag: ChainDAGRef) =
|
||||
## Initialize cached light client data
|
||||
if not dag.shouldImportLcData:
|
||||
return
|
||||
|
||||
# Prune non-finalized data
|
||||
dag.lcDataStore.db.delNonFinalizedPeriodsFrom(dag.firstNonFinalizedPeriod)
|
||||
|
||||
# Initialize tail slot
|
||||
let targetTailSlot = max(dag.targetLightClientTailSlot, dag.tail.slot)
|
||||
dag.lcDataStore.cache.tailSlot = max(dag.head.slot, targetTailSlot)
|
||||
|
||||
# In `OnlyNew` mode, only head state needs to be cached
|
||||
if dag.head.slot < dag.lcDataStore.cache.tailSlot:
|
||||
return
|
||||
if dag.lcDataStore.importMode == LightClientDataImportMode.OnlyNew:
|
||||
withState(dag.headState):
|
||||
when consensusFork >= ConsensusFork.Altair:
|
||||
dag.cacheLightClientData(forkyState, dag.head.bid)
|
||||
else: raiseAssert "Unreachable" # `tailSlot` cannot be before Altair
|
||||
return
|
||||
|
||||
# Import light client data for finalized period through finalized head
|
||||
let
|
||||
finalizedSlot = max(dag.finalizedHead.blck.slot, targetTailSlot)
|
||||
finalizedPeriod = finalizedSlot.sync_committee_period
|
||||
var res =
|
||||
if finalizedSlot < dag.lcDataStore.cache.tailSlot:
|
||||
if dag.lcDataStore.importMode == LightClientDataImportMode.OnlyNew:
|
||||
Opt[void].ok()
|
||||
elif finalizedSlot >= dag.lcDataStore.cache.tailSlot:
|
||||
Opt[void].ok()
|
||||
else:
|
||||
dag.lcDataStore.cache.tailSlot = finalizedSlot
|
||||
dag.initLightClientDataForPeriod(finalizedPeriod)
|
||||
else:
|
||||
Opt[void].ok()
|
||||
|
||||
let lightClientStartTick = Moment.now()
|
||||
logScope: lightClientDataMaxPeriods = dag.lcDataStore.maxPeriods
|
||||
logScope:
|
||||
lightClientDataMaxPeriods = dag.lcDataStore.maxPeriods
|
||||
importMode = dag.lcDataStore.importMode
|
||||
debug "Initializing cached LC data", res
|
||||
|
||||
proc isSyncAggregateCanonical(
|
||||
dag: ChainDAGRef, state: ForkyHashedBeaconState,
|
||||
sync_aggregate: TrustedSyncAggregate, signature_slot: Slot): bool =
|
||||
if signature_slot > state.data.slot:
|
||||
return false
|
||||
let bid = dag.getBlockIdAtSlot(state, signature_slot).valueOr:
|
||||
return false
|
||||
if bid.slot != signature_slot:
|
||||
return false
|
||||
let bdata = dag.getForkedBlock(bid).valueOr:
|
||||
return false
|
||||
withBlck(bdata):
|
||||
when consensusFork >= ConsensusFork.Altair:
|
||||
forkyBlck.message.body.sync_aggregate == sync_aggregate
|
||||
else:
|
||||
false
|
||||
|
||||
# Build list of block to process.
|
||||
# As it is slow to load states in descending order,
|
||||
# build a reverse todo list to then process them in ascending order
|
||||
let tailSlot = dag.lcDataStore.cache.tailSlot
|
||||
var
|
||||
blocks = newSeqOfCap[BlockId](dag.head.slot - finalizedSlot + 1)
|
||||
blocks = newSeqOfCap[BlockId](dag.head.slot - tailSlot + 1)
|
||||
bid = dag.head.bid
|
||||
while bid.slot > finalizedSlot:
|
||||
while bid.slot > tailSlot:
|
||||
blocks.add bid
|
||||
bid = dag.existingParent(bid).valueOr:
|
||||
dag.handleUnexpectedLightClientError(bid.slot)
|
||||
res.err()
|
||||
break
|
||||
if bid.slot == finalizedSlot:
|
||||
if bid.slot == tailSlot:
|
||||
blocks.add bid
|
||||
|
||||
# Process blocks (reuses `dag.headState`, but restores it to the current head)
|
||||
|
@ -721,7 +723,7 @@ proc initLightClientDataCache*(dag: ChainDAGRef) =
|
|||
for i in countdown(blocks.high, blocks.low):
|
||||
bid = blocks[i]
|
||||
if not dag.updateExistingState(
|
||||
dag.headState, bid.atSlot, save = false, cache):
|
||||
dag.headState, bid.atSlot(), save = false, cache):
|
||||
dag.handleUnexpectedLightClientError(bid.slot)
|
||||
res.err()
|
||||
continue
|
||||
|
@ -731,13 +733,30 @@ proc initLightClientDataCache*(dag: ChainDAGRef) =
|
|||
continue
|
||||
withStateAndBlck(dag.headState, bdata):
|
||||
when consensusFork >= ConsensusFork.Altair:
|
||||
# Create `LightClientUpdate` instances
|
||||
if i < blocks.high:
|
||||
if i == blocks.high:
|
||||
let
|
||||
period = bid.slot.sync_committee_period
|
||||
best = newClone dag.lcDataStore.db.getBestUpdate(period)
|
||||
withForkyUpdate(best[]):
|
||||
when lcDataFork > LightClientDataFork.None:
|
||||
let
|
||||
attestedSlot = forkyUpdate.attested_header.beacon.slot
|
||||
signatureSlot = forkyUpdate.signature_slot
|
||||
if attestedSlot.sync_committee_period != period or
|
||||
signatureSlot.sync_committee_period != period:
|
||||
error "Invalid LC data cached", best = best[], period
|
||||
best[].reset()
|
||||
elif not dag.isSyncAggregateCanonical(
|
||||
forkyState,
|
||||
forkyUpdate.sync_aggregate.asTrusted(), # From DB, is trusted
|
||||
forkyUpdate.signature_slot):
|
||||
best[].reset() # Cached data is too recent or from other branch
|
||||
else:
|
||||
discard # Cached data is ancestor of `bid`
|
||||
dag.cacheLightClientData(forkyState, bid, best)
|
||||
else:
|
||||
dag.createLightClientUpdates(
|
||||
forkyState, forkyBlck, parentBid = blocks[i + 1])
|
||||
|
||||
# Cache light client data (non-finalized blocks may refer to this)
|
||||
dag.cacheLightClientData(forkyState, bid)
|
||||
else: raiseAssert "Unreachable"
|
||||
|
||||
let lightClientEndTick = Moment.now()
|
||||
|
@ -778,7 +797,6 @@ proc processNewBlockForLightClient*(
|
|||
when consensusFork >= ConsensusFork.Altair:
|
||||
template forkyState: untyped = state.forky(consensusFork)
|
||||
dag.createLightClientUpdates(forkyState, signedBlock, parentBid)
|
||||
dag.cacheLightClientData(forkyState, signedBlock.toBlockId())
|
||||
else:
|
||||
raiseAssert "Unreachable" # `tailSlot` cannot be before Altair
|
||||
|
||||
|
@ -789,31 +807,21 @@ proc processHeadChangeForLightClient*(dag: ChainDAGRef) =
|
|||
return
|
||||
if dag.head.slot < dag.lcDataStore.cache.tailSlot:
|
||||
return
|
||||
let
|
||||
headPeriod = dag.head.slot.sync_committee_period
|
||||
lowSlot = max(dag.lcDataStore.cache.tailSlot, dag.finalizedHead.slot)
|
||||
lowPeriod = lowSlot.sync_committee_period
|
||||
|
||||
# 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):
|
||||
let
|
||||
tailPeriod = dag.lcDataStore.cache.tailSlot.sync_committee_period
|
||||
lowPeriod = max(dag.firstNonFinalizedPeriod, tailPeriod)
|
||||
if headPeriod > lowPeriod:
|
||||
let tmpState = assignClone(dag.headState)
|
||||
for period in lowPeriod ..< headPeriod:
|
||||
let
|
||||
syncCommitteeRoot =
|
||||
dag.syncCommitteeRootForPeriod(tmpState[], period).valueOr:
|
||||
dag.handleUnexpectedLightClientError(period.start_slot)
|
||||
continue
|
||||
key = (period, syncCommitteeRoot)
|
||||
dag.lcDataStore.db.putBestUpdate(
|
||||
period, dag.lcDataStore.cache.pendingBest.getOrDefault(key))
|
||||
withState(dag.headState): # Common case separate to avoid `tmpState` copy
|
||||
when consensusFork >= ConsensusFork.Altair:
|
||||
let key = (headPeriod, forkyState.syncCommitteeRoot)
|
||||
dag.lcDataStore.db.putBestUpdate(
|
||||
headPeriod, dag.lcDataStore.cache.pendingBest.getOrDefault(key))
|
||||
else: raiseAssert "Unreachable" # `tailSlot` cannot be before Altair
|
||||
var blck = dag.head
|
||||
for period in countdown(headPeriod, lowPeriod):
|
||||
blck = blck.get_ancestor((period + 1).start_slot - 1)
|
||||
if blck == nil:
|
||||
return
|
||||
if blck.slot < lowSlot:
|
||||
return
|
||||
dag.lcDataStore.db.putBestUpdate(
|
||||
blck.slot.sync_committee_period,
|
||||
dag.getLightClientData(blck.bid).current_period_best_update[])
|
||||
|
||||
proc processFinalizationForLightClient*(
|
||||
dag: ChainDAGRef, oldFinalizedHead: BlockSlot) =
|
||||
|
@ -900,16 +908,6 @@ proc processFinalizationForLightClient*(
|
|||
let targetTailPeriod = dag.targetLightClientTailSlot.sync_committee_period
|
||||
dag.lcDataStore.db.keepPeriodsFrom(targetTailPeriod)
|
||||
|
||||
# Prune best `LightClientUpdate` referring to non-finalized sync committees
|
||||
# that are no longer relevant, i.e., orphaned or too old
|
||||
let firstNonFinalizedPeriod = dag.firstNonFinalizedPeriod
|
||||
var keysToDelete: seq[(SyncCommitteePeriod, Eth2Digest)]
|
||||
for (period, committeeRoot) in dag.lcDataStore.cache.pendingBest.keys:
|
||||
if period < firstNonFinalizedPeriod:
|
||||
keysToDelete.add (period, committeeRoot)
|
||||
for key in keysToDelete:
|
||||
dag.lcDataStore.cache.pendingBest.del key
|
||||
|
||||
proc getLightClientBootstrap(
|
||||
dag: ChainDAGRef,
|
||||
header: ForkyLightClientHeader): ForkedLightClientBootstrap =
|
||||
|
@ -1003,7 +1001,8 @@ proc getLightClientUpdateForPeriod*(
|
|||
if not dag.lcDataStore.serve:
|
||||
return default(ForkedLightClientUpdate)
|
||||
|
||||
if dag.lcDataStore.importMode == LightClientDataImportMode.OnDemand:
|
||||
if dag.lcDataStore.importMode == LightClientDataImportMode.OnDemand and
|
||||
period < dag.finalizedHead.blck.slot.sync_committee_period:
|
||||
if dag.initLightClientUpdateForPeriod(period).isErr:
|
||||
return default(ForkedLightClientUpdate)
|
||||
let
|
||||
|
|
|
@ -743,3 +743,7 @@ template asTrusted*(
|
|||
SigVerifiedSignedBeaconBlock |
|
||||
MsgTrustedSignedBeaconBlock): TrustedSignedBeaconBlock =
|
||||
isomorphicCast[TrustedSignedBeaconBlock](x)
|
||||
|
||||
template asTrusted*(
|
||||
x: SyncAggregate): TrustedSyncAggregate =
|
||||
isomorphicCast[TrustedSyncAggregate](x)
|
||||
|
|
Loading…
Reference in New Issue