make LC data fork aware (#4493)

In a future fork, light client data will be extended with execution info
to support more use cases. To anticipate such an upgrade, introduce
`Forky` and `Forked` types, and ready the database schema.
Because the mapping of sync committee periods to fork versions is not
necessarily unique (fork schedule not in sync with period boundaries),
an additional column is added to `period` -> `LightClientUpdate` table.
This commit is contained in:
Etan Kissling 2023-01-12 18:11:38 +01:00 committed by GitHub
parent 3b60b225b3
commit 7e276937dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1200 additions and 556 deletions

View File

@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2018-2022 Status Research & Development GmbH
# Copyright (c) 2018-2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -524,7 +524,8 @@ proc new*(T: type BeaconChainDB,
lcData = db.initLightClientDataDB(LightClientDataDBNames(
altairCurrentBranches: "lc_altair_current_branches",
altairBestUpdates: "lc_altair_best_updates",
legacyAltairBestUpdates: "lc_altair_best_updates",
bestUpdates: "lc_best_updates",
sealedPeriods: "lc_sealed_periods")).expectDb()
var blobs : KvStoreRef

View File

@ -12,6 +12,7 @@ else:
import
# Status libraries
stew/base10,
chronicles,
eth/db/kvstore_sqlite3,
# Beacon chain internals
@ -21,25 +22,24 @@ import
logScope: topics = "lcdata"
# `altair_current_sync_committee_branches` holds merkle proofs needed to
# construct `LightClientBootstrap` objects. The sync committee needs to
# be computed from the main DAG on-demand (usually a fast state access).
# `lc_altair_current_branches` holds merkle proofs needed to
# construct `LightClientBootstrap` objects.
# SSZ because this data does not compress well, and because this data
# needs to be bundled together with other data to fulfill requests.
#
# `altair_best_updates` holds full `LightClientUpdate` objects in SSZ form.
# These objects are frequently queried in bunk, but there is only one per
# `lc_best_updates` holds full `LightClientUpdate` objects in SSZ form.
# These objects are frequently queried in bulk, but there is only one per
# sync committee period, so storing the full sync committee is acceptable.
# This data could be stored as SZSSZ to avoid on-the-fly compression when a
# libp2p request is handled. However, the space savings are quite small.
# Furthermore, `LightClientUpdate` is consulted on each new block to attempt
# improving it. Continuously decompressing and recompressing seems inefficient.
# Finally, the libp2p context bytes depend on `attested_header.slot` to derive
# the underlying fork digest. The table name is insufficient to determine this
# unless one is made for each fork, even if there was no structural change.
# the underlying fork digest; the `kind` column is not sufficient to derive
# the fork digest, because the same storage format may be used across forks.
# SSZ storage selected due to the small size and reduced logic complexity.
#
# `sealed_sync_committee_periods` contains the sync committee periods for which
# `lc_sealed_periods` contains the sync committee periods for which
# full light client data was imported. Data for these periods may no longer
# improve regardless of further block processing. The listed periods are skipped
# when restarting the program.
@ -52,8 +52,8 @@ type
keepFromStmt: SqliteStmt[int64, void]
BestLightClientUpdateStore = object
getStmt: SqliteStmt[int64, seq[byte]]
putStmt: SqliteStmt[(int64, seq[byte]), void]
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]
@ -75,7 +75,7 @@ type
## Data stored for all finalized epoch boundary blocks.
bestUpdates: BestLightClientUpdateStore
## SyncCommitteePeriod -> altair.LightClientUpdate
## SyncCommitteePeriod -> (LightClientDataFork, LightClientUpdate)
## Stores the `LightClientUpdate` with the most `sync_committee_bits` per
## `SyncCommitteePeriod`. Sync committee finality gives precedence.
@ -162,25 +162,43 @@ func putCurrentSyncCommitteeBranch*(
proc initBestUpdatesStore(
backend: SqStoreRef,
name: string): KvResult[BestLightClientUpdateStore] =
name, legacyAltairName: string,
): KvResult[BestLightClientUpdateStore] =
? backend.exec("""
CREATE TABLE IF NOT EXISTS `""" & name & """` (
`period` INTEGER PRIMARY KEY, -- `SyncCommitteePeriod`
`update` BLOB -- `altair.LightClientUpdate` (SSZ)
`kind` INTEGER, -- `LightClientDataFork`
`update` BLOB -- `LightClientUpdate` (SSZ)
);
""")
if backend.hasTable(legacyAltairName).expect("SQL query OK"):
info "Importing Altair light client data"
# SyncCommitteePeriod -> altair.LightClientUpdate
const legacyKind = Base10.toString(ord(LightClientDataFork.Altair).uint)
? backend.exec("""
INSERT OR IGNORE INTO `""" & name & """` (
`period`, `kind`, `update`
)
SELECT `period`, """ & legacyKind & """ AS `kind`, `update`
FROM `""" & legacyAltairName & """`;
""")
? backend.exec("""
DROP TABLE `""" & legacyAltairName & """`;
""")
let
getStmt = backend.prepareStmt("""
SELECT `update`
SELECT `kind`, `update`
FROM `""" & name & """`
WHERE `period` = ?;
""", int64, seq[byte], managed = false).expect("SQL query OK")
""", int64, (int64, seq[byte]), managed = false)
.expect("SQL query OK")
putStmt = backend.prepareStmt("""
REPLACE INTO `""" & name & """` (
`period`, `update`
) VALUES (?, ?);
""", (int64, seq[byte]), void, managed = false).expect("SQL query OK")
`period`, `kind`, `update`
) VALUES (?, ?, ?);
""", (int64, int64, seq[byte]), void, managed = false)
.expect("SQL query OK")
delStmt = backend.prepareStmt("""
DELETE FROM `""" & name & """`
WHERE `period` = ?;
@ -210,34 +228,47 @@ func close(store: BestLightClientUpdateStore) =
proc getBestUpdate*(
db: LightClientDataDB, period: SyncCommitteePeriod
): altair.LightClientUpdate =
): ForkedLightClientUpdate =
doAssert period.isSupportedBySQLite
var update: seq[byte]
var update: (int64, seq[byte])
for res in db.bestUpdates.getStmt.exec(period.int64, update):
res.expect("SQL query OK")
try:
return SSZ.decode(update, altair.LightClientUpdate)
case update[0]
of ord(LightClientDataFork.Altair).int64:
return ForkedLightClientUpdate(
kind: LightClientDataFork.Altair,
altairData: SSZ.decode(update[1], altair.LightClientUpdate))
else:
warn "Unsupported LC data store kind", store = "bestUpdates",
period, kind = update[0]
return default(ForkedLightClientUpdate)
except SszError as exc:
error "LC data store corrupted", store = "bestUpdates",
period, exc = exc.msg
return default(altair.LightClientUpdate)
period, kind = update[0], exc = exc.msg
return default(ForkedLightClientUpdate)
func putBestUpdate*(
db: LightClientDataDB, period: SyncCommitteePeriod,
update: altair.LightClientUpdate) =
update: ForkedLightClientUpdate) =
doAssert period.isSupportedBySQLite
let numParticipants = update.sync_aggregate.num_active_participants
if numParticipants < MIN_SYNC_COMMITTEE_PARTICIPANTS:
let res = db.bestUpdates.delStmt.exec(period.int64)
res.expect("SQL query OK")
else:
let res = db.bestUpdates.putStmt.exec(
(period.int64, SSZ.encode(update)))
res.expect("SQL query OK")
withForkyUpdate(update):
when lcDataFork >= LightClientDataFork.Altair:
let numParticipants = forkyUpdate.sync_aggregate.num_active_participants
if numParticipants < MIN_SYNC_COMMITTEE_PARTICIPANTS:
let res = db.bestUpdates.delStmt.exec(period.int64)
res.expect("SQL query OK")
else:
let res = db.bestUpdates.putStmt.exec(
(period.int64, lcDataFork.int64, SSZ.encode(forkyUpdate)))
res.expect("SQL query OK")
else:
let res = db.bestUpdates.delStmt.exec(period.int64)
res.expect("SQL query OK")
proc putUpdateIfBetter*(
db: LightClientDataDB, period: SyncCommitteePeriod,
update: altair.LightClientUpdate) =
update: ForkedLightClientUpdate) =
let existing = db.getBestUpdate(period)
if is_better_update(update, existing):
db.putBestUpdate(period, update)
@ -299,13 +330,14 @@ func sealPeriod*(
let res = db.sealedPeriods.putStmt.exec(period.int64)
res.expect("SQL query OK")
func delPeriodsFrom*(
func delNonFinalizedPeriodsFrom*(
db: LightClientDataDB, minPeriod: SyncCommitteePeriod) =
doAssert minPeriod.isSupportedBySQLite
let res1 = db.sealedPeriods.delFromStmt.exec(minPeriod.int64)
res1.expect("SQL query OK")
let res2 = db.bestUpdates.delFromStmt.exec(minPeriod.int64)
res2.expect("SQL query OK")
# `currentBranches` only has finalized data
func keepPeriodsFrom*(
db: LightClientDataDB, minPeriod: SyncCommitteePeriod) =
@ -321,7 +353,8 @@ func keepPeriodsFrom*(
type LightClientDataDBNames* = object
altairCurrentBranches*: string
altairBestUpdates*: string
legacyAltairBestUpdates*: string
bestUpdates*: string
sealedPeriods*: string
proc initLightClientDataDB*(
@ -331,7 +364,8 @@ proc initLightClientDataDB*(
currentBranches =
? backend.initCurrentBranchesStore(names.altairCurrentBranches)
bestUpdates =
? backend.initBestUpdatesStore(names.altairBestUpdates)
? backend.initBestUpdatesStore(
names.bestUpdates, names.legacyAltairBestUpdates)
sealedPeriods =
? backend.initSealedPeriodsStore(names.sealedPeriods)

View File

@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2018-2022 Status Research & Development GmbH
# Copyright (c) 2018-2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -43,8 +43,10 @@ type
blocksQueue*: AsyncEventQueue[EventBeaconBlockObject]
headQueue*: AsyncEventQueue[HeadChangeInfoObject]
reorgQueue*: AsyncEventQueue[ReorgInfoObject]
finUpdateQueue*: AsyncEventQueue[altair.LightClientFinalityUpdate]
optUpdateQueue*: AsyncEventQueue[altair.LightClientOptimisticUpdate]
finUpdateQueue*: AsyncEventQueue[
RestVersioned[ForkedLightClientFinalityUpdate]]
optUpdateQueue*: AsyncEventQueue[
RestVersioned[ForkedLightClientOptimisticUpdate]]
attestQueue*: AsyncEventQueue[Attestation]
contribQueue*: AsyncEventQueue[SignedContributionAndProof]
exitQueue*: AsyncEventQueue[SignedVoluntaryExit]

View File

@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2022 Status Research & Development GmbH
# Copyright (c) 2022-2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -29,9 +29,9 @@ type
## Like `full`, but import on demand instead of on start.
OnLightClientFinalityUpdateCallback* =
proc(data: altair.LightClientFinalityUpdate) {.gcsafe, raises: [Defect].}
proc(data: ForkedLightClientFinalityUpdate) {.gcsafe, raises: [Defect].}
OnLightClientOptimisticUpdateCallback* =
proc(data: altair.LightClientOptimisticUpdate) {.gcsafe, raises: [Defect].}
proc(data: ForkedLightClientOptimisticUpdate) {.gcsafe, raises: [Defect].}
CachedLightClientData* = object
## Cached data from historical non-finalized states to improve speed when
@ -49,12 +49,12 @@ type
## Data stored for the finalized head block and all non-finalized blocks.
pendingBest*:
Table[(SyncCommitteePeriod, Eth2Digest), altair.LightClientUpdate]
Table[(SyncCommitteePeriod, Eth2Digest), ForkedLightClientUpdate]
## 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
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.

View File

@ -14,14 +14,10 @@ import
# Status libraries
stew/[bitops2, objects],
# Beacon chain internals
../spec/datatypes/[phase0, altair, bellatrix],
../spec/datatypes/[phase0, altair, bellatrix, capella, eip4844],
../beacon_chain_db_light_client,
"."/[block_pools_types, blockchain_dag]
from ../spec/datatypes/capella import TrustedSignedBeaconBlock, asSigned
from ../spec/datatypes/eip4844 import
HashedBeaconState, TrustedSignedBeaconBlock, asSigned
logScope: topics = "chaindag_lc"
type
@ -292,7 +288,7 @@ proc initLightClientUpdateForPeriod(
highBid = highBsi.bid
maxParticipantsRes = dag.maxParticipantsBlock(highBid, lowSlot)
maxParticipantsBid = maxParticipantsRes.bid.valueOr:
const update = default(altair.LightClientUpdate)
const update = default(ForkedLightClientUpdate)
if fullPeriodCovered and maxParticipantsRes.res.isOk: # No block in period
dag.lcDataStore.db.putBestUpdate(period, update)
else:
@ -350,7 +346,7 @@ proc initLightClientUpdateForPeriod(
finalizedBid = finalizedBsi.bid # For fallback `break` at start of loop
# Save best light client data for given period
var update {.noinit.}: altair.LightClientUpdate
var update {.noinit.}: ForkedLightClientUpdate
let attestedBid = dag.existingParent(signatureBid).valueOr:
dag.handleUnexpectedLightClientError(signatureBid.slot)
return err()
@ -360,35 +356,41 @@ proc initLightClientUpdateForPeriod(
return err()
withStateAndBlck(updatedState, bdata):
when stateFork >= BeaconStateFork.Altair:
update.attested_header = blck.toBeaconBlockHeader()
update.next_sync_committee = forkyState.data.next_sync_committee
update.next_sync_committee_branch =
forkyState.data.build_proof(altair.NEXT_SYNC_COMMITTEE_INDEX).get
if finalizedBid.slot == FAR_FUTURE_SLOT:
update.finality_branch.reset()
else:
update.finality_branch =
forkyState.data.build_proof(altair.FINALIZED_ROOT_INDEX).get
const lcDataFork = LightClientDataFork.Altair
update = ForkedLightClientUpdate(kind: lcDataFork)
template forkyUpdate: untyped = update.forky(lcDataFork)
forkyUpdate.attested_header = blck.toBeaconBlockHeader()
forkyUpdate.next_sync_committee = forkyState.data.next_sync_committee
forkyUpdate.next_sync_committee_branch =
forkyState.data.build_proof(altair.NEXT_SYNC_COMMITTEE_INDEX).get
if finalizedBid.slot != FAR_FUTURE_SLOT:
forkyUpdate.finality_branch =
forkyState.data.build_proof(altair.FINALIZED_ROOT_INDEX).get
else: raiseAssert "Unreachable"
do:
dag.handleUnexpectedLightClientError(attestedBid.slot)
return err()
if finalizedBid.slot == FAR_FUTURE_SLOT or finalizedBid.slot == GENESIS_SLOT:
update.finalized_header.reset()
else:
if finalizedBid.slot != FAR_FUTURE_SLOT and finalizedBid.slot != GENESIS_SLOT:
let bdata = dag.getExistingForkedBlock(finalizedBid).valueOr:
dag.handleUnexpectedLightClientError(finalizedBid.slot)
return err()
withBlck(bdata):
update.finalized_header = blck.toBeaconBlockHeader()
withForkyUpdate(update):
when lcDataFork >= LightClientDataFork.Altair:
forkyUpdate.finalized_header = blck.toBeaconBlockHeader()
let bdata = dag.getExistingForkedBlock(signatureBid).valueOr:
dag.handleUnexpectedLightClientError(signatureBid.slot)
return err()
withBlck(bdata):
when stateFork >= BeaconStateFork.Altair:
update.sync_aggregate = blck.asSigned().message.body.sync_aggregate
withForkyUpdate(update):
when lcDataFork >= LightClientDataFork.Altair:
forkyUpdate.sync_aggregate =
blck.asSigned().message.body.sync_aggregate
else: raiseAssert "Unreachable"
update.signature_slot = signatureBid.slot
withForkyUpdate(update):
when lcDataFork >= LightClientDataFork.Altair:
forkyUpdate.signature_slot = signatureBid.slot
if fullPeriodCovered and res.isOk:
dag.lcDataStore.db.putBestUpdate(period, update)
@ -451,19 +453,35 @@ proc deleteLightClientData*(dag: ChainDAGRef, bid: BlockId) =
template lazy_header(name: untyped): untyped {.dirty.} =
## `createLightClientUpdates` helper to lazily load a known block header.
var
`name _ ptr`: ptr[BeaconBlockHeader]
`name _ ptr`: ptr[data_fork.header]
`name _ ok` = true
template `assign _ name`(target: var BeaconBlockHeader, bid: BlockId): bool =
template `assign _ name`(
obj: var SomeForkyLightClientObject, bid: BlockId): untyped =
if `name _ ptr` != nil:
target = `name _ ptr`[]
obj.name = `name _ ptr`[]
elif `name _ ok`:
let bdata = dag.getExistingForkedBlock(bid)
if bdata.isErr:
dag.handleUnexpectedLightClientError(bid.slot)
`name _ ok` = false
else:
target = bdata.get.toBeaconBlockHeader()
`name _ ptr` = addr target
obj.name = bdata.get.toBeaconBlockHeader()
`name _ ptr` = addr obj.name
`name _ ok`
template `assign _ name _ with_migration`(
obj: var SomeForkedLightClientObject, bid: BlockId): untyped =
if `name _ ptr` != nil:
obj.migrateToDataFork(data_fork)
obj.forky(data_fork).name = `name _ ptr`[]
elif `name _ ok`:
let bdata = dag.getExistingForkedBlock(bid)
if bdata.isErr:
dag.handleUnexpectedLightClientError(bid.slot)
`name _ ok` = false
else:
obj.migrateToDataFork(data_fork)
obj.forky(data_fork).name = bdata.get.toBeaconBlockHeader()
`name _ ptr` = addr obj.forky(data_fork).name
`name _ ok`
template lazy_data(name: untyped): untyped {.dirty.} =
@ -494,7 +512,8 @@ proc createLightClientUpdates(
dag: ChainDAGRef,
state: HashedBeaconStateWithSyncCommittee,
blck: TrustedSignedBeaconBlockWithSyncAggregate,
parent_bid: BlockId) =
parent_bid: BlockId,
data_fork: static LightClientDataFork) =
## 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.
@ -518,35 +537,39 @@ proc createLightClientUpdates(
lazy_header(finalized_header)
# Update latest light client data
template latest(): auto = dag.lcDataStore.cache.latest
template latest(): untyped = dag.lcDataStore.cache.latest
var
newFinality = false
newOptimistic = false
let
signature_slot = blck.message.slot
is_later =
if attested_slot != latest.attested_header.slot:
attested_slot > latest.attested_header.slot
is_later = withForkyFinalityUpdate(latest):
when lcDataFork >= LightClientDataFork.Altair:
if attested_slot != forkyFinalityUpdate.attested_header.slot:
attested_slot > forkyFinalityUpdate.attested_header.slot
else:
signature_slot > forkyFinalityUpdate.signature_slot
else:
signature_slot > latest.signature_slot
if is_later and latest.attested_header.assign_attested_header(attested_bid):
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)
let finalized_slot = attested_data.finalized_slot
if finalized_slot == latest.finalized_header.slot:
latest.finality_branch = attested_data.finality_branch
if finalized_slot == forkyLatest.finalized_header.slot:
forkyLatest.finality_branch = attested_data.finality_branch
elif finalized_slot == GENESIS_SLOT:
latest.finalized_header.reset()
latest.finality_branch = attested_data.finality_branch
forkyLatest.finalized_header.reset()
forkyLatest.finality_branch = attested_data.finality_branch
elif finalized_slot >= dag.tail.slot and
load_finalized_bid(finalized_slot) and
latest.finalized_header.assign_finalized_header(finalized_bid):
latest.finality_branch = attested_data.finality_branch
forkyLatest.assign_finalized_header(finalized_bid):
forkyLatest.finality_branch = attested_data.finality_branch
newFinality = true
else:
latest.finalized_header.reset()
latest.finality_branch.reset()
latest.sync_aggregate = sync_aggregate
latest.signature_slot = signature_slot
forkyLatest.finalized_header.reset()
forkyLatest.finality_branch.reset()
forkyLatest.sync_aggregate = sync_aggregate
forkyLatest.signature_slot = signature_slot
newOptimistic = true
# Track best light client data for current period
@ -577,43 +600,60 @@ proc createLightClientUpdates(
has_finality: has_finality,
num_active_participants: num_active_participants)
is_better = is_better_data(meta, best.toMeta)
if is_better and best.attested_header.assign_attested_header(attested_bid):
best.next_sync_committee = next_sync_committee
best.next_sync_committee_branch = attested_data.next_sync_committee_branch
if finalized_slot == best.finalized_header.slot:
best.finality_branch = attested_data.finality_branch
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.slot:
forkyBest.finality_branch = attested_data.finality_branch
elif finalized_slot == GENESIS_SLOT:
best.finalized_header.reset()
best.finality_branch = attested_data.finality_branch
forkyBest.finalized_header.reset()
forkyBest.finality_branch = attested_data.finality_branch
elif has_finality and
best.finalized_header.assign_finalized_header(finalized_bid):
best.finality_branch = attested_data.finality_branch
forkyBest.assign_finalized_header(finalized_bid):
forkyBest.finality_branch = attested_data.finality_branch
else:
best.finalized_header.reset()
best.finality_branch.reset()
best.sync_aggregate = sync_aggregate
best.signature_slot = signature_slot
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)
debug "Best LC update improved", period = attested_period, update = best
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 = 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)
proc createLightClientUpdates(
dag: ChainDAGRef,
state: HashedBeaconStateWithSyncCommittee,
blck: TrustedSignedBeaconBlockWithSyncAggregate,
parent_bid: BlockId) =
# Attested block (parent) determines `LightClientUpdate` fork
case dag.cfg.lcDataForkAtEpoch(parent_bid.slot.epoch)
of LightClientDataFork.Altair:
dag.createLightClientUpdates(
state, blck, parent_bid, LightClientDataFork.Altair)
of LightClientDataFork.None:
return
proc initLightClientDataCache*(dag: ChainDAGRef) =
## Initialize cached light client data
if not dag.shouldImportLcData:
return
# Prune non-finalized data
dag.lcDataStore.db.delPeriodsFrom(dag.firstNonFinalizedPeriod)
dag.lcDataStore.db.delNonFinalizedPeriodsFrom(dag.firstNonFinalizedPeriod)
# Initialize tail slot
let targetTailSlot = dag.targetLightClientTailSlot
@ -836,84 +876,107 @@ proc processFinalizationForLightClient*(
proc getLightClientBootstrap*(
dag: ChainDAGRef,
blockRoot: Eth2Digest): Opt[altair.LightClientBootstrap] =
blockRoot: Eth2Digest): ForkedLightClientBootstrap =
if not dag.lcDataStore.serve:
return err()
return default(ForkedLightClientBootstrap)
let bdata = dag.getForkedBlock(blockRoot).valueOr:
debug "LC bootstrap unavailable: Block not found", blockRoot
return err()
return default(ForkedLightClientBootstrap)
withBlck(bdata):
let slot = blck.message.slot
when stateFork >= BeaconStateFork.Altair:
if slot < dag.targetLightClientTailSlot:
debug "LC bootstrap unavailable: Block too old", slot
return err()
return default(ForkedLightClientBootstrap)
if slot > dag.finalizedHead.blck.slot:
debug "LC bootstrap unavailable: Not finalized", blockRoot
return err()
return default(ForkedLightClientBootstrap)
var branch = dag.lcDataStore.db.getCurrentSyncCommitteeBranch(slot)
if branch.isZeroMemory:
if dag.lcDataStore.importMode == LightClientDataImportMode.OnDemand:
let bsi = ? dag.getExistingBlockIdAtSlot(slot)
let tmpState = assignClone(dag.headState)
let
bsi = dag.getExistingBlockIdAtSlot(slot).valueOr:
return default(ForkedLightClientBootstrap)
tmpState = assignClone(dag.headState)
dag.withUpdatedExistingState(tmpState[], bsi) do:
branch = withState(updatedState):
when stateFork >= BeaconStateFork.Altair:
forkyState.data.build_proof(
altair.CURRENT_SYNC_COMMITTEE_INDEX).get
else: raiseAssert "Unreachable"
do: return err()
do: return default(ForkedLightClientBootstrap)
dag.lcDataStore.db.putCurrentSyncCommitteeBranch(slot, branch)
else:
debug "LC bootstrap unavailable: Data not cached", slot
return err()
return default(ForkedLightClientBootstrap)
let period = slot.sync_committee_period
let tmpState = assignClone(dag.headState)
var bootstrap {.noinit.}: altair.LightClientBootstrap
bootstrap.header =
blck.toBeaconBlockHeader()
bootstrap.current_sync_committee =
? dag.existingCurrentSyncCommitteeForPeriod(tmpState[], period)
bootstrap.current_sync_committee_branch =
branch
return ok bootstrap
const lcDataFork = LightClientDataFork.Altair
var bootstrap = ForkedLightClientBootstrap(kind: lcDataFork)
template forkyBootstrap: untyped = bootstrap.forky(lcDataFork)
let
period = slot.sync_committee_period
tmpState = assignClone(dag.headState)
forkyBootstrap.current_sync_committee =
dag.existingCurrentSyncCommitteeForPeriod(tmpState[], period).valueOr:
return default(ForkedLightClientBootstrap)
forkyBootstrap.header = blck.toBeaconBlockHeader()
forkyBootstrap.current_sync_committee_branch = branch
return bootstrap
else:
debug "LC bootstrap unavailable: Block before Altair", slot
return err()
return default(ForkedLightClientBootstrap)
proc getLightClientUpdateForPeriod*(
dag: ChainDAGRef,
period: SyncCommitteePeriod): Option[altair.LightClientUpdate] =
period: SyncCommitteePeriod): ForkedLightClientUpdate =
if not dag.lcDataStore.serve:
return
return default(ForkedLightClientUpdate)
if dag.lcDataStore.importMode == LightClientDataImportMode.OnDemand:
if dag.initLightClientUpdateForPeriod(period).isErr:
return
result = some(dag.lcDataStore.db.getBestUpdate(period))
let numParticipants = result.get.sync_aggregate.num_active_participants
return default(ForkedLightClientUpdate)
let
update = dag.lcDataStore.db.getBestUpdate(period)
numParticipants = withForkyUpdate(update):
when lcDataFork >= LightClientDataFork.Altair:
forkyUpdate.sync_aggregate.num_active_participants
else:
0
if numParticipants < MIN_SYNC_COMMITTEE_PARTICIPANTS:
result.reset()
return default(ForkedLightClientUpdate)
update
proc getLightClientFinalityUpdate*(
dag: ChainDAGRef): Option[altair.LightClientFinalityUpdate] =
dag: ChainDAGRef): ForkedLightClientFinalityUpdate =
if not dag.lcDataStore.serve:
return
return default(ForkedLightClientFinalityUpdate)
result = some(dag.lcDataStore.cache.latest)
let numParticipants = result.get.sync_aggregate.num_active_participants
let
finalityUpdate = dag.lcDataStore.cache.latest
numParticipants = withForkyFinalityUpdate(finalityUpdate):
when lcDataFork >= LightClientDataFork.Altair:
forkyFinalityUpdate.sync_aggregate.num_active_participants
else:
0
if numParticipants < MIN_SYNC_COMMITTEE_PARTICIPANTS:
result.reset()
return default(ForkedLightClientFinalityUpdate)
finalityUpdate
proc getLightClientOptimisticUpdate*(
dag: ChainDAGRef): Option[altair.LightClientOptimisticUpdate] =
dag: ChainDAGRef): ForkedLightClientOptimisticUpdate =
if not dag.lcDataStore.serve:
return
return default(ForkedLightClientOptimisticUpdate)
result = some(dag.lcDataStore.cache.latest.toOptimistic)
let numParticipants = result.get.sync_aggregate.num_active_participants
let
optimisticUpdate = dag.lcDataStore.cache.latest.toOptimistic
numParticipants = withForkyOptimisticUpdate(optimisticUpdate):
when lcDataFork >= LightClientDataFork.Altair:
forkyOptimisticUpdate.sync_aggregate.num_active_participants
else:
0
if numParticipants < MIN_SYNC_COMMITTEE_PARTICIPANTS:
result.reset()
return default(ForkedLightClientOptimisticUpdate)
optimisticUpdate

View File

@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2018-2022 Status Research & Development GmbH
# Copyright (c) 2018-2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -617,7 +617,7 @@ proc processSignedContributionAndProof*(
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.0/specs/altair/light-client/sync-protocol.md#process_light_client_finality_update
proc processLightClientFinalityUpdate*(
self: var Eth2Processor, src: MsgSource,
finality_update: altair.LightClientFinalityUpdate
finality_update: ForkedLightClientFinalityUpdate
): Result[void, ValidationError] =
let
wallTime = self.getCurrentBeaconTime()
@ -628,7 +628,7 @@ proc processLightClientFinalityUpdate*(
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.0/specs/altair/light-client/sync-protocol.md#process_light_client_optimistic_update
proc processLightClientOptimisticUpdate*(
self: var Eth2Processor, src: MsgSource,
optimistic_update: altair.LightClientOptimisticUpdate
optimistic_update: ForkedLightClientOptimisticUpdate
): Result[void, ValidationError] =
let
wallTime = self.getCurrentBeaconTime()

View File

@ -1094,16 +1094,24 @@ proc validateContribution*(
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.0/specs/altair/light-client/p2p-interface.md#light_client_finality_update
proc validateLightClientFinalityUpdate*(
pool: var LightClientPool, dag: ChainDAGRef,
finality_update: altair.LightClientFinalityUpdate,
finality_update: ForkedLightClientFinalityUpdate,
wallTime: BeaconTime): Result[void, ValidationError] =
let finalized_slot = finality_update.finalized_header.slot
let finalized_slot = withForkyFinalityUpdate(finality_update):
when lcDataFork >= LightClientDataFork.Altair:
forkyFinalityUpdate.finalized_header.slot
else:
GENESIS_SLOT
if finalized_slot <= pool.latestForwardedFinalitySlot:
# [IGNORE] The `finalized_header.slot` is greater than that of all previously
# forwarded `finality_update`s
# [IGNORE] The `finalized_header.slot` is greater than that of all
# previously forwarded `finality_update`s
return errIgnore("LightClientFinalityUpdate: slot already forwarded")
let
signature_slot = finality_update.signature_slot
signature_slot = withForkyFinalityUpdate(finality_update):
when lcDataFork >= LightClientDataFork.Altair:
forkyFinalityUpdate.signature_slot
else:
GENESIS_SLOT
currentTime = wallTime + MAXIMUM_GOSSIP_CLOCK_DISPARITY
forwardTime = signature_slot.light_client_finality_update_time
if currentTime < forwardTime:
@ -1111,7 +1119,7 @@ proc validateLightClientFinalityUpdate*(
# `signature_slot` was given enough time to propagate through the network.
return errIgnore("LightClientFinalityUpdate: received too early")
if finality_update != dag.lcDataStore.cache.latest:
if not finality_update.matches(dag.lcDataStore.cache.latest):
# [IGNORE] The received `finality_update` matches the locally computed one
# exactly.
return errIgnore("LightClientFinalityUpdate: not matching local")
@ -1122,16 +1130,24 @@ proc validateLightClientFinalityUpdate*(
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.0/specs/altair/light-client/p2p-interface.md#light_client_optimistic_update
proc validateLightClientOptimisticUpdate*(
pool: var LightClientPool, dag: ChainDAGRef,
optimistic_update: altair.LightClientOptimisticUpdate,
optimistic_update: ForkedLightClientOptimisticUpdate,
wallTime: BeaconTime): Result[void, ValidationError] =
let attested_slot = optimistic_update.attested_header.slot
let attested_slot = withForkyOptimisticUpdate(optimistic_update):
when lcDataFork >= LightClientDataFork.Altair:
forkyOptimisticUpdate.attested_header.slot
else:
GENESIS_SLOT
if attested_slot <= pool.latestForwardedOptimisticSlot:
# The `attested_header.slot` is greater than that of all previously
# forwarded `optimistic_update`s
# [IGNORE] The `attested_header.slot` is greater than that of all
# previously forwarded `optimistic_update`s
return errIgnore("LightClientOptimisticUpdate: slot already forwarded")
let
signature_slot = optimistic_update.signature_slot
signature_slot = withForkyOptimisticUpdate(optimistic_update):
when lcDataFork >= LightClientDataFork.Altair:
forkyOptimisticUpdate.signature_slot
else:
GENESIS_SLOT
currentTime = wallTime + MAXIMUM_GOSSIP_CLOCK_DISPARITY
forwardTime = signature_slot.light_client_optimistic_update_time
if currentTime < forwardTime:

View File

@ -13,7 +13,6 @@ else:
import
stew/objects,
chronos, metrics,
../spec/datatypes/altair,
../spec/light_client_sync,
../consensus_object_pools/block_pools_types,
".."/[beacon_clock, sszdump],
@ -41,13 +40,13 @@ type
ValueObserver[V] =
proc(v: V) {.gcsafe, raises: [Defect].}
BootstrapObserver* =
ValueObserver[altair.LightClientBootstrap]
ValueObserver[ForkedLightClientBootstrap]
UpdateObserver* =
ValueObserver[altair.LightClientUpdate]
ValueObserver[ForkedLightClientUpdate]
FinalityUpdateObserver* =
ValueObserver[altair.LightClientFinalityUpdate]
ValueObserver[ForkedLightClientFinalityUpdate]
OptimisticUpdateObserver* =
ValueObserver[altair.LightClientOptimisticUpdate]
ValueObserver[ForkedLightClientOptimisticUpdate]
LightClientFinalizationMode* {.pure.} = enum
Strict
@ -120,7 +119,7 @@ type
lastDuplicateTick: BeaconTime # Moment when last duplicate update received
numDuplicatesSinceProgress: int # Number of duplicates since last progress
latestFinalityUpdate: altair.LightClientOptimisticUpdate
latestFinalityUpdate: ForkedLightClientOptimisticUpdate
const
# These constants have been chosen empirically and are not backed by spec
@ -173,13 +172,13 @@ proc new*(
proc dumpInvalidObject(
self: LightClientProcessor,
obj: SomeLightClientObject) =
obj: SomeForkyLightClientObject) =
if self.dumpEnabled:
dump(self.dumpDirInvalid, obj)
proc dumpObject[T](
self: LightClientProcessor,
obj: SomeLightClientObject,
obj: SomeForkyLightClientObject,
res: Result[T, VerifierError]) =
if self.dumpEnabled and res.isErr:
case res.error
@ -214,40 +213,47 @@ proc tryForceUpdate(
proc processObject(
self: var LightClientProcessor,
obj: SomeLightClientObject,
obj: SomeForkedLightClientObject,
wallTime: BeaconTime): Result[void, VerifierError] =
let
wallSlot = wallTime.slotOrZero()
store = self.store
res =
when obj is altair.LightClientBootstrap:
if store[].isSome:
err(VerifierError.Duplicate)
else:
let trustedBlockRoot = self.getTrustedBlockRoot()
if trustedBlockRoot.isNone:
res = withForkyObject(obj):
when lcDataFork >= LightClientDataFork.Altair:
when forkyObject is ForkyLightClientBootstrap:
if store[].isSome:
err(VerifierError.Duplicate)
else:
let trustedBlockRoot = self.getTrustedBlockRoot()
if trustedBlockRoot.isNone:
err(VerifierError.MissingParent)
else:
let initRes =
initialize_light_client_store(trustedBlockRoot.get, forkyObject)
if initRes.isErr:
err(initRes.error)
else:
store[] = some(initRes.get)
ok()
elif forkyObject is SomeForkyLightClientUpdate:
if store[].isNone:
err(VerifierError.MissingParent)
else:
let initRes =
initialize_light_client_store(trustedBlockRoot.get, obj)
if initRes.isErr:
err(initRes.error)
else:
store[] = some(initRes.get)
ok()
elif obj is SomeLightClientUpdate:
if store[].isNone:
err(VerifierError.MissingParent)
else:
store[].get.process_light_client_update(
obj, wallSlot, self.cfg, self.genesis_validators_root)
store[].get.process_light_client_update(
forkyObject, wallSlot, self.cfg, self.genesis_validators_root)
else:
err(VerifierError.Invalid)
self.dumpObject(obj, res)
withForkyObject(obj):
when lcDataFork >= LightClientDataFork.Altair:
self.dumpObject(forkyObject, res)
if res.isErr:
when obj is altair.LightClientUpdate:
when obj is ForkedLightClientUpdate:
const storeDataFork = typeof(store[].get).kind
if self.finalizationMode == LightClientFinalizationMode.Optimistic and
store[].isSome and store[].get.best_valid_update.isSome:
store[].isSome and store[].get.best_valid_update.isSome and
obj.kind > LightClientDataFork.None and obj.kind <= storeDataFork:
# `best_valid_update` gets set when no supermajority / finality proof
# is available. In that case, we will wait for a better update.
# If none is made available within reasonable time, the light client
@ -256,7 +262,9 @@ proc processObject(
of VerifierError.Duplicate:
if wallTime >= self.lastDuplicateTick + duplicateRateLimit:
if self.numDuplicatesSinceProgress < minForceUpdateDuplicates:
if obj.matches(store[].get.best_valid_update.get):
let upgradedObj = obj.migratingToDataFork(storeDataFork)
if upgradedObj.forky(storeDataFork).matches(
store[].get.best_valid_update.get):
self.lastDuplicateTick = wallTime
inc self.numDuplicatesSinceProgress
if self.numDuplicatesSinceProgress >= minForceUpdateDuplicates and
@ -269,7 +277,7 @@ proc processObject(
return res
when obj is altair.LightClientBootstrap | altair.LightClientUpdate:
when obj is ForkedLightClientBootstrap | ForkedLightClientUpdate:
if self.finalizationMode == LightClientFinalizationMode.Optimistic:
self.lastProgressTick = wallTime
self.lastDuplicateTick = wallTime + duplicateCountDelay
@ -278,7 +286,7 @@ proc processObject(
res
template withReportedProgress(
obj: SomeLightClientObject | Nothing, body: untyped): bool =
obj: SomeForkedLightClientObject | Nothing, body: untyped): bool =
block:
let
previousWasInitialized = store[].isSome
@ -331,16 +339,16 @@ template withReportedProgress(
if didProgress:
when obj is Nothing:
discard
elif obj is altair.LightClientBootstrap:
elif obj is ForkedLightClientBootstrap:
if self.bootstrapObserver != nil:
self.bootstrapObserver(obj)
elif obj is altair.LightClientUpdate:
elif obj is ForkedLightClientUpdate:
if self.updateObserver != nil:
self.updateObserver(obj)
elif obj is altair.LightClientFinalityUpdate:
elif obj is ForkedLightClientFinalityUpdate:
if self.finalityUpdateObserver != nil:
self.finalityUpdateObserver(obj)
elif obj is altair.LightClientOptimisticUpdate:
elif obj is ForkedLightClientOptimisticUpdate:
if self.optimisticUpdateObserver != nil:
self.optimisticUpdateObserver(obj)
else: raiseAssert "Unreachable"
@ -353,7 +361,7 @@ template withReportedProgress(body: untyped): bool =
proc storeObject*(
self: var LightClientProcessor,
src: MsgSource, wallTime: BeaconTime,
obj: SomeLightClientObject): Result[bool, VerifierError] =
obj: SomeForkedLightClientObject): Result[bool, VerifierError] =
## storeObject is the main entry point for unvalidated light client objects -
## all untrusted objects pass through here. When storing an object, we will
## update the `LightClientStore` accordingly
@ -372,13 +380,16 @@ proc storeObject*(
light_client_store_object_duration_seconds.observe(
storeObjectDur.toFloatSeconds())
let objSlot =
when obj is altair.LightClientBootstrap:
obj.header.slot
elif obj is SomeLightClientUpdateWithFinality:
obj.finalized_header.slot
let objSlot = withForkyObject(obj):
when lcDataFork >= LightClientDataFork.Altair:
when forkyObject is ForkyLightClientBootstrap:
forkyObject.header.slot
elif forkyObject is SomeForkyLightClientUpdateWithFinality:
forkyObject.finalized_header.slot
else:
forkyObject.attested_header.slot
else:
obj.attested_header.slot
GENESIS_SLOT
debug "LC object processed",
finalizedSlot = store[].get.finalized_header.slot,
optimisticSlot = store[].get.optimistic_header.slot,
@ -409,7 +420,7 @@ proc resetToFinalizedHeader*(
proc addObject*(
self: var LightClientProcessor,
src: MsgSource,
obj: SomeLightClientObject,
obj: SomeForkedLightClientObject,
resfut: Future[Result[void, VerifierError]] = nil) =
## Enqueue a Gossip-validated light client object for verification
# Backpressure:
@ -448,12 +459,16 @@ func toValidationError(
self: var LightClientProcessor,
r: Result[bool, VerifierError],
wallTime: BeaconTime,
obj: SomeLightClientObject): Result[void, ValidationError] =
obj: SomeForkedLightClientObject): Result[void, ValidationError] =
if r.isOk:
let didSignificantProgress = r.get
if didSignificantProgress:
let
signature_slot = obj.signature_slot
signature_slot = withForkyObject(obj):
when lcDataFork >= LightClientDataFork.Altair:
forkyObject.signature_slot
else:
GENESIS_SLOT
currentTime = wallTime + MAXIMUM_GOSSIP_CLOCK_DISPARITY
forwardTime = signature_slot.light_client_finality_update_time
if currentTime < forwardTime:
@ -466,11 +481,11 @@ func toValidationError(
return errIgnore(typeof(obj).name & ": received too early")
ok()
else:
when obj is altair.LightClientOptimisticUpdate:
when obj is ForkedLightClientOptimisticUpdate:
# [IGNORE] The `optimistic_update` either matches corresponding fields
# of the most recently forwarded `LightClientFinalityUpdate` (if any),
# or it advances the `optimistic_header` of the local `LightClientStore`
if obj == self.latestFinalityUpdate:
if obj.matches(self.latestFinalityUpdate):
return ok()
# [IGNORE] The `finality_update` advances the `finalized_header` of the
# local `LightClientStore`.
@ -493,7 +508,7 @@ func toValidationError(
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.0/specs/altair/light-client/sync-protocol.md#process_light_client_finality_update
proc processLightClientFinalityUpdate*(
self: var LightClientProcessor, src: MsgSource,
finality_update: altair.LightClientFinalityUpdate
finality_update: ForkedLightClientFinalityUpdate
): Result[void, ValidationError] =
let
wallTime = self.getBeaconTime()
@ -506,14 +521,24 @@ proc processLightClientFinalityUpdate*(
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.0/specs/altair/light-client/sync-protocol.md#process_light_client_finality_update
proc processLightClientOptimisticUpdate*(
self: var LightClientProcessor, src: MsgSource,
optimistic_update: altair.LightClientOptimisticUpdate
optimistic_update: ForkedLightClientOptimisticUpdate
): Result[void, ValidationError] =
let
wallTime = self.getBeaconTime()
r = self.storeObject(src, wallTime, optimistic_update)
v = self.toValidationError(r, wallTime, optimistic_update)
if v.isOk:
let latestFinalitySlot = self.latestFinalityUpdate.attested_header.slot
if optimistic_update.attested_header.slot >= latestFinalitySlot:
let
latestFinalitySlot = withForkyOptimisticUpdate(self.latestFinalityUpdate):
when lcDataFork >= LightClientDataFork.Altair:
forkyOptimisticUpdate.attested_header.slot
else:
GENESIS_SLOT
attestedSlot = withForkyOptimisticUpdate(optimistic_update):
when lcDataFork >= LightClientDataFork.Altair:
forkyOptimisticUpdate.attested_header.slot
else:
GENESIS_SLOT
if attestedSlot >= latestFinalitySlot:
self.latestFinalityUpdate.reset() # Only forward once
v

View File

@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2022 Status Research & Development GmbH
# Copyright (c) 2022-2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -32,13 +32,13 @@ type
LightClientValueObserver[V] =
proc(lightClient: LightClient, v: V) {.gcsafe, raises: [Defect].}
LightClientBootstrapObserver* =
LightClientValueObserver[altair.LightClientBootstrap]
LightClientValueObserver[ForkedLightClientBootstrap]
LightClientUpdateObserver* =
LightClientValueObserver[altair.LightClientUpdate]
LightClientValueObserver[ForkedLightClientUpdate]
LightClientFinalityUpdateObserver* =
LightClientValueObserver[altair.LightClientFinalityUpdate]
LightClientValueObserver[ForkedLightClientFinalityUpdate]
LightClientOptimisticUpdateObserver* =
LightClientValueObserver[altair.LightClientOptimisticUpdate]
LightClientValueObserver[ForkedLightClientOptimisticUpdate]
LightClient* = ref object
network: Eth2Node
@ -109,19 +109,19 @@ proc createLightClient(
lightClient.onOptimisticHeader(
lightClient, lightClient.optimisticHeader.get)
proc bootstrapObserver(obj: altair.LightClientBootstrap) =
proc bootstrapObserver(obj: ForkedLightClientBootstrap) =
if lightClient.bootstrapObserver != nil:
lightClient.bootstrapObserver(lightClient, obj)
proc updateObserver(obj: altair.LightClientUpdate) =
proc updateObserver(obj: ForkedLightClientUpdate) =
if lightClient.updateObserver != nil:
lightClient.updateObserver(lightClient, obj)
proc finalityObserver(obj: altair.LightClientFinalityUpdate) =
proc finalityObserver(obj: ForkedLightClientFinalityUpdate) =
if lightClient.finalityUpdateObserver != nil:
lightClient.finalityUpdateObserver(lightClient, obj)
proc optimisticObserver(obj: altair.LightClientOptimisticUpdate) =
proc optimisticObserver(obj: ForkedLightClientOptimisticUpdate) =
if lightClient.optimisticUpdateObserver != nil:
lightClient.optimisticUpdateObserver(lightClient, obj)
@ -132,18 +132,18 @@ proc createLightClient(
onStoreInitialized, onFinalizedHeader, onOptimisticHeader,
bootstrapObserver, updateObserver, finalityObserver, optimisticObserver)
proc lightClientVerifier(obj: SomeLightClientObject):
proc lightClientVerifier(obj: SomeForkedLightClientObject):
Future[Result[void, VerifierError]] =
let resfut = newFuture[Result[void, VerifierError]]("lightClientVerifier")
lightClient.processor[].addObject(MsgSource.gossip, obj, resfut)
resfut
proc bootstrapVerifier(obj: altair.LightClientBootstrap): auto =
proc bootstrapVerifier(obj: ForkedLightClientBootstrap): auto =
lightClientVerifier(obj)
proc updateVerifier(obj: altair.LightClientUpdate): auto =
proc updateVerifier(obj: ForkedLightClientUpdate): auto =
lightClientVerifier(obj)
proc finalityVerifier(obj: altair.LightClientFinalityUpdate): auto =
proc finalityVerifier(obj: ForkedLightClientFinalityUpdate): auto =
lightClientVerifier(obj)
proc optimisticVerifier(obj: altair.LightClientOptimisticUpdate): auto =
proc optimisticVerifier(obj: ForkedLightClientOptimisticUpdate): auto =
lightClientVerifier(obj)
func isLightClientStoreInitialized(): bool =
@ -235,31 +235,31 @@ declareCounter beacon_light_client_optimistic_updates_dropped,
"Number of invalid LC optimistic updates dropped by this node", labels = ["reason"]
template logReceived(
msg: altair.LightClientFinalityUpdate) =
msg: ForkyLightClientFinalityUpdate) =
debug "LC finality update received", finality_update = msg
template logValidated(
msg: altair.LightClientFinalityUpdate) =
msg: ForkyLightClientFinalityUpdate) =
trace "LC finality update validated", finality_update = msg
beacon_light_client_finality_updates_received.inc()
proc logDropped(
msg: altair.LightClientFinalityUpdate, es: varargs[ValidationError]) =
msg: ForkyLightClientFinalityUpdate, es: varargs[ValidationError]) =
for e in es:
debug "Dropping LC finality update", finality_update = msg, error = e
beacon_light_client_finality_updates_dropped.inc(1, [$es[0][0]])
template logReceived(
msg: altair.LightClientOptimisticUpdate) =
msg: ForkyLightClientOptimisticUpdate) =
debug "LC optimistic update received", optimistic_update = msg
template logValidated(
msg: altair.LightClientOptimisticUpdate) =
msg: ForkyLightClientOptimisticUpdate) =
trace "LC optimistic update validated", optimistic_update = msg
beacon_light_client_optimistic_updates_received.inc()
proc logDropped(
msg: altair.LightClientOptimisticUpdate, es: varargs[ValidationError]) =
msg: ForkyLightClientOptimisticUpdate, es: varargs[ValidationError]) =
for e in es:
debug "Dropping LC optimistic update", optimistic_update = msg, error = e
beacon_light_client_optimistic_updates_dropped.inc(1, [$es[0][0]])
@ -272,10 +272,28 @@ proc installMessageValidators*(
template getLocalWallPeriod(): auto =
lightClient.getBeaconTime().slotOrZero().sync_committee_period
template validate[T: SomeLightClientObject](
msg: T, validatorProcName: untyped): ValidationResult =
template validate[T: SomeForkyLightClientObject](
msg: T,
contextFork: BeaconStateFork,
validatorProcName: untyped): ValidationResult =
msg.logReceived()
const invalidContextForkError =
(ValidationResult.Reject, cstring "Invalid context fork")
if contextFork != lightClient.cfg.stateForkAtEpoch(msg.contextEpoch):
msg.logDropped(invalidContextForkError)
return ValidationResult.Reject
var obj {.noinit.}: T.forked
if contextFork >= BeaconStateFork.Altair:
const lcDataFork = LightClientDataFork.Altair
obj = T.forked(kind: lcDataFork)
template forkyObj: untyped = obj.forky(lcDataFork)
forkyObj = msg
else:
msg.logDropped(invalidContextForkError)
return ValidationResult.Reject
var
ignoreErrors {.noinit.}: array[2, ValidationError]
numIgnoreErrors = 0
@ -283,7 +301,7 @@ proc installMessageValidators*(
let res1 =
if eth2Processor != nil:
let
v = eth2Processor[].`validatorProcName`(MsgSource.gossip, msg)
v = eth2Processor[].`validatorProcName`(MsgSource.gossip, obj)
res = v.toValidationResult()
if res == ValidationResult.Reject:
msg.logDropped(v.error)
@ -298,7 +316,7 @@ proc installMessageValidators*(
let res2 =
if lightClient.manager.isGossipSupported(getLocalWallPeriod()):
let
v = lightClient.processor[].`validatorProcName`(MsgSource.gossip, msg)
v = lightClient.processor[].`validatorProcName`(MsgSource.gossip, obj)
res = v.toValidationResult()
if res == ValidationResult.Reject:
msg.logDropped(v.error)
@ -323,16 +341,18 @@ proc installMessageValidators*(
ValidationResult.Ignore
let forkDigests = lightClient.forkDigests
for digest in [forkDigests.altair, forkDigests.bellatrix]:
for stateFork in BeaconStateFork.Altair .. BeaconStateFork.Bellatrix:
let digest = forkDigests[].atStateFork(stateFork)
lightClient.network.addValidator(
getLightClientFinalityUpdateTopic(digest),
proc(msg: altair.LightClientFinalityUpdate): ValidationResult =
validate(msg, processLightClientFinalityUpdate))
validate(msg, stateFork, processLightClientFinalityUpdate))
lightClient.network.addValidator(
getLightClientOptimisticUpdateTopic(digest),
proc(msg: altair.LightClientOptimisticUpdate): ValidationResult =
validate(msg, processLightClientOptimisticUpdate))
validate(msg, stateFork, processLightClientOptimisticUpdate))
proc updateGossipStatus*(
lightClient: LightClient, slot: Slot, dagIsBehind = default(Option[bool])) =

View File

@ -978,6 +978,12 @@ template write*[M; maxLen: static Limit](
mixin sendResponseChunk
sendResponseChunk(UntypedResponse(r), val, contextBytes)
template writeSSZ*[M; maxLen: static Limit](
r: MultipleChunksResponse[M, maxLen], val: auto,
contextBytes: openArray[byte] = []): untyped =
mixin sendResponseChunk
sendResponseChunk(UntypedResponse(r), val, contextBytes)
template writeBytesSZ*(
r: MultipleChunksResponse, uncompressedLen: uint64,
bytes: openArray[byte], contextBytes: openArray[byte]): untyped =
@ -990,6 +996,13 @@ template send*[M](
doAssert UntypedResponse(r).writtenChunks == 0
sendResponseChunk(UntypedResponse(r), val, contextBytes)
template sendSSZ*[M](
r: SingleChunkResponse[M], val: auto,
contextBytes: openArray[byte] = []): untyped =
mixin sendResponseChunk
doAssert UntypedResponse(r).writtenChunks == 0
sendResponseChunk(UntypedResponse(r), val, contextBytes)
proc performProtocolHandshakes(peer: Peer, incoming: bool) {.async.} =
# Loop down serially because it's easier to reason about the connection state
# when there are fewer async races, specially during setup
@ -2665,15 +2678,15 @@ proc broadcastSignedContributionAndProof*(
node.broadcast(topic, msg)
proc broadcastLightClientFinalityUpdate*(
node: Eth2Node, msg: altair.LightClientFinalityUpdate):
node: Eth2Node, msg: ForkyLightClientFinalityUpdate):
Future[SendResult] =
let topic = getLightClientFinalityUpdateTopic(
node.forkDigestAtEpoch(msg.attested_header.slot.epoch))
node.forkDigestAtEpoch(msg.contextEpoch))
node.broadcast(topic, msg)
proc broadcastLightClientOptimisticUpdate*(
node: Eth2Node, msg: altair.LightClientOptimisticUpdate):
node: Eth2Node, msg: ForkyLightClientOptimisticUpdate):
Future[SendResult] =
let topic = getLightClientOptimisticUpdateTopic(
node.forkDigestAtEpoch(msg.attested_header.slot.epoch))
node.forkDigestAtEpoch(msg.contextEpoch))
node.broadcast(topic, msg)

View File

@ -149,10 +149,29 @@ proc loadChainDag(
shouldEnableTestFeatures: bool): ChainDAGRef =
info "Loading block DAG from database", path = config.databaseDir
proc onLightClientFinalityUpdate(data: altair.LightClientFinalityUpdate) =
eventBus.finUpdateQueue.emit(data)
proc onLightClientOptimisticUpdate(data: altair.LightClientOptimisticUpdate) =
eventBus.optUpdateQueue.emit(data)
var dag: ChainDAGRef
proc onLightClientFinalityUpdate(data: ForkedLightClientFinalityUpdate) =
if dag == nil: return
withForkyFinalityUpdate(data):
when lcDataFork >= LightClientDataFork.Altair:
let contextFork =
dag.cfg.stateForkAtEpoch(forkyFinalityUpdate.contextEpoch)
eventBus.finUpdateQueue.emit(
RestVersioned[ForkedLightClientFinalityUpdate](
data: data,
jsonVersion: contextFork,
sszContext: dag.forkDigests[].atStateFork(contextFork)))
proc onLightClientOptimisticUpdate(data: ForkedLightClientOptimisticUpdate) =
if dag == nil: return
withForkyOptimisticUpdate(data):
when lcDataFork >= LightClientDataFork.Altair:
let contextFork =
dag.cfg.stateForkAtEpoch(forkyOptimisticUpdate.contextEpoch)
eventBus.optUpdateQueue.emit(
RestVersioned[ForkedLightClientOptimisticUpdate](
data: data,
jsonVersion: contextFork,
sszContext: dag.forkDigests[].atStateFork(contextFork)))
let
extraFlags =
@ -167,19 +186,19 @@ proc loadChainDag(
onLightClientOptimisticUpdateCb =
if config.lightClientDataServe: onLightClientOptimisticUpdate
else: nil
dag = ChainDAGRef.init(
cfg, db, validatorMonitor, extraFlags + chainDagFlags, config.eraDir,
vanityLogs = getVanityLogs(detectTTY(config.logStdout)),
lcDataConfig = LightClientDataConfig(
serve: config.lightClientDataServe,
importMode: config.lightClientDataImportMode,
maxPeriods: config.lightClientDataMaxPeriods,
onLightClientFinalityUpdate: onLightClientFinalityUpdateCb,
onLightClientOptimisticUpdate: onLightClientOptimisticUpdateCb))
databaseGenesisValidatorsRoot =
getStateField(dag.headState, genesis_validators_root)
dag = ChainDAGRef.init(
cfg, db, validatorMonitor, extraFlags + chainDagFlags, config.eraDir,
vanityLogs = getVanityLogs(detectTTY(config.logStdout)),
lcDataConfig = LightClientDataConfig(
serve: config.lightClientDataServe,
importMode: config.lightClientDataImportMode,
maxPeriods: config.lightClientDataMaxPeriods,
onLightClientFinalityUpdate: onLightClientFinalityUpdateCb,
onLightClientOptimisticUpdate: onLightClientOptimisticUpdateCb))
if networkGenesisValidatorsRoot.isSome:
let databaseGenesisValidatorsRoot =
getStateField(dag.headState, genesis_validators_root)
if networkGenesisValidatorsRoot.get != databaseGenesisValidatorsRoot:
fatal "The specified --data-dir contains data for a different network",
networkGenesisValidatorsRoot = networkGenesisValidatorsRoot.get,
@ -418,8 +437,10 @@ proc init*(T: type BeaconNode,
blocksQueue: newAsyncEventQueue[EventBeaconBlockObject](),
headQueue: newAsyncEventQueue[HeadChangeInfoObject](),
reorgQueue: newAsyncEventQueue[ReorgInfoObject](),
finUpdateQueue: newAsyncEventQueue[altair.LightClientFinalityUpdate](),
optUpdateQueue: newAsyncEventQueue[altair.LightClientOptimisticUpdate](),
finUpdateQueue: newAsyncEventQueue[
RestVersioned[ForkedLightClientFinalityUpdate]](),
optUpdateQueue: newAsyncEventQueue[
RestVersioned[ForkedLightClientOptimisticUpdate]](),
attestQueue: newAsyncEventQueue[Attestation](),
contribQueue: newAsyncEventQueue[SignedContributionAndProof](),
exitQueue: newAsyncEventQueue[SignedVoluntaryExit](),

View File

@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2021-2022 Status Research & Development GmbH
# Copyright (c) 2021-2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -37,20 +37,21 @@ proc installLightClientApiHandlers*(router: var RestRouter, node: BeaconNode) =
block_root.get()
let bootstrap = node.dag.getLightClientBootstrap(vroot)
if bootstrap.isNone:
return RestApiResponse.jsonError(Http404, LCBootstrapUnavailable)
let
contextEpoch = bootstrap.get.contextEpoch
contextFork = node.dag.cfg.stateForkAtEpoch(contextEpoch)
return
if contentType == sszMediaType:
let headers = [("eth-consensus-version", contextFork.toString())]
RestApiResponse.sszResponse(bootstrap.get, headers)
elif contentType == jsonMediaType:
RestApiResponse.jsonResponseWVersion(bootstrap.get, contextFork)
withForkyBootstrap(bootstrap):
when lcDataFork >= LightClientDataFork.Altair:
let
contextEpoch = forkyBootstrap.contextEpoch
contextFork = node.dag.cfg.stateForkAtEpoch(contextEpoch)
return
if contentType == sszMediaType:
let headers = [("eth-consensus-version", contextFork.toString())]
RestApiResponse.sszResponse(forkyBootstrap, headers)
elif contentType == jsonMediaType:
RestApiResponse.jsonResponseWVersion(forkyBootstrap, contextFork)
else:
RestApiResponse.jsonError(Http500, InvalidAcceptError)
else:
RestApiResponse.jsonError(Http500, InvalidAcceptError)
return RestApiResponse.jsonError(Http404, LCBootstrapUnavailable)
# https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Beacon/getLightClientUpdatesByRange
router.api(MethodGet,
@ -93,23 +94,27 @@ proc installLightClientApiHandlers*(router: var RestRouter, node: BeaconNode) =
numPeriods = min(vcount, maxSupportedCount)
onePastPeriod = vstart + numPeriods
var updates = newSeqOfCap[RestVersioned[LightClientUpdate]](numPeriods)
var updates =
newSeqOfCap[RestVersioned[ForkedLightClientUpdate]](numPeriods)
for period in vstart..<onePastPeriod:
let update = node.dag.getLightClientUpdateForPeriod(period)
if update.isSome:
let
contextEpoch = update.get.contextEpoch
contextFork = node.dag.cfg.stateForkAtEpoch(contextEpoch)
updates.add RestVersioned[LightClientUpdate](
data: update.get,
jsonVersion: contextFork,
sszContext: node.dag.forkDigests[].atStateFork(contextFork))
let
update = node.dag.getLightClientUpdateForPeriod(period)
contextEpoch = withForkyUpdate(update):
when lcDataFork >= LightClientDataFork.Altair:
forkyUpdate.contextEpoch
else:
continue
contextFork = node.dag.cfg.stateForkAtEpoch(contextEpoch)
updates.add RestVersioned[ForkedLightClientUpdate](
data: update,
jsonVersion: contextFork,
sszContext: node.dag.forkDigests[].atStateFork(contextFork))
return
if contentType == sszMediaType:
RestApiResponse.sszResponseVersionedList(updates)
RestApiResponse.sszResponseVersioned(updates)
elif contentType == jsonMediaType:
RestApiResponse.jsonResponseVersionedList(updates)
RestApiResponse.jsonResponseVersioned(updates)
else:
RestApiResponse.jsonError(Http500, InvalidAcceptError)
@ -127,20 +132,22 @@ proc installLightClientApiHandlers*(router: var RestRouter, node: BeaconNode) =
res.get()
let finality_update = node.dag.getLightClientFinalityUpdate()
if finality_update.isNone:
return RestApiResponse.jsonError(Http404, LCFinUpdateUnavailable)
let
contextEpoch = finality_update.get.contextEpoch
contextFork = node.dag.cfg.stateForkAtEpoch(contextEpoch)
return
if contentType == sszMediaType:
let headers = [("eth-consensus-version", contextFork.toString())]
RestApiResponse.sszResponse(finality_update.get, headers)
elif contentType == jsonMediaType:
RestApiResponse.jsonResponseWVersion(finality_update.get, contextFork)
withForkyFinalityUpdate(finality_update):
when lcDataFork >= LightClientDataFork.Altair:
let
contextEpoch = forkyFinalityUpdate.contextEpoch
contextFork = node.dag.cfg.stateForkAtEpoch(contextEpoch)
return
if contentType == sszMediaType:
let headers = [("eth-consensus-version", contextFork.toString())]
RestApiResponse.sszResponse(forkyFinalityUpdate, headers)
elif contentType == jsonMediaType:
RestApiResponse.jsonResponseWVersion(
forkyFinalityUpdate, contextFork)
else:
RestApiResponse.jsonError(Http500, InvalidAcceptError)
else:
RestApiResponse.jsonError(Http500, InvalidAcceptError)
return RestApiResponse.jsonError(Http404, LCFinUpdateUnavailable)
# https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Beacon/getLightClientOptimisticUpdate
router.api(MethodGet,
@ -156,17 +163,19 @@ proc installLightClientApiHandlers*(router: var RestRouter, node: BeaconNode) =
res.get()
let optimistic_update = node.dag.getLightClientOptimisticUpdate()
if optimistic_update.isNone:
return RestApiResponse.jsonError(Http404, LCOptUpdateUnavailable)
let
contextEpoch = optimistic_update.get.contextEpoch
contextFork = node.dag.cfg.stateForkAtEpoch(contextEpoch)
return
if contentType == sszMediaType:
let headers = [("eth-consensus-version", contextFork.toString())]
RestApiResponse.sszResponse(optimistic_update.get, headers)
elif contentType == jsonMediaType:
RestApiResponse.jsonResponseWVersion(optimistic_update.get, contextFork)
withForkyOptimisticUpdate(optimistic_update):
when lcDataFork >= LightClientDataFork.Altair:
let
contextEpoch = forkyOptimisticUpdate.contextEpoch
contextFork = node.dag.cfg.stateForkAtEpoch(contextEpoch)
return
if contentType == sszMediaType:
let headers = [("eth-consensus-version", contextFork.toString())]
RestApiResponse.sszResponse(forkyOptimisticUpdate, headers)
elif contentType == jsonMediaType:
RestApiResponse.jsonResponseWVersion(
forkyOptimisticUpdate, contextFork)
else:
RestApiResponse.jsonError(Http500, InvalidAcceptError)
else:
RestApiResponse.jsonError(Http500, InvalidAcceptError)
return RestApiResponse.jsonError(Http404, LCOptUpdateUnavailable)

View File

@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2018-2022 Status Research & Development GmbH
# Copyright (c) 2018-2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -137,6 +137,11 @@ type
GetPhase0StateSszResponse |
GetPhase0BlockSszResponse
RestVersioned*[T] = object
data*: T
jsonVersion*: BeaconStateFork
sszContext*: ForkDigest
when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
else:
@ -159,6 +164,27 @@ proc prepareJsonResponse*(t: typedesc[RestApiResponse], d: auto): seq[byte] =
default
res
proc prepareJsonStringResponse*[T: SomeForkedLightClientObject](
t: typedesc[RestApiResponse], d: RestVersioned[T]): string =
let res =
block:
var default: string
try:
var stream = memoryOutput()
var writer = JsonWriter[RestJson].init(stream)
withForkyObject(d.data):
when lcDataFork >= LightClientDataFork.Altair:
writer.beginRecord()
writer.writeField("version", d.jsonVersion.toString())
writer.writeField("data", forkyObject)
writer.endRecord()
stream.getOutput(string)
except SerializationError:
default
except IOError:
default
res
proc prepareJsonStringResponse*(t: typedesc[RestApiResponse], d: auto): string =
let res =
block:
@ -304,14 +330,9 @@ proc jsonResponseWVersion*(t: typedesc[RestApiResponse], data: auto,
default
RestApiResponse.response(res, Http200, "application/json", headers = headers)
type RestVersioned*[T] = object
data*: T
jsonVersion*: BeaconStateFork
sszContext*: ForkDigest
proc jsonResponseVersionedList*[T](t: typedesc[RestApiResponse],
entries: openArray[RestVersioned[T]]
): RestApiResponse =
proc jsonResponseVersioned*[T: SomeForkedLightClientObject](
t: typedesc[RestApiResponse],
entries: openArray[RestVersioned[T]]): RestApiResponse =
let res =
block:
var default: seq[byte]
@ -319,10 +340,12 @@ proc jsonResponseVersionedList*[T](t: typedesc[RestApiResponse],
var stream = memoryOutput()
var writer = JsonWriter[RestJson].init(stream)
for e in writer.stepwiseArrayCreation(entries):
writer.beginRecord()
writer.writeField("version", e.jsonVersion.toString())
writer.writeField("data", e.data)
writer.endRecord()
withForkyObject(e.data):
when lcDataFork >= LightClientDataFork.Altair:
writer.beginRecord()
writer.writeField("version", e.jsonVersion.toString())
writer.writeField("data", forkyObject)
writer.endRecord()
stream.getOutput(seq[byte])
except SerializationError:
default
@ -466,21 +489,23 @@ proc jsonErrorList*(t: typedesc[RestApiResponse],
default
RestApiResponse.error(status, data, "application/json")
proc sszResponseVersionedList*[T](t: typedesc[RestApiResponse],
entries: openArray[RestVersioned[T]]
): RestApiResponse =
proc sszResponseVersioned*[T: SomeForkedLightClientObject](
t: typedesc[RestApiResponse],
entries: openArray[RestVersioned[T]]): RestApiResponse =
let res =
block:
var default: seq[byte]
try:
var stream = memoryOutput()
for e in entries:
var cursor = stream.delayFixedSizeWrite(sizeof(uint64))
let initPos = stream.pos
stream.write e.sszContext.data
var writer = SszWriter.init(stream)
writer.writeValue e.data
cursor.finalWrite (stream.pos - initPos).uint64.toBytesLE()
withForkyUpdate(e.data):
when lcDataFork >= LightClientDataFork.Altair:
var cursor = stream.delayFixedSizeWrite(sizeof(uint64))
let initPos = stream.pos
stream.write e.sszContext.data
var writer = SszWriter.init(stream)
writer.writeValue forkyUpdate
cursor.finalWrite (stream.pos - initPos).uint64.toBytesLE()
stream.getOutput(seq[byte])
except SerializationError:
default

View File

@ -14,7 +14,9 @@ import
stew/assign2,
chronicles,
../extras,
"."/[block_id, eth2_merkleization, eth2_ssz_serialization, presets],
"."/[
block_id, eth2_merkleization, eth2_ssz_serialization,
forks_light_client, presets],
./datatypes/[phase0, altair, bellatrix, capella, eip4844],
./mev/bellatrix_mev
@ -22,7 +24,7 @@ import
# it sequentially
export
extras, block_id, phase0, altair, bellatrix, eth2_merkleization,
eth2_ssz_serialization, presets, bellatrix_mev
eth2_ssz_serialization, forks_light_client, presets, bellatrix_mev
# This file contains helpers for dealing with forks - we have two ways we can
# deal with forks:

View File

@ -0,0 +1,309 @@
# beacon_chain
# Copyright (c) 2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
else:
{.push raises: [].}
import
./datatypes/[phase0, altair]
type
LightClientDataFork* {.pure.} = enum # Append only, used in DB data!
None = 0, # only use non-0 in DB to detect accidentally uninitialized data
Altair = 1
ForkyLightClientHeader* =
BeaconBlockHeader
ForkyLightClientBootstrap* =
altair.LightClientBootstrap
ForkyLightClientUpdate* =
altair.LightClientUpdate
ForkyLightClientFinalityUpdate* =
altair.LightClientFinalityUpdate
ForkyLightClientOptimisticUpdate* =
altair.LightClientOptimisticUpdate
SomeForkyLightClientUpdateWithSyncCommittee* =
ForkyLightClientUpdate
SomeForkyLightClientUpdateWithFinality* =
ForkyLightClientUpdate |
ForkyLightClientFinalityUpdate
SomeForkyLightClientUpdate* =
ForkyLightClientUpdate |
ForkyLightClientFinalityUpdate |
ForkyLightClientOptimisticUpdate
SomeForkyLightClientObject* =
ForkyLightClientBootstrap |
SomeForkyLightClientUpdate
ForkedLightClientBootstrap* = object
case kind*: LightClientDataFork
of LightClientDataFork.None:
discard
of LightClientDataFork.Altair:
altairData*: altair.LightClientBootstrap
ForkedLightClientUpdate* = object
case kind*: LightClientDataFork
of LightClientDataFork.None:
discard
of LightClientDataFork.Altair:
altairData*: altair.LightClientUpdate
ForkedLightClientFinalityUpdate* = object
case kind*: LightClientDataFork
of LightClientDataFork.None:
discard
of LightClientDataFork.Altair:
altairData*: altair.LightClientFinalityUpdate
ForkedLightClientOptimisticUpdate* = object
case kind*: LightClientDataFork
of LightClientDataFork.None:
discard
of LightClientDataFork.Altair:
altairData*: altair.LightClientOptimisticUpdate
SomeForkedLightClientUpdate* =
ForkedLightClientUpdate |
ForkedLightClientFinalityUpdate |
ForkedLightClientOptimisticUpdate
SomeForkedLightClientObject* =
ForkedLightClientBootstrap |
SomeForkedLightClientUpdate
func lcDataForkAtEpoch*(
cfg: RuntimeConfig, epoch: Epoch): LightClientDataFork =
if epoch >= cfg.ALTAIR_FORK_EPOCH:
LightClientDataFork.Altair
else:
LightClientDataFork.None
template kind*(x: typedesc[altair.LightClientStore]): LightClientDataFork =
LightClientDataFork.Altair
template header*(kind: static LightClientDataFork): auto =
when kind >= LightClientDataFork.Altair:
typedesc[BeaconBlockHeader]
else:
static: raiseAssert "Unreachable"
template forky*(
x: typedesc[ForkedLightClientBootstrap],
kind: static LightClientDataFork): auto =
when kind >= LightClientDataFork.Altair:
typedesc[altair.LightClientBootstrap]
else:
static: raiseAssert "Unreachable"
template forky*(
x: typedesc[ForkedLightClientUpdate],
kind: static LightClientDataFork): auto =
when kind >= LightClientDataFork.Altair:
typedesc[altair.LightClientUpdate]
else:
static: raiseAssert "Unreachable"
template forky*(
x: typedesc[ForkedLightClientFinalityUpdate],
kind: static LightClientDataFork): auto =
when kind >= LightClientDataFork.Altair:
typedesc[altair.LightClientFinalityUpdate]
else:
static: raiseAssert "Unreachable"
template forky*(
x: typedesc[ForkedLightClientOptimisticUpdate],
kind: static LightClientDataFork): auto =
when kind >= LightClientDataFork.Altair:
typedesc[altair.LightClientOptimisticUpdate]
else:
static: raiseAssert "Unreachable"
template forked*(x: typedesc[ForkyLightClientBootstrap]): auto =
typedesc[ForkedLightClientBootstrap]
template forked*(x: typedesc[ForkyLightClientUpdate]): auto =
typedesc[ForkedLightClientUpdate]
template forked*(x: typedesc[ForkyLightClientFinalityUpdate]): auto =
typedesc[ForkedLightClientFinalityUpdate]
template forked*(x: typedesc[ForkyLightClientOptimisticUpdate]): auto =
typedesc[ForkedLightClientOptimisticUpdate]
template withForkyBootstrap*(
x: ForkedLightClientBootstrap, body: untyped): untyped =
case x.kind
of LightClientDataFork.Altair:
const lcDataFork {.inject.} = LightClientDataFork.Altair
template forkyBootstrap: untyped {.inject.} = x.altairData
body
of LightClientDataFork.None:
const lcDataFork {.inject.} = LightClientDataFork.None
body
template withForkyUpdate*(
x: ForkedLightClientUpdate, body: untyped): untyped =
case x.kind
of LightClientDataFork.Altair:
const lcDataFork {.inject.} = LightClientDataFork.Altair
template forkyUpdate: untyped {.inject.} = x.altairData
body
of LightClientDataFork.None:
const lcDataFork {.inject.} = LightClientDataFork.None
body
template withForkyFinalityUpdate*(
x: ForkedLightClientFinalityUpdate, body: untyped): untyped =
case x.kind
of LightClientDataFork.Altair:
const lcDataFork {.inject.} = LightClientDataFork.Altair
template forkyFinalityUpdate: untyped {.inject.} = x.altairData
body
of LightClientDataFork.None:
const lcDataFork {.inject.} = LightClientDataFork.None
body
template withForkyOptimisticUpdate*(
x: ForkedLightClientOptimisticUpdate, body: untyped): untyped =
case x.kind
of LightClientDataFork.Altair:
const lcDataFork {.inject.} = LightClientDataFork.Altair
template forkyOptimisticUpdate: untyped {.inject.} = x.altairData
body
of LightClientDataFork.None:
const lcDataFork {.inject.} = LightClientDataFork.None
body
template withForkyObject*(
x: SomeForkedLightClientObject, body: untyped): untyped =
case x.kind
of LightClientDataFork.Altair:
const lcDataFork {.inject.} = LightClientDataFork.Altair
template forkyObject: untyped {.inject.} = x.altairData
body
of LightClientDataFork.None:
const lcDataFork {.inject.} = LightClientDataFork.None
body
template toOptimistic*(
update: SomeForkedLightClientUpdate): ForkedLightClientOptimisticUpdate =
when update is ForkyLightClientOptimisticUpdate:
update
else:
withForkyObject(update):
when lcDataFork >= LightClientDataFork.Altair:
ForkedLightClientOptimisticUpdate(
kind: lcDataFork,
altairData: forkyObject.toOptimistic())
else:
default(ForkedLightClientOptimisticUpdate)
func matches*[A, B: SomeForkedLightClientUpdate](a: A, b: B): bool =
if a.kind != b.kind:
return false
withForkyObject(a):
when lcDataFork >= LightClientDataFork.Altair:
forkyObject.matches(b.forky(lcDataFork))
else:
true
template forky*(
x: SomeForkedLightClientObject, kind: static LightClientDataFork): untyped =
when kind == LightClientDataFork.Altair:
x.altairData
else:
discard
func migrateToDataFork*(
x: var ForkedLightClientBootstrap,
newKind: static LightClientDataFork) =
if newKind == x.kind:
# Already at correct kind
discard
elif newKind < x.kind:
# Downgrade not supported, re-initialize
x = ForkedLightClientBootstrap(kind: newKind)
else:
# Upgrade to Altair
when newKind >= LightClientDataFork.Altair:
if x.kind < LightClientDataFork.Altair:
x = ForkedLightClientBootstrap(
kind: LightClientDataFork.Altair)
doAssert x.kind == newKind
func migrateToDataFork*(
x: var ForkedLightClientUpdate,
newKind: static LightClientDataFork) =
if newKind == x.kind:
# Already at correct kind
discard
elif newKind < x.kind:
# Downgrade not supported, re-initialize
x = ForkedLightClientUpdate(kind: newKind)
else:
# Upgrade to Altair
when newKind >= LightClientDataFork.Altair:
if x.kind < LightClientDataFork.Altair:
x = ForkedLightClientUpdate(
kind: LightClientDataFork.Altair)
doAssert x.kind == newKind
func migrateToDataFork*(
x: var ForkedLightClientFinalityUpdate,
newKind: static LightClientDataFork) =
if newKind == x.kind:
# Already at correct kind
discard
elif newKind < x.kind:
# Downgrade not supported, re-initialize
x = ForkedLightClientFinalityUpdate(kind: newKind)
else:
# Upgrade to Altair
when newKind >= LightClientDataFork.Altair:
if x.kind < LightClientDataFork.Altair:
x = ForkedLightClientFinalityUpdate(
kind: LightClientDataFork.Altair)
doAssert x.kind == newKind
func migrateToDataFork*(
x: var ForkedLightClientOptimisticUpdate,
newKind: static LightClientDataFork) =
if newKind == x.kind:
# Already at correct kind
discard
elif newKind < x.kind:
# Downgrade not supported, re-initialize
x = ForkedLightClientOptimisticUpdate(kind: newKind)
else:
# Upgrade to Altair
when newKind >= LightClientDataFork.Altair:
if x.kind < LightClientDataFork.Altair:
x = ForkedLightClientOptimisticUpdate(
kind: LightClientDataFork.Altair)
doAssert x.kind == newKind
func migratingToDataFork*[T: SomeForkedLightClientObject](
x: T, newKind: static LightClientDataFork): T =
var upgradedObject = x
upgradedObject.migrateToDataFork(newKind)
upgradedObject

View File

@ -262,6 +262,13 @@ func toMeta*(update: SomeLightClientUpdate): LightClientUpdateMetadata =
update.sync_aggregate.num_active_participants.uint64
meta
template toMeta*(update: ForkedLightClientUpdate): LightClientUpdateMetadata =
withForkyUpdate(update):
when lcDataFork >= LightClientDataFork.Altair:
forkyUpdate.toMeta()
else:
default(LightClientUpdateMetadata)
func is_better_data*(new_meta, old_meta: LightClientUpdateMetadata): bool =
# Compare supermajority (> 2/3) sync committee participation
const max_active_participants = SYNC_COMMITTEE_SIZE.uint64
@ -310,7 +317,8 @@ func is_better_data*(new_meta, old_meta: LightClientUpdateMetadata): bool =
# Tiebreaker 2: Prefer older data (fewer changes to best data)
new_meta.attested_slot < old_meta.attested_slot
template is_better_update*[A, B: SomeLightClientUpdate](
template is_better_update*[
A, B: SomeLightClientUpdate | ForkedLightClientUpdate](
new_update: A, old_update: B): bool =
is_better_data(toMeta(new_update), toMeta(old_update))
@ -324,8 +332,6 @@ func contextEpoch*(bootstrap: altair.LightClientBootstrap): Epoch =
func contextEpoch*(update: SomeLightClientUpdate): Epoch =
update.attested_header.slot.epoch
from ./datatypes/eip4844 import BeaconState
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.0/specs/bellatrix/beacon-chain.md#is_merge_transition_complete
func is_merge_transition_complete*(
state: bellatrix.BeaconState | capella.BeaconState | eip4844.BeaconState): bool =

View File

@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2018-2022 Status Research & Development GmbH
# Copyright (c) 2018-2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -14,8 +14,7 @@ import
std/[os, strformat],
chronicles,
./spec/[
beaconstate, eth2_ssz_serialization, eth2_merkleization, forks, helpers],
./spec/datatypes/[phase0, altair]
beaconstate, eth2_ssz_serialization, eth2_merkleization, forks, helpers]
export
beaconstate, eth2_ssz_serialization, eth2_merkleization, forks
@ -51,7 +50,7 @@ proc dump*(dir: string, v: SyncCommitteeMessage, validator: ValidatorPubKey) =
logErrors:
SSZ.saveFile(dir / &"sync-committee-msg-{v.slot}-{shortLog(validator)}.ssz", v)
proc dump*(dir: string, v: altair.LightClientBootstrap) =
proc dump*(dir: string, v: ForkyLightClientBootstrap) =
logErrors:
let
prefix = "bootstrap"
@ -65,16 +64,16 @@ proc dump*(dir: string, v: SomeLightClientUpdate) =
logErrors:
let
prefix =
when v is altair.LightClientUpdate:
when v is ForkyLightClientUpdate:
"update"
elif v is altair.LightClientFinalityUpdate:
elif v is ForkyLightClientFinalityUpdate:
"finality-update"
elif v is altair.LightClientOptimisticUpdate:
elif v is ForkyLightClientOptimisticUpdate:
"optimistic-update"
attestedSlot = v.attested_header.slot
attestedBlck = shortLog(v.attested_header.hash_tree_root())
syncCommitteeSuffix =
when v is SomeLightClientUpdateWithSyncCommittee:
when v is SomeForkyLightClientUpdateWithSyncCommittee:
if v.is_sync_committee_update:
"s"
else:
@ -82,7 +81,7 @@ proc dump*(dir: string, v: SomeLightClientUpdate) =
else:
""
finalitySuffix =
when v is SomeLightClientUpdateWithFinality:
when v is SomeForkyLightClientUpdateWithFinality:
if v.is_finality_update:
"f"
else:

View File

@ -13,7 +13,6 @@ else:
import chronos, chronicles, stew/base10
import
eth/p2p/discoveryv5/random2,
../spec/datatypes/[altair],
../networking/eth2_network,
../beacon_clock,
"."/sync_protocol, "."/sync_manager
@ -28,24 +27,24 @@ type
Endpoint[K, V] =
(K, V) # https://github.com/nim-lang/Nim/issues/19531
Bootstrap =
Endpoint[Eth2Digest, altair.LightClientBootstrap]
Endpoint[Eth2Digest, ForkedLightClientBootstrap]
UpdatesByRange =
Endpoint[Slice[SyncCommitteePeriod], altair.LightClientUpdate]
Endpoint[Slice[SyncCommitteePeriod], ForkedLightClientUpdate]
FinalityUpdate =
Endpoint[Nothing, altair.LightClientFinalityUpdate]
Endpoint[Nothing, ForkedLightClientFinalityUpdate]
OptimisticUpdate =
Endpoint[Nothing, altair.LightClientOptimisticUpdate]
Endpoint[Nothing, ForkedLightClientOptimisticUpdate]
ValueVerifier[V] =
proc(v: V): Future[Result[void, VerifierError]] {.gcsafe, raises: [Defect].}
BootstrapVerifier* =
ValueVerifier[altair.LightClientBootstrap]
ValueVerifier[ForkedLightClientBootstrap]
UpdateVerifier* =
ValueVerifier[altair.LightClientUpdate]
ValueVerifier[ForkedLightClientUpdate]
FinalityUpdateVerifier* =
ValueVerifier[altair.LightClientFinalityUpdate]
ValueVerifier[ForkedLightClientFinalityUpdate]
OptimisticUpdateVerifier* =
ValueVerifier[altair.LightClientOptimisticUpdate]
ValueVerifier[ForkedLightClientOptimisticUpdate]
GetTrustedBlockRootCallback* =
proc(): Option[Eth2Digest] {.gcsafe, raises: [Defect].}
@ -121,13 +120,13 @@ proc doRequest(
e: typedesc[Bootstrap],
peer: Peer,
blockRoot: Eth2Digest
): Future[NetRes[altair.LightClientBootstrap]] {.
): Future[NetRes[ForkedLightClientBootstrap]] {.
raises: [Defect, IOError].} =
peer.lightClientBootstrap(blockRoot)
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.0/specs/altair/light-client/p2p-interface.md#lightclientupdatesbyrange
type LightClientUpdatesByRangeResponse =
NetRes[List[altair.LightClientUpdate, MAX_REQUEST_LIGHT_CLIENT_UPDATES]]
NetRes[List[ForkedLightClientUpdate, MAX_REQUEST_LIGHT_CLIENT_UPDATES]]
proc doRequest(
e: typedesc[UpdatesByRange],
peer: Peer,
@ -146,31 +145,38 @@ proc doRequest(
" > " & Base10.toString(reqCount.uint) & ")")
var expectedPeriod = startPeriod
for update in response.get:
let
attestedPeriod = update.attested_header.slot.sync_committee_period
signaturePeriod = update.signature_slot.sync_committee_period
if attestedPeriod != update.signature_slot.sync_committee_period:
raise newException(ResponseError, "Conflicting sync committee periods" &
" (signature: " & Base10.toString(distinctBase(signaturePeriod)) &
" != " & Base10.toString(distinctBase(attestedPeriod)) & ")")
if attestedPeriod < expectedPeriod:
raise newException(ResponseError, "Unexpected sync committee period" &
" (" & Base10.toString(distinctBase(attestedPeriod)) &
" < " & Base10.toString(distinctBase(expectedPeriod)) & ")")
if attestedPeriod > expectedPeriod:
if attestedPeriod > lastPeriod:
raise newException(ResponseError, "Sync committee period too high" &
" (" & Base10.toString(distinctBase(attestedPeriod)) &
" > " & Base10.toString(distinctBase(lastPeriod)) & ")")
expectedPeriod = attestedPeriod
inc expectedPeriod
withForkyUpdate(update):
when lcDataFork >= LightClientDataFork.Altair:
let
attPeriod = forkyUpdate.attested_header.slot.sync_committee_period
sigPeriod = forkyUpdate.signature_slot.sync_committee_period
if attPeriod != sigPeriod:
raise newException(
ResponseError, "Conflicting sync committee periods" &
" (signature: " & Base10.toString(distinctBase(sigPeriod)) &
" != " & Base10.toString(distinctBase(attPeriod)) & ")")
if attPeriod < expectedPeriod:
raise newException(
ResponseError, "Unexpected sync committee period" &
" (" & Base10.toString(distinctBase(attPeriod)) &
" < " & Base10.toString(distinctBase(expectedPeriod)) & ")")
if attPeriod > expectedPeriod:
if attPeriod > lastPeriod:
raise newException(
ResponseError, "Sync committee period too high" &
" (" & Base10.toString(distinctBase(attPeriod)) &
" > " & Base10.toString(distinctBase(lastPeriod)) & ")")
expectedPeriod = attPeriod
inc expectedPeriod
else:
raise newException(ResponseError, "Invalid context bytes")
return response
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.0/specs/altair/light-client/p2p-interface.md#getlightclientfinalityupdate
proc doRequest(
e: typedesc[FinalityUpdate],
peer: Peer
): Future[NetRes[altair.LightClientFinalityUpdate]] {.
): Future[NetRes[ForkedLightClientFinalityUpdate]] {.
raises: [Defect, IOError].} =
peer.lightClientFinalityUpdate()
@ -178,7 +184,7 @@ proc doRequest(
proc doRequest(
e: typedesc[OptimisticUpdate],
peer: Peer
): Future[NetRes[altair.LightClientOptimisticUpdate]] {.
): Future[NetRes[ForkedLightClientOptimisticUpdate]] {.
raises: [Defect, IOError].} =
peer.lightClientOptimisticUpdate()
@ -186,13 +192,13 @@ template valueVerifier[E](
self: LightClientManager,
e: typedesc[E]
): ValueVerifier[E.V] =
when E.V is altair.LightClientBootstrap:
when E.V is ForkedLightClientBootstrap:
self.bootstrapVerifier
elif E.V is altair.LightClientUpdate:
elif E.V is ForkedLightClientUpdate:
self.updateVerifier
elif E.V is altair.LightClientFinalityUpdate:
elif E.V is ForkedLightClientFinalityUpdate:
self.finalityUpdateVerifier
elif E.V is altair.LightClientOptimisticUpdate:
elif E.V is ForkedLightClientOptimisticUpdate:
self.optimisticUpdateVerifier
else: static: doAssert false
@ -231,20 +237,31 @@ proc workerTask[E](
return didProgress
of VerifierError.Duplicate:
# Ignore, a concurrent request may have already fulfilled this
when E.V is altair.LightClientBootstrap:
when E.V is ForkedLightClientBootstrap:
didProgress = true
else:
discard
of VerifierError.UnviableFork:
# Descore, peer is on an incompatible fork version
notice "Received value from an unviable fork", value = val.shortLog,
endpoint = E.name, peer, peer_score = peer.getScore()
withForkyObject(val):
when lcDataFork >= LightClientDataFork.Altair:
notice "Received value from an unviable fork",
value = forkyObject,
endpoint = E.name, peer, peer_score = peer.getScore()
else:
notice "Received value from an unviable fork",
endpoint = E.name, peer, peer_score = peer.getScore()
peer.updateScore(PeerScoreUnviableFork)
return didProgress
of VerifierError.Invalid:
# Descore, received data is malformed
warn "Received invalid value", value = val.shortLog,
endpoint = E.name, peer, peer_score = peer.getScore()
withForkyObject(val):
when lcDataFork >= LightClientDataFork.Altair:
warn "Received invalid value", value = forkyObject.shortLog,
endpoint = E.name, peer, peer_score = peer.getScore()
else:
warn "Received invalid value",
endpoint = E.name, peer, peer_score = peer.getScore()
peer.updateScore(PeerScoreBadValues)
return didProgress
else:

View File

@ -117,28 +117,33 @@ proc readChunkPayload*(
return neterr InvalidContextBytes
proc readChunkPayload*(
conn: Connection, peer: Peer, MsgType: type SomeLightClientObject):
conn: Connection, peer: Peer, MsgType: type SomeForkedLightClientObject):
Future[NetRes[MsgType]] {.async.} =
var contextBytes: ForkDigest
try:
await conn.readExactly(addr contextBytes, sizeof contextBytes)
except CatchableError:
return neterr UnexpectedEOF
let stateFork =
let contextFork =
peer.network.forkDigests[].stateForkForDigest(contextBytes).valueOr:
return neterr InvalidContextBytes
let res =
if stateFork >= BeaconStateFork.Altair:
await eth2_network.readChunkPayload(conn, peer, MsgType)
if contextFork >= BeaconStateFork.Altair:
const lcDataFork = LightClientDataFork.Altair
let res = await eth2_network.readChunkPayload(
conn, peer, MsgType.forky(lcDataFork))
if res.isOk:
if contextFork != peer.network.cfg.stateForkAtEpoch(res.get.contextEpoch):
return neterr InvalidContextBytes
var obj = ok MsgType(kind: lcDataFork)
template forkyObj: untyped = obj.get.forky(lcDataFork)
forkyObj = res.get
return obj
else:
doAssert stateFork == BeaconStateFork.Phase0
return neterr InvalidContextBytes
if res.isErr:
return err(res.error)
if stateFork != peer.network.cfg.stateForkAtEpoch(res.get.contextEpoch):
return err(res.error)
else:
doAssert contextFork == BeaconStateFork.Phase0
return neterr InvalidContextBytes
return ok res.get
func shortLog*(s: StatusMsg): auto =
(
@ -476,7 +481,7 @@ p2pProtocol BeaconSync(version = 1,
proc lightClientBootstrap(
peer: Peer,
blockRoot: Eth2Digest,
response: SingleChunkResponse[altair.LightClientBootstrap])
response: SingleChunkResponse[ForkedLightClientBootstrap])
{.async, libp2pProtocol("light_client_bootstrap", 1,
isLightClientRequest = true).} =
trace "Received LC bootstrap request", peer, blockRoot
@ -484,17 +489,19 @@ p2pProtocol BeaconSync(version = 1,
doAssert dag.lcDataStore.serve
let bootstrap = dag.getLightClientBootstrap(blockRoot)
if bootstrap.isOk:
let
contextEpoch = bootstrap.get.contextEpoch
contextBytes = peer.networkState.forkDigestAtEpoch(contextEpoch).data
withForkyBootstrap(bootstrap):
when lcDataFork >= LightClientDataFork.Altair:
let
contextEpoch = forkyBootstrap.contextEpoch
contextBytes = peer.networkState.forkDigestAtEpoch(contextEpoch).data
# TODO extract from libp2pProtocol
peer.awaitQuota(
lightClientBootstrapResponseCost, "light_client_bootstrap/1")
await response.send(bootstrap.get, contextBytes)
else:
raise newException(ResourceUnavailableError, LCBootstrapUnavailable)
# TODO extract from libp2pProtocol
peer.awaitQuota(
lightClientBootstrapResponseCost,
"light_client_bootstrap/1")
await response.sendSSZ(forkyBootstrap, contextBytes)
else:
raise newException(ResourceUnavailableError, LCBootstrapUnavailable)
debug "LC bootstrap request done", peer, blockRoot
@ -504,7 +511,7 @@ p2pProtocol BeaconSync(version = 1,
startPeriod: SyncCommitteePeriod,
reqCount: uint64,
response: MultipleChunksResponse[
altair.LightClientUpdate, MAX_REQUEST_LIGHT_CLIENT_UPDATES])
ForkedLightClientUpdate, MAX_REQUEST_LIGHT_CLIENT_UPDATES])
{.async, libp2pProtocol("light_client_updates_by_range", 1,
isLightClientRequest = true).} =
trace "Received LC updates by range request", peer, startPeriod, reqCount
@ -525,23 +532,28 @@ p2pProtocol BeaconSync(version = 1,
var found = 0
for period in startPeriod..<onePastPeriod:
let update = dag.getLightClientUpdateForPeriod(period)
if update.isSome:
let
contextEpoch = update.get.contextEpoch
contextBytes = peer.networkState.forkDigestAtEpoch(contextEpoch).data
withForkyUpdate(update):
when lcDataFork >= LightClientDataFork.Altair:
let
contextEpoch = forkyUpdate.contextEpoch
contextBytes =
peer.networkState.forkDigestAtEpoch(contextEpoch).data
# TODO extract from libp2pProtocol
peer.awaitQuota(
lightClientUpdateResponseCost, "light_client_updates_by_range/1")
await response.write(update.get, contextBytes)
inc found
# TODO extract from libp2pProtocol
peer.awaitQuota(
lightClientUpdateResponseCost,
"light_client_updates_by_range/1")
await response.writeSSZ(forkyUpdate, contextBytes)
inc found
else:
discard
debug "LC updates by range request done", peer, startPeriod, count, found
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.0/specs/altair/light-client/p2p-interface.md#getlightclientfinalityupdate
proc lightClientFinalityUpdate(
peer: Peer,
response: SingleChunkResponse[altair.LightClientFinalityUpdate])
response: SingleChunkResponse[ForkedLightClientFinalityUpdate])
{.async, libp2pProtocol("light_client_finality_update", 1,
isLightClientRequest = true).} =
trace "Received LC finality update request", peer
@ -549,25 +561,26 @@ p2pProtocol BeaconSync(version = 1,
doAssert dag.lcDataStore.serve
let finality_update = dag.getLightClientFinalityUpdate()
if finality_update.isSome:
let
contextEpoch = finality_update.get.contextEpoch
contextBytes = peer.networkState.forkDigestAtEpoch(contextEpoch).data
# TODO extract from libp2pProtocol
peer.awaitQuota(
lightClientFinalityUpdateResponseCost, "light_client_finality_update/1")
await response.send(finality_update.get, contextBytes)
else:
raise newException(ResourceUnavailableError, LCFinUpdateUnavailable)
withForkyFinalityUpdate(finality_update):
when lcDataFork >= LightClientDataFork.Altair:
let
contextEpoch = forkyFinalityUpdate.contextEpoch
contextBytes = peer.networkState.forkDigestAtEpoch(contextEpoch).data
# TODO extract from libp2pProtocol
peer.awaitQuota(
lightClientFinalityUpdateResponseCost,
"light_client_finality_update/1")
await response.sendSSZ(forkyFinalityUpdate, contextBytes)
else:
raise newException(ResourceUnavailableError, LCFinUpdateUnavailable)
debug "LC finality update request done", peer
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.0/specs/altair/light-client/p2p-interface.md#getlightclientoptimisticupdate
proc lightClientOptimisticUpdate(
peer: Peer,
response: SingleChunkResponse[altair.LightClientOptimisticUpdate])
response: SingleChunkResponse[ForkedLightClientOptimisticUpdate])
{.async, libp2pProtocol("light_client_optimistic_update", 1,
isLightClientRequest = true).} =
trace "Received LC optimistic update request", peer
@ -575,17 +588,19 @@ p2pProtocol BeaconSync(version = 1,
doAssert dag.lcDataStore.serve
let optimistic_update = dag.getLightClientOptimisticUpdate()
if optimistic_update.isSome:
let
contextEpoch = optimistic_update.get.contextEpoch
contextBytes = peer.networkState.forkDigestAtEpoch(contextEpoch).data
withForkyOptimisticUpdate(optimistic_update):
when lcDataFork >= LightClientDataFork.Altair:
let
contextEpoch = forkyOptimisticUpdate.contextEpoch
contextBytes = peer.networkState.forkDigestAtEpoch(contextEpoch).data
# TODO extract from libp2pProtocol
peer.awaitQuota(
lightClientOptimisticUpdateResponseCost, "light_client_optimistic_update/1")
await response.send(optimistic_update.get, contextBytes)
else:
raise newException(ResourceUnavailableError, LCOptUpdateUnavailable)
# TODO extract from libp2pProtocol
peer.awaitQuota(
lightClientOptimisticUpdateResponseCost,
"light_client_optimistic_update/1")
await response.sendSSZ(forkyOptimisticUpdate, contextBytes)
else:
raise newException(ResourceUnavailableError, LCOptUpdateUnavailable)
debug "LC optimistic update request done", peer

View File

@ -180,45 +180,48 @@ proc handleLightClientUpdates*(node: BeaconNode, slot: Slot) {.async.} =
debug "Waiting to send LC updates", slot, delay = shortLog(sendTime.offset)
await sleepAsync(sendTime.offset)
template latest(): auto = node.dag.lcDataStore.cache.latest
let signature_slot = latest.signature_slot
if slot != signature_slot:
return
withForkyFinalityUpdate(node.dag.lcDataStore.cache.latest):
when lcDataFork >= LightClientDataFork.Altair:
let signature_slot = forkyFinalityUpdate.signature_slot
if slot != signature_slot:
return
let num_active_participants = latest.sync_aggregate.num_active_participants
if num_active_participants < MIN_SYNC_COMMITTEE_PARTICIPANTS:
return
let num_active_participants =
forkyFinalityUpdate.sync_aggregate.num_active_participants
if num_active_participants < MIN_SYNC_COMMITTEE_PARTICIPANTS:
return
let finalized_slot = latest.finalized_header.slot
if finalized_slot > node.lightClientPool[].latestForwardedFinalitySlot:
template msg(): auto = latest
let sendResult = await node.network.broadcastLightClientFinalityUpdate(msg)
let finalized_slot = forkyFinalityUpdate.finalized_header.slot
if finalized_slot > node.lightClientPool[].latestForwardedFinalitySlot:
template msg(): auto = forkyFinalityUpdate
let sendResult =
await node.network.broadcastLightClientFinalityUpdate(msg)
# Optimization for message with ephemeral validity, whether sent or not
node.lightClientPool[].latestForwardedFinalitySlot = finalized_slot
# Optimization for message with ephemeral validity, whether sent or not
node.lightClientPool[].latestForwardedFinalitySlot = finalized_slot
if sendResult.isOk:
beacon_light_client_finality_updates_sent.inc()
notice "LC finality update sent", message = shortLog(msg)
else:
warn "LC finality update failed to send",
error = sendResult.error()
if sendResult.isOk:
beacon_light_client_finality_updates_sent.inc()
notice "LC finality update sent", message = shortLog(msg)
else:
warn "LC finality update failed to send",
error = sendResult.error()
let attested_slot = latest.attested_header.slot
if attested_slot > node.lightClientPool[].latestForwardedOptimisticSlot:
let msg = latest.toOptimistic
let sendResult =
await node.network.broadcastLightClientOptimisticUpdate(msg)
let attested_slot = forkyFinalityUpdate.attested_header.slot
if attested_slot > node.lightClientPool[].latestForwardedOptimisticSlot:
let msg = forkyFinalityUpdate.toOptimistic
let sendResult =
await node.network.broadcastLightClientOptimisticUpdate(msg)
# Optimization for message with ephemeral validity, whether sent or not
node.lightClientPool[].latestForwardedOptimisticSlot = attested_slot
# Optimization for message with ephemeral validity, whether sent or not
node.lightClientPool[].latestForwardedOptimisticSlot = attested_slot
if sendResult.isOk:
beacon_light_client_optimistic_updates_sent.inc()
notice "LC optimistic update sent", message = shortLog(msg)
else:
warn "LC optimistic update failed to send",
error = sendResult.error()
if sendResult.isOk:
beacon_light_client_optimistic_updates_sent.inc()
notice "LC optimistic update sent", message = shortLog(msg)
else:
warn "LC optimistic update failed to send",
error = sendResult.error()
proc createAndSendAttestation(node: BeaconNode,
fork: Fork,

View File

@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2021-2022 Status Research & Development GmbH
# Copyright (c) 2021-2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -92,27 +92,42 @@ suite "Light client" & preset():
test "Pre-Altair":
# Genesis
check:
dag.headState.kind == BeaconStateFork.Phase0
dag.getLightClientUpdateForPeriod(0.SyncCommitteePeriod).isNone
dag.getLightClientFinalityUpdate.isNone
dag.getLightClientOptimisticUpdate.isNone
block:
let
update = dag.getLightClientUpdateForPeriod(0.SyncCommitteePeriod)
finalityUpdate = dag.getLightClientFinalityUpdate
optimisticUpdate = dag.getLightClientOptimisticUpdate
check:
dag.headState.kind == BeaconStateFork.Phase0
update.kind == LightClientDataFork.None
finalityUpdate.kind == LightClientDataFork.None
optimisticUpdate.kind == LightClientDataFork.None
# Advance to last slot before Altair
dag.advanceToSlot(altairStartSlot - 1, verifier, quarantine[])
check:
dag.headState.kind == BeaconStateFork.Phase0
dag.getLightClientUpdateForPeriod(0.SyncCommitteePeriod).isNone
dag.getLightClientFinalityUpdate.isNone
dag.getLightClientOptimisticUpdate.isNone
block:
let
update = dag.getLightClientUpdateForPeriod(0.SyncCommitteePeriod)
finalityUpdate = dag.getLightClientFinalityUpdate
optimisticUpdate = dag.getLightClientOptimisticUpdate
check:
dag.headState.kind == BeaconStateFork.Phase0
update.kind == LightClientDataFork.None
finalityUpdate.kind == LightClientDataFork.None
optimisticUpdate.kind == LightClientDataFork.None
# Advance to Altair
dag.advanceToSlot(altairStartSlot, verifier, quarantine[])
check:
dag.headState.kind == BeaconStateFork.Altair
dag.getLightClientUpdateForPeriod(0.SyncCommitteePeriod).isNone
dag.getLightClientFinalityUpdate.isNone
dag.getLightClientOptimisticUpdate.isNone
block:
let
update = dag.getLightClientUpdateForPeriod(0.SyncCommitteePeriod)
finalityUpdate = dag.getLightClientFinalityUpdate
optimisticUpdate = dag.getLightClientOptimisticUpdate
check:
dag.headState.kind == BeaconStateFork.Altair
update.kind == LightClientDataFork.None
finalityUpdate.kind == LightClientDataFork.None
optimisticUpdate.kind == LightClientDataFork.None
test "Light client sync":
# Advance to Altair
@ -132,10 +147,12 @@ suite "Light client" & preset():
let currentSlot = getStateField(dag.headState, slot)
# Initialize light client store
const storeDataFork = LightClientStore.kind
let bootstrap = dag.getLightClientBootstrap(trusted_block_root)
check bootstrap.isOk
check bootstrap.kind == storeDataFork
template forkyBootstrap: untyped = bootstrap.forky(storeDataFork)
var storeRes = initialize_light_client_store(
trusted_block_root, bootstrap.get)
trusted_block_root, forkyBootstrap)
check storeRes.isOk
template store(): auto = storeRes.get
@ -149,30 +166,31 @@ suite "Light client" & preset():
else:
store.finalized_header.slot.sync_committee_period
update = dag.getLightClientUpdateForPeriod(period)
res = process_light_client_update(
store, update.get, currentSlot, cfg, genesis_validators_root)
check update.kind == storeDataFork
template forkyUpdate: untyped = update.forky(storeDataFork)
let res = process_light_client_update(
store, forkyUpdate, currentSlot, cfg, genesis_validators_root)
check:
update.isSome
update.get.finalized_header.slot.sync_committee_period == period
forkyUpdate.finalized_header.slot.sync_committee_period == period
res.isOk
if update.get.finalized_header.slot > bootstrap.get.header.slot:
store.finalized_header == update.get.finalized_header
if forkyUpdate.finalized_header.slot > forkyBootstrap.header.slot:
store.finalized_header == forkyUpdate.finalized_header
else:
store.finalized_header == bootstrap.get.header
store.finalized_header == forkyBootstrap.header
inc numIterations
if numIterations > 20: doAssert false # Avoid endless loop on test failure
# Sync to latest update
let
finalityUpdate = dag.getLightClientFinalityUpdate
res = process_light_client_update(
store, finalityUpdate.get, currentSlot, cfg, genesis_validators_root)
let finalityUpdate = dag.getLightClientFinalityUpdate
check finalityUpdate.kind == storeDataFork
template forkyFinalityUpdate: untyped = finalityUpdate.forky(storeDataFork)
let res = process_light_client_update(
store, forkyFinalityUpdate, currentSlot, cfg, genesis_validators_root)
check:
finalityUpdate.isSome
finalityUpdate.get.attested_header.slot == dag.head.parent.slot
forkyFinalityUpdate.attested_header.slot == dag.head.parent.slot
res.isOk
store.finalized_header == finalityUpdate.get.finalized_header
store.optimistic_header == finalityUpdate.get.attested_header
store.finalized_header == forkyFinalityUpdate.finalized_header
store.optimistic_header == forkyFinalityUpdate.attested_header
test "Init from checkpoint":
# Fetch genesis state

View File

@ -1,5 +1,5 @@
# beacon_chain
# Copyright (c) 2022 Status Research & Development GmbH
# Copyright (c) 2022-2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@ -99,6 +99,7 @@ suite "Light client processor" & preset():
func onStoreInitialized() = inc numOnStoreInitializedCalls
let store = (ref Option[LightClientStore])()
const storeDataFork = typeof(store[].get).kind
var
processor = LightClientProcessor.new(
false, "", "", cfg, genesis_validators_root, finalizationMode,
@ -107,29 +108,38 @@ suite "Light client processor" & preset():
test "Sync" & testNameSuffix:
let bootstrap = dag.getLightClientBootstrap(trustedBlockRoot)
check bootstrap.isOk
setTimeToSlot(bootstrap.get.header.slot)
check:
bootstrap.kind > LightClientDataFork.None
bootstrap.kind <= storeDataFork
let upgradedBootstrap = bootstrap.migratingToDataFork(storeDataFork)
template forkyBootstrap: untyped = upgradedBootstrap.forky(storeDataFork)
setTimeToSlot(forkyBootstrap.header.slot)
res = processor[].storeObject(
MsgSource.gossip, getBeaconTime(), bootstrap.get)
MsgSource.gossip, getBeaconTime(), bootstrap)
check:
res.isOk
numOnStoreInitializedCalls == 1
store[].isSome
# Reduce stack size by making this a `proc`
proc applyPeriodWithSupermajority(period: SyncCommitteePeriod) =
let update = dag.getLightClientUpdateForPeriod(period)
check update.isSome
setTimeToSlot(update.get.signature_slot)
check:
update.kind > LightClientDataFork.None
update.kind <= storeDataFork
let upgradedUpdate = update.migratingToDataFork(storeDataFork)
template forkyUpdate: untyped = upgradedUpdate.forky(storeDataFork)
setTimeToSlot(forkyUpdate.signature_slot)
res = processor[].storeObject(
MsgSource.gossip, getBeaconTime(), update.get)
MsgSource.gossip, getBeaconTime(), update)
check:
res.isOk
store[].isSome
if update.get.finalized_header.slot > bootstrap.get.header.slot:
store[].get.finalized_header == update.get.finalized_header
if forkyUpdate.finalized_header.slot > forkyBootstrap.header.slot:
store[].get.finalized_header == forkyUpdate.finalized_header
else:
store[].get.finalized_header == bootstrap.get.header
store[].get.optimistic_header == update.get.attested_header
store[].get.finalized_header == forkyBootstrap.header
store[].get.optimistic_header == forkyUpdate.attested_header
for period in lowPeriod .. lastPeriodWithSupermajority:
applyPeriodWithSupermajority(period)
@ -137,12 +147,16 @@ suite "Light client processor" & preset():
# Reduce stack size by making this a `proc`
proc applyPeriodWithoutSupermajority(period: SyncCommitteePeriod) =
let update = dag.getLightClientUpdateForPeriod(period)
check update.isSome
setTimeToSlot(update.get.signature_slot)
check:
update.kind > LightClientDataFork.None
update.kind <= storeDataFork
let upgradedUpdate = update.migratingToDataFork(storeDataFork)
template forkyUpdate: untyped = upgradedUpdate.forky(storeDataFork)
setTimeToSlot(forkyUpdate.signature_slot)
for i in 0 ..< 2:
res = processor[].storeObject(
MsgSource.gossip, getBeaconTime(), update.get)
MsgSource.gossip, getBeaconTime(), update)
if finalizationMode == LightClientFinalizationMode.Optimistic or
period == lastPeriodWithSupermajority + 1:
if finalizationMode == LightClientFinalizationMode.Optimistic or
@ -151,25 +165,25 @@ suite "Light client processor" & preset():
res.isOk
store[].isSome
store[].get.best_valid_update.isSome
store[].get.best_valid_update.get == update.get
store[].get.best_valid_update.get.matches(forkyUpdate)
else:
check:
res.isErr
res.error == VerifierError.Duplicate
store[].isSome
store[].get.best_valid_update.isSome
store[].get.best_valid_update.get == update.get
store[].get.best_valid_update.get.matches(forkyUpdate)
else:
check:
res.isErr
res.error == VerifierError.MissingParent
store[].isSome
store[].get.best_valid_update.isSome
store[].get.best_valid_update.get != update.get
not store[].get.best_valid_update.get.matches(forkyUpdate)
proc applyDuplicate() = # Reduce stack size by making this a `proc`
res = processor[].storeObject(
MsgSource.gossip, getBeaconTime(), update.get)
MsgSource.gossip, getBeaconTime(), update)
if finalizationMode == LightClientFinalizationMode.Optimistic or
period == lastPeriodWithSupermajority + 1:
check:
@ -177,14 +191,14 @@ suite "Light client processor" & preset():
res.error == VerifierError.Duplicate
store[].isSome
store[].get.best_valid_update.isSome
store[].get.best_valid_update.get == update.get
store[].get.best_valid_update.get.matches(forkyUpdate)
else:
check:
res.isErr
res.error == VerifierError.MissingParent
store[].isSome
store[].get.best_valid_update.isSome
store[].get.best_valid_update.get != update.get
not store[].get.best_valid_update.get.matches(forkyUpdate)
applyDuplicate()
time += chronos.minutes(15)
@ -194,34 +208,34 @@ suite "Light client processor" & preset():
time += chronos.minutes(15)
res = processor[].storeObject(
MsgSource.gossip, getBeaconTime(), update.get)
MsgSource.gossip, getBeaconTime(), update)
if finalizationMode == LightClientFinalizationMode.Optimistic:
check:
res.isErr
res.error == VerifierError.Duplicate
store[].isSome
store[].get.best_valid_update.isNone
if store[].get.finalized_header == update.get.attested_header:
if store[].get.finalized_header == forkyUpdate.attested_header:
break
check store[].get.finalized_header == update.get.finalized_header
check store[].get.finalized_header == forkyUpdate.finalized_header
elif period == lastPeriodWithSupermajority + 1:
check:
res.isErr
res.error == VerifierError.Duplicate
store[].isSome
store[].get.best_valid_update.isSome
store[].get.best_valid_update.get == update.get
store[].get.best_valid_update.get.matches(forkyUpdate)
else:
check:
res.isErr
res.error == VerifierError.MissingParent
store[].isSome
store[].get.best_valid_update.isSome
store[].get.best_valid_update.get != update.get
not store[].get.best_valid_update.get.matches(forkyUpdate)
if finalizationMode == LightClientFinalizationMode.Optimistic:
check store[].get.finalized_header == update.get.attested_header
check store[].get.finalized_header == forkyUpdate.attested_header
else:
check store[].get.finalized_header != update.get.attested_header
check store[].get.finalized_header != forkyUpdate.attested_header
for period in lastPeriodWithSupermajority + 1 .. highPeriod:
applyPeriodWithoutSupermajority(period)
@ -229,18 +243,24 @@ suite "Light client processor" & preset():
let
previousFinalized = store[].get.finalized_header
finalityUpdate = dag.getLightClientFinalityUpdate()
check finalityUpdate.isSome
setTimeToSlot(finalityUpdate.get.signature_slot)
check:
finalityUpdate.kind > LightClientDataFork.None
finalityUpdate.kind <= storeDataFork
let upgradedFinalityUpdate =
finalityUpdate.migratingToDataFork(storeDataFork)
template forkyFinalityUpdate: untyped =
upgradedFinalityUpdate.forky(storeDataFork)
setTimeToSlot(forkyFinalityUpdate.signature_slot)
res = processor[].storeObject(
MsgSource.gossip, getBeaconTime(), finalityUpdate.get)
MsgSource.gossip, getBeaconTime(), finalityUpdate)
if res.isOk:
check:
finalizationMode == LightClientFinalizationMode.Optimistic
store[].isSome
store[].get.finalized_header == previousFinalized
store[].get.best_valid_update.isSome
store[].get.best_valid_update.get.matches(finalityUpdate.get)
store[].get.optimistic_header == finalityUpdate.get.attested_header
store[].get.best_valid_update.get.matches(forkyFinalityUpdate)
store[].get.optimistic_header == forkyFinalityUpdate.attested_header
elif finalizationMode == LightClientFinalizationMode.Optimistic:
check res.error == VerifierError.Duplicate
else:
@ -249,11 +269,17 @@ suite "Light client processor" & preset():
test "Invalid bootstrap" & testNameSuffix:
var bootstrap = dag.getLightClientBootstrap(trustedBlockRoot)
check bootstrap.isOk
bootstrap.get.header.slot.inc()
setTimeToSlot(bootstrap.get.header.slot)
check:
bootstrap.kind > LightClientDataFork.None
bootstrap.kind <= storeDataFork
withForkyBootstrap(bootstrap):
when lcDataFork >= LightClientDataFork.Altair:
forkyBootstrap.header.slot.inc()
let upgradedBootstrap = bootstrap.migratingToDataFork(storeDataFork)
template forkyBootstrap: untyped = upgradedBootstrap.forky(storeDataFork)
setTimeToSlot(forkyBootstrap.header.slot)
res = processor[].storeObject(
MsgSource.gossip, getBeaconTime(), bootstrap.get)
MsgSource.gossip, getBeaconTime(), bootstrap)
check:
res.isErr
res.error == VerifierError.Invalid
@ -261,15 +287,19 @@ suite "Light client processor" & preset():
test "Duplicate bootstrap" & testNameSuffix:
let bootstrap = dag.getLightClientBootstrap(trustedBlockRoot)
check bootstrap.isOk
setTimeToSlot(bootstrap.get.header.slot)
check:
bootstrap.kind > LightClientDataFork.None
bootstrap.kind <= storeDataFork
let upgradedBootstrap = bootstrap.migratingToDataFork(storeDataFork)
template forkyBootstrap: untyped = upgradedBootstrap.forky(storeDataFork)
setTimeToSlot(forkyBootstrap.header.slot)
res = processor[].storeObject(
MsgSource.gossip, getBeaconTime(), bootstrap.get)
MsgSource.gossip, getBeaconTime(), bootstrap)
check:
res.isOk
numOnStoreInitializedCalls == 1
res = processor[].storeObject(
MsgSource.gossip, getBeaconTime(), bootstrap.get)
MsgSource.gossip, getBeaconTime(), bootstrap)
check:
res.isErr
res.error == VerifierError.Duplicate
@ -277,10 +307,14 @@ suite "Light client processor" & preset():
test "Missing bootstrap (update)" & testNameSuffix:
let update = dag.getLightClientUpdateForPeriod(lowPeriod)
check update.isSome
setTimeToSlot(update.get.signature_slot)
check:
update.kind > LightClientDataFork.None
update.kind <= storeDataFork
let upgradedUpdate = update.migratingToDataFork(storeDataFork)
template forkyUpdate: untyped = upgradedUpdate.forky(storeDataFork)
setTimeToSlot(forkyUpdate.signature_slot)
res = processor[].storeObject(
MsgSource.gossip, getBeaconTime(), update.get)
MsgSource.gossip, getBeaconTime(), update)
check:
res.isErr
res.error == VerifierError.MissingParent
@ -288,10 +322,16 @@ suite "Light client processor" & preset():
test "Missing bootstrap (finality update)" & testNameSuffix:
let finalityUpdate = dag.getLightClientFinalityUpdate()
check finalityUpdate.isSome
setTimeToSlot(finalityUpdate.get.signature_slot)
check:
finalityUpdate.kind > LightClientDataFork.None
finalityUpdate.kind <= storeDataFork
let upgradedFinalityUpdate =
finalityUpdate.migratingToDataFork(storeDataFork)
template forkyFinalityUpdate: untyped =
upgradedFinalityUpdate.forky(storeDataFork)
setTimeToSlot(forkyFinalityUpdate.signature_slot)
res = processor[].storeObject(
MsgSource.gossip, getBeaconTime(), finalityUpdate.get)
MsgSource.gossip, getBeaconTime(), finalityUpdate)
check:
res.isErr
res.error == VerifierError.MissingParent
@ -299,10 +339,16 @@ suite "Light client processor" & preset():
test "Missing bootstrap (optimistic update)" & testNameSuffix:
let optimisticUpdate = dag.getLightClientOptimisticUpdate()
check optimisticUpdate.isSome
setTimeToSlot(optimisticUpdate.get.signature_slot)
check:
optimisticUpdate.kind > LightClientDataFork.None
optimisticUpdate.kind <= storeDataFork
let upgradedOptimisticUpdate =
optimisticUpdate.migratingToDataFork(storeDataFork)
template forkyOptimisticUpdate: untyped =
upgradedOptimisticUpdate.forky(storeDataFork)
setTimeToSlot(forkyOptimisticUpdate.signature_slot)
res = processor[].storeObject(
MsgSource.gossip, getBeaconTime(), optimisticUpdate.get)
MsgSource.gossip, getBeaconTime(), optimisticUpdate)
check:
res.isErr
res.error == VerifierError.MissingParent