mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-02-26 21:20:34 +00:00
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:
parent
3b60b225b3
commit
7e276937dc
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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])) =
|
||||
|
@ -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)
|
||||
|
@ -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](),
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
309
beacon_chain/spec/forks_light_client.nim
Normal file
309
beacon_chain/spec/forks_light_client.nim
Normal 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
|
@ -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 =
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user