extend light client protocol for Electra

Add missing Electra support for light client protocol:

- https://github.com/ethereum/consensus-specs/pull/3811

Tested against PR consensus-spec-tests, the test runner automatically
picks up the new tests once available.
This commit is contained in:
Etan Kissling 2024-06-21 15:02:01 +02:00
parent 31653d5869
commit 65ae4d8eb1
No known key found for this signature in database
GPG Key ID: B21DA824C5A3D03D
11 changed files with 758 additions and 130 deletions

View File

@ -542,12 +542,22 @@ proc new*(T: type BeaconChainDB,
"lc_deneb_headers"
else:
"",
electraHeaders:
if cfg.DENEB_FORK_EPOCH != FAR_FUTURE_EPOCH:
"lc_electra_headers"
else:
"",
altairCurrentBranches: "lc_altair_current_branches",
electraCurrentBranches:
if cfg.ELECTRA_FORK_EPOCH != FAR_FUTURE_EPOCH:
"lc_electra_current_branches"
else:
"",
altairSyncCommittees: "lc_altair_sync_committees",
legacyAltairBestUpdates: "lc_altair_best_updates",
bestUpdates: "lc_best_updates",
sealedPeriods: "lc_sealed_periods")).expectDb()
static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb
static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
var blobs : KvStoreRef
if cfg.DENEB_FORK_EPOCH != FAR_FUTURE_EPOCH:

View File

@ -28,13 +28,15 @@ logScope: topics = "lcdata"
# - Altair: ~38 KB per `SyncCommitteePeriod` (~1.0 MB per month)
# - Capella: ~221 KB per `SyncCommitteePeriod` (~6.0 MB per month)
# - Deneb: ~225 KB per `SyncCommitteePeriod` (~6.2 MB per month)
# - Electra: ~249 KB per `SyncCommitteePeriod` (~6.8 MB per month)
#
# `lc_altair_current_branches` holds Merkle proofs needed to
# `lc_xxxxx_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.
# Mainnet data size (all columns):
# - Altair ... Deneb: ~42 KB per `SyncCommitteePeriod` (~1.1 MB per month)
# - Electra: ~50 KB per `SyncCommitteePeriod` (~1.4 MB per month)
#
# `lc_altair_sync_committees` contains a copy of finalized sync committees.
# They are initially populated from the main DAG (usually a fast state access).
@ -42,7 +44,7 @@ logScope: topics = "lcdata"
# SSZ because this data does not compress well, and because this data
# needs to be bundled together with other data to fulfill requests.
# Mainnet data size (all columns):
# - Altair ... Deneb: ~24 KB per `SyncCommitteePeriod` (~0.7 MB per month)
# - Altair ... Electra: ~24 KB per `SyncCommitteePeriod` (~0.7 MB per month)
#
# `lc_best_updates` holds full `LightClientUpdate` objects in SSZ form.
# These objects are frequently queried in bulk, but there is only one per
@ -59,6 +61,7 @@ logScope: topics = "lcdata"
# - Altair: ~25 KB per `SyncCommitteePeriod` (~0.7 MB per month)
# - Capella: ~26 KB per `SyncCommitteePeriod` (~0.7 MB per month)
# - Deneb: ~26 KB per `SyncCommitteePeriod` (~0.7 MB per month)
# - Electra: ~27 KB per `SyncCommitteePeriod` (~0.7 MB per month)
#
# `lc_sealed_periods` contains the sync committee periods for which
# full light client data was imported. Data for these periods may no longer
@ -73,12 +76,16 @@ logScope: topics = "lcdata"
# 600 = 32+20+32+32+256+32+8+8+8+8+4+32+32+32+32+32
# - Deneb: 256*(112+4+616+128+40)/1024*28/1024
# 616 = 32+20+32+32+256+32+8+8+8+8+4+32+32+32+32+32+8+8
# - Electra: 256*(112+4+712+128+40)/1024*28/1024
# 712 = 32+20+32+32+256+32+8+8+8+8+4+32+32+32+32+32+8+8+32+32+32
#
# Committee branch computations:
# - Altair: 256*(5*32+8)/1024*28/1024
# - Electra: 256*(6*32+8)/1024*28/1024
#
# Finality branch computations:
# - Altair: 256*(6*32+8)/1024*28/1024
# - Electra: 256*(7*32+8)/1024*28/1024
#
# Committee computations:
# - Altair: (24624+8)/1024*28/1024
@ -91,6 +98,7 @@ logScope: topics = "lcdata"
# - Altair: (112+24624+5*32+112+6*32+112+8+9)/1024*28/1024
# - Capella: (4+884+24624+5*32+4+884+6*32+112+8+9)/1024*28/1024
# - Deneb: (4+900+24624+5*32+4+900+6*32+112+8+9)/1024*28/1024
# - Electra: (4+996+24624+6*32+4+996+7*32+112+8+9)/1024*28/1024
type
LightClientHeaderStore = object
@ -98,6 +106,11 @@ type
putStmt: SqliteStmt[(array[32, byte], int64, seq[byte]), void]
keepFromStmt: SqliteStmt[int64, void]
BranchFork {.pure.} = enum
None = 0,
Altair,
Electra
CurrentSyncCommitteeBranchStore = object
containsStmt: SqliteStmt[int64, int64]
getStmt: SqliteStmt[int64, seq[byte]]
@ -135,8 +148,8 @@ type
## Eth2Digest -> (Slot, LightClientHeader)
## Cached block headers to support longer retention than block storage.
currentBranches: CurrentSyncCommitteeBranchStore
## Slot -> altair.CurrentSyncCommitteeBranch
currentBranches: array[BranchFork, CurrentSyncCommitteeBranchStore]
## Slot -> CurrentSyncCommitteeBranch
## Cached data for creating future `LightClientBootstrap` instances.
## Key is the block slot of which the post state was used to get the data.
## Data stored for all finalized epoch boundary blocks.
@ -234,12 +247,14 @@ func putHeader*[T: ForkyLightClientHeader](
proc initCurrentBranchesStore(
backend: SqStoreRef,
name: string): KvResult[CurrentSyncCommitteeBranchStore] =
name, typeName: string): KvResult[CurrentSyncCommitteeBranchStore] =
if name == "":
return ok CurrentSyncCommitteeBranchStore()
if not backend.readOnly:
? backend.exec("""
CREATE TABLE IF NOT EXISTS `""" & name & """` (
`slot` INTEGER PRIMARY KEY, -- `Slot` (up through 2^63-1)
`branch` BLOB -- `altair.CurrentSyncCommitteeBranch` (SSZ)
`branch` BLOB -- `""" & typeName & """` (SSZ)
);
""")
if not ? backend.hasTable(name):
@ -278,40 +293,46 @@ func close(store: var CurrentSyncCommitteeBranchStore) =
store.putStmt.disposeSafe()
store.keepFromStmt.disposeSafe()
func hasCurrentSyncCommitteeBranch*(
template kind(x: typedesc[altair.CurrentSyncCommitteeBranch]): BranchFork =
BranchFork.Altair
template kind(x: typedesc[electra.CurrentSyncCommitteeBranch]): BranchFork =
BranchFork.Electra
func hasCurrentSyncCommitteeBranch*[T: ForkyCurrentSyncCommitteeBranch](
db: LightClientDataDB, slot: Slot): bool =
if not slot.isSupportedBySQLite or
distinctBase(db.currentBranches.containsStmt) == nil:
distinctBase(db.currentBranches[T.kind].containsStmt) == nil:
return false
var exists: int64
for res in db.currentBranches.containsStmt.exec(slot.int64, exists):
for res in db.currentBranches[T.kind].containsStmt.exec(slot.int64, exists):
res.expect("SQL query OK")
doAssert exists == 1
return true
false
proc getCurrentSyncCommitteeBranch*(
db: LightClientDataDB, slot: Slot): Opt[altair.CurrentSyncCommitteeBranch] =
proc getCurrentSyncCommitteeBranch*[T: ForkyCurrentSyncCommitteeBranch](
db: LightClientDataDB, slot: Slot): Opt[T] =
if not slot.isSupportedBySQLite or
distinctBase(db.currentBranches.getStmt) == nil:
return Opt.none(altair.CurrentSyncCommitteeBranch)
distinctBase(db.currentBranches[T.kind].getStmt) == nil:
return Opt.none(T)
var branch: seq[byte]
for res in db.currentBranches.getStmt.exec(slot.int64, branch):
for res in db.currentBranches[T.kind].getStmt.exec(slot.int64, branch):
res.expect("SQL query OK")
try:
return ok SSZ.decode(branch, altair.CurrentSyncCommitteeBranch)
return ok SSZ.decode(branch, T)
except SerializationError as exc:
error "LC data store corrupted", store = "currentBranches",
error "LC data store corrupted", store = "currentBranches", kind = T.kind,
slot, exc = exc.msg
return Opt.none(altair.CurrentSyncCommitteeBranch)
return Opt.none(T)
func putCurrentSyncCommitteeBranch*(
db: LightClientDataDB, slot: Slot,
branch: altair.CurrentSyncCommitteeBranch) =
func putCurrentSyncCommitteeBranch*[T: ForkyCurrentSyncCommitteeBranch](
db: LightClientDataDB, slot: Slot, branch: T) =
doAssert not db.backend.readOnly # All `stmt` are non-nil
if not slot.isSupportedBySQLite:
return
let res = db.currentBranches.putStmt.exec((slot.int64, SSZ.encode(branch)))
let res = db.currentBranches[T.kind].putStmt.exec(
(slot.int64, SSZ.encode(branch)))
res.expect("SQL query OK")
proc initSyncCommitteesStore(
@ -643,9 +664,11 @@ func keepPeriodsFrom*(
let res = db.syncCommittees.keepFromStmt.exec(minPeriod.int64)
res.expect("SQL query OK")
let minSlot = min(minPeriod.start_slot, int64.high.Slot)
block:
let res = db.currentBranches.keepFromStmt.exec(minSlot.int64)
res.expect("SQL query OK")
for branchFork, store in db.currentBranches:
if branchFork > BranchFork.None and
distinctBase(store.keepFromStmt) != nil:
let res = store.keepFromStmt.exec(minSlot.int64)
res.expect("SQL query OK")
for lcDataFork, store in db.headers:
if lcDataFork > LightClientDataFork.None and
distinctBase(store.keepFromStmt) != nil:
@ -656,7 +679,9 @@ type LightClientDataDBNames* = object
altairHeaders*: string
capellaHeaders*: string
denebHeaders*: string
electraHeaders*: string
altairCurrentBranches*: string
electraCurrentBranches*: string
altairSyncCommittees*: string
legacyAltairBestUpdates*: string
bestUpdates*: string
@ -665,7 +690,7 @@ type LightClientDataDBNames* = object
proc initLightClientDataDB*(
backend: SqStoreRef,
names: LightClientDataDBNames): KvResult[LightClientDataDB] =
static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb
static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
let
headers = [
# LightClientDataFork.None
@ -678,10 +703,21 @@ proc initLightClientDataDB*(
names.capellaHeaders, "capella.LightClientHeader"),
# LightClientDataFork.Deneb
? backend.initHeadersStore(
names.denebHeaders, "deneb.LightClientHeader")
names.denebHeaders, "deneb.LightClientHeader"),
# LightClientDataFork.Electra
? backend.initHeadersStore(
names.electraHeaders, "electra.LightClientHeader"),
]
currentBranches = [
# BranchFork.None
CurrentSyncCommitteeBranchStore(),
# BranchFork.Altair
? backend.initCurrentBranchesStore(
names.altairCurrentBranches, "altair.CurrentSyncCommitteeBranch"),
# BranchFork.Electra
? backend.initCurrentBranchesStore(
names.electraCurrentBranches, "electra.CurrentSyncCommitteeBranch"),
]
currentBranches =
? backend.initCurrentBranchesStore(names.altairCurrentBranches)
syncCommittees =
? backend.initSyncCommitteesStore(names.altairSyncCommittees)
legacyBestUpdates =
@ -706,7 +742,9 @@ proc close*(db: LightClientDataDB) =
for lcDataFork in LightClientDataFork:
if lcDataFork > LightClientDataFork.None:
db.headers[lcDataFork].close()
db.currentBranches.close()
for branchFork in BranchFork:
if branchFork > BranchFork.None:
db.currentBranches[branchFork].close()
db.syncCommittees.close()
db.legacyBestUpdates.close()
db.bestUpdates.close()

View File

@ -33,11 +33,13 @@ type
CachedLightClientData* = object
## Cached data from historical non-finalized states to improve speed when
## creating future `LightClientUpdate` and `LightClientBootstrap` instances.
current_sync_committee_branch*: altair.CurrentSyncCommitteeBranch
next_sync_committee_branch*: altair.NextSyncCommitteeBranch
current_sync_committee_branch*:
LightClientDataFork.high.CurrentSyncCommitteeBranch
next_sync_committee_branch*:
LightClientDataFork.high.NextSyncCommitteeBranch
finalized_slot*: Slot
finality_branch*: altair.FinalityBranch
finality_branch*: LightClientDataFork.high.FinalityBranch
current_period_best_update*: ref ForkedLightClientUpdate
latest_signature_slot*: Slot

View File

@ -22,6 +22,15 @@ template nextEpochBoundarySlot(slot: Slot): Slot =
## referring to a block at given slot.
(slot + (SLOTS_PER_EPOCH - 1)).epoch.start_slot
func hasCurrentSyncCommitteeBranch(dag: ChainDAGRef, slot: Slot): bool =
let epoch = dag.cfg.consensusForkAtEpoch(slot.epoch)
withLcDataFork(lcDataForkAtConsensusFork(epoch)):
when lcDataFork > LightClientDataFork.None:
hasCurrentSyncCommitteeBranch[lcDataFork.CurrentSyncCommitteeBranch](
dag.lcDataStore.db, slot)
else:
true
proc updateExistingState(
dag: ChainDAGRef, state: var ForkedHashedBeaconState, bsi: BlockSlotId,
save: bool, cache: var StateCache): bool =
@ -226,7 +235,7 @@ proc initLightClientBootstrapForPeriod(
bid = bsi.bid
boundarySlot = bid.slot.nextEpochBoundarySlot
if boundarySlot == nextBoundarySlot and bid.slot >= lowSlot and
not dag.lcDataStore.db.hasCurrentSyncCommitteeBranch(bid.slot):
not dag.hasCurrentSyncCommitteeBranch(bid.slot):
let bdata = dag.getExistingForkedBlock(bid).valueOr:
dag.handleUnexpectedLightClientError(bid.slot)
res.err()
@ -246,7 +255,7 @@ proc initLightClientBootstrapForPeriod(
forkyBlck.toLightClientHeader(lcDataFork))
dag.lcDataStore.db.putCurrentSyncCommitteeBranch(
bid.slot, forkyState.data.build_proof(
altair.CURRENT_SYNC_COMMITTEE_GINDEX).get)
lcDataFork.CURRENT_SYNC_COMMITTEE_GINDEX).get)
else: raiseAssert "Unreachable"
res
@ -393,13 +402,13 @@ proc initLightClientUpdateForPeriod(
update = ForkedLightClientUpdate.init(lcDataFork.LightClientUpdate(
attested_header: forkyBlck.toLightClientHeader(lcDataFork),
next_sync_committee: forkyState.data.next_sync_committee,
next_sync_committee_branch:
forkyState.data.build_proof(altair.NEXT_SYNC_COMMITTEE_GINDEX).get,
next_sync_committee_branch: forkyState.data.build_proof(
lcDataFork.NEXT_SYNC_COMMITTEE_GINDEX).get,
finality_branch:
if finalizedBid.slot != FAR_FUTURE_SLOT:
forkyState.data.build_proof(altair.FINALIZED_ROOT_GINDEX).get
forkyState.data.build_proof(lcDataFork.FINALIZED_ROOT_GINDEX).get
else:
default(FinalityBranch)))
default(lcDataFork.FinalityBranch)))
else: raiseAssert "Unreachable"
do:
dag.handleUnexpectedLightClientError(attestedBid.slot)
@ -464,17 +473,21 @@ proc cacheLightClientData(
## Cache data for a given block and its post-state to speed up creating future
## `LightClientUpdate` and `LightClientBootstrap` instances that refer to this
## block and state.
const lcDataFork = lcDataForkAtConsensusFork(typeof(state).kind)
let
bid = blck.toBlockId()
cachedData = CachedLightClientData(
current_sync_committee_branch:
state.data.build_proof(altair.CURRENT_SYNC_COMMITTEE_GINDEX).get,
next_sync_committee_branch:
state.data.build_proof(altair.NEXT_SYNC_COMMITTEE_GINDEX).get,
current_sync_committee_branch: normalize_merkle_branch(
state.data.build_proof(lcDataFork.CURRENT_SYNC_COMMITTEE_GINDEX).get,
LightClientDataFork.high.CURRENT_SYNC_COMMITTEE_GINDEX),
next_sync_committee_branch: normalize_merkle_branch(
state.data.build_proof(lcDataFork.NEXT_SYNC_COMMITTEE_GINDEX).get,
LightClientDataFork.high.NEXT_SYNC_COMMITTEE_GINDEX),
finalized_slot:
state.data.finalized_checkpoint.epoch.start_slot,
finality_branch:
state.data.build_proof(altair.FINALIZED_ROOT_GINDEX).get,
finality_branch: normalize_merkle_branch(
state.data.build_proof(lcDataFork.FINALIZED_ROOT_GINDEX).get,
LightClientDataFork.high.FINALIZED_ROOT_GINDEX),
current_period_best_update:
current_period_best_update,
latest_signature_slot:
@ -538,15 +551,18 @@ proc assignLightClientData(
when lcDataFork > LightClientDataFork.None:
forkyObject.next_sync_committee =
next_sync_committee.get
forkyObject.next_sync_committee_branch =
attested_data.next_sync_committee_branch
forkyObject.next_sync_committee_branch = normalize_merkle_branch(
attested_data.next_sync_committee_branch,
lcDataFork.NEXT_SYNC_COMMITTEE_GINDEX)
else:
doAssert next_sync_committee.isNone
var finalized_slot = attested_data.finalized_slot
withForkyObject(obj):
when lcDataFork > LightClientDataFork.None:
if finalized_slot == forkyObject.finalized_header.beacon.slot:
forkyObject.finality_branch = attested_data.finality_branch
forkyObject.finality_branch = normalize_merkle_branch(
attested_data.finality_branch,
lcDataFork.FINALIZED_ROOT_GINDEX)
elif finalized_slot < max(dag.tail.slot, dag.backfill.slot):
forkyObject.finalized_header.reset()
forkyObject.finality_branch.reset()
@ -564,10 +580,14 @@ proc assignLightClientData(
attested_data.finalized_slot = finalized_slot
dag.lcDataStore.cache.data[attested_bid] = attested_data
if finalized_slot == forkyObject.finalized_header.beacon.slot:
forkyObject.finality_branch = attested_data.finality_branch
forkyObject.finality_branch = normalize_merkle_branch(
attested_data.finality_branch,
lcDataFork.FINALIZED_ROOT_GINDEX)
elif finalized_slot == GENESIS_SLOT:
forkyObject.finalized_header.reset()
forkyObject.finality_branch = attested_data.finality_branch
forkyObject.finality_branch = normalize_merkle_branch(
attested_data.finality_branch,
lcDataFork.FINALIZED_ROOT_GINDEX)
else:
var fin_header = dag.getExistingLightClientHeader(finalized_bid)
if fin_header.kind == LightClientDataFork.None:
@ -577,7 +597,9 @@ proc assignLightClientData(
else:
fin_header.migrateToDataFork(lcDataFork)
forkyObject.finalized_header = fin_header.forky(lcDataFork)
forkyObject.finality_branch = attested_data.finality_branch
forkyObject.finality_branch = normalize_merkle_branch(
attested_data.finality_branch,
lcDataFork.FINALIZED_ROOT_GINDEX)
withForkyObject(obj):
when lcDataFork > LightClientDataFork.None:
forkyObject.sync_aggregate = sync_aggregate
@ -1014,7 +1036,7 @@ proc getLightClientBootstrap(
# Ensure `current_sync_committee_branch` is known
if dag.lcDataStore.importMode == LightClientDataImportMode.OnDemand and
not dag.lcDataStore.db.hasCurrentSyncCommitteeBranch(slot):
not dag.hasCurrentSyncCommitteeBranch(slot):
let
bsi = dag.getExistingBlockIdAtSlot(slot).valueOr:
return default(ForkedLightClientBootstrap)
@ -1022,13 +1044,14 @@ proc getLightClientBootstrap(
dag.withUpdatedExistingState(tmpState[], bsi) do:
withState(updatedState):
when consensusFork >= ConsensusFork.Altair:
const lcDataFork = lcDataForkAtConsensusFork(consensusFork)
if not dag.lcDataStore.db.hasSyncCommittee(period):
dag.lcDataStore.db.putSyncCommittee(
period, forkyState.data.current_sync_committee)
dag.lcDataStore.db.putHeader(header)
dag.lcDataStore.db.putCurrentSyncCommitteeBranch(
slot, forkyState.data.build_proof(
altair.CURRENT_SYNC_COMMITTEE_GINDEX).get)
lcDataFork.CURRENT_SYNC_COMMITTEE_GINDEX).get)
else: raiseAssert "Unreachable"
do: return default(ForkedLightClientBootstrap)
@ -1050,7 +1073,8 @@ proc getLightClientBootstrap(
debug "LC bootstrap unavailable: Sync committee not cached", period
return default(ForkedLightClientBootstrap)),
current_sync_committee_branch: (block:
dag.lcDataStore.db.getCurrentSyncCommitteeBranch(slot).valueOr:
getCurrentSyncCommitteeBranch[lcDataFork.CurrentSyncCommitteeBranch](
dag.lcDataStore.db, slot).valueOr:
debug "LC bootstrap unavailable: Committee branch not cached", slot
return default(ForkedLightClientBootstrap))))

View File

@ -626,14 +626,16 @@ func kzg_commitment_inclusion_proof_gindex*(
BLOB_KZG_COMMITMENTS_FIRST_GINDEX + index
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.5/specs/deneb/light-client/sync-protocol.md#modified-get_lc_execution_root
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/deneb/light-client/sync-protocol.md#modified-get_lc_execution_root
func get_lc_execution_root*(
header: LightClientHeader, cfg: RuntimeConfig): Eth2Digest =
let epoch = header.beacon.slot.epoch
# [New in Deneb]
if epoch >= cfg.DENEB_FORK_EPOCH:
return hash_tree_root(header.execution)
# [Modified in Deneb]
if epoch >= cfg.CAPELLA_FORK_EPOCH:
let execution_header = capella.ExecutionPayloadHeader(
parent_hash: header.execution.parent_hash,
@ -655,11 +657,12 @@ func get_lc_execution_root*(
ZERO_HASH
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.5/specs/deneb/light-client/sync-protocol.md#modified-is_valid_light_client_header
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/deneb/light-client/sync-protocol.md#modified-is_valid_light_client_header
func is_valid_light_client_header*(
header: LightClientHeader, cfg: RuntimeConfig): bool =
let epoch = header.beacon.slot.epoch
# [New in Deneb:EIP4844]
if epoch < cfg.DENEB_FORK_EPOCH:
if header.execution.blob_gas_used != 0 or
header.execution.excess_blob_gas != 0:

View File

@ -29,24 +29,25 @@ from stew/bitops2 import log2trunc
from stew/byteutils import to0xHex
from ./altair import
EpochParticipationFlags, InactivityScores, SyncAggregate, SyncCommittee,
TrustedSyncAggregate
TrustedSyncAggregate, num_active_participants
from ./bellatrix import BloomLogs, ExecutionAddress, Transaction
from ./capella import
HistoricalSummary, SignedBLSToExecutionChangeList, Withdrawal
ExecutionBranch, HistoricalSummary, SignedBLSToExecutionChangeList,
Withdrawal, EXECUTION_PAYLOAD_GINDEX
from ./deneb import Blobs, BlobsBundle, KzgCommitments, KzgProofs
export json_serialization, base, kzg4844
const
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.5/specs/altair/light-client/sync-protocol.md#constants
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/sync-protocol.md#constants
# All of these indices are rooted in `BeaconState`.
# The first member (`genesis_time`) is 64, subsequent members +1 each.
# If there are ever more than 64 members in `BeaconState`, indices change!
# `FINALIZED_ROOT_GINDEX` is one layer deeper, i.e., `84 * 2 + 1`.
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/ssz/merkle-proofs.md
FINALIZED_ROOT_GINDEX = 169.GeneralizedIndex # finalized_checkpoint > root
CURRENT_SYNC_COMMITTEE_GINDEX = 86.GeneralizedIndex # current_sync_committee
NEXT_SYNC_COMMITTEE_GINDEX = 87.GeneralizedIndex # next_sync_committee
FINALIZED_ROOT_GINDEX* = 169.GeneralizedIndex # finalized_checkpoint > root
CURRENT_SYNC_COMMITTEE_GINDEX* = 86.GeneralizedIndex # current_sync_committee
NEXT_SYNC_COMMITTEE_GINDEX* = 87.GeneralizedIndex # next_sync_committee
type
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/beacon-chain.md#depositrequest
@ -182,15 +183,6 @@ type
source_pubkey*: ValidatorPubKey
target_pubkey*: ValidatorPubKey
FinalityBranch =
array[log2trunc(FINALIZED_ROOT_GINDEX), Eth2Digest]
CurrentSyncCommitteeBranch =
array[log2trunc(CURRENT_SYNC_COMMITTEE_GINDEX), Eth2Digest]
NextSyncCommitteeBranch =
array[log2trunc(NEXT_SYNC_COMMITTEE_GINDEX), Eth2Digest]
# https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/validator.md#aggregateandproof
AggregateAndProof* = object
aggregator_index*: uint64 # `ValidatorIndex` after validation
@ -202,6 +194,15 @@ type
message*: AggregateAndProof
signature*: ValidatorSig
FinalityBranch* =
array[log2trunc(FINALIZED_ROOT_GINDEX), Eth2Digest]
CurrentSyncCommitteeBranch* =
array[log2trunc(CURRENT_SYNC_COMMITTEE_GINDEX), Eth2Digest]
NextSyncCommitteeBranch* =
array[log2trunc(NEXT_SYNC_COMMITTEE_GINDEX), Eth2Digest]
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.5/specs/capella/light-client/sync-protocol.md#modified-lightclientheader
LightClientHeader* = object
beacon*: BeaconBlockHeader
@ -675,6 +676,233 @@ func shortLog*(v: ExecutionPayload): auto =
excess_blob_gas: $(v.excess_blob_gas)
)
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/sync-protocol.md#modified-get_lc_execution_root
func get_lc_execution_root*(
header: LightClientHeader, cfg: RuntimeConfig): Eth2Digest =
let epoch = header.beacon.slot.epoch
# [New in Electra]
if epoch >= cfg.ELECTRA_FORK_EPOCH:
return hash_tree_root(header.execution)
# [Modified in Electra]
if epoch >= cfg.DENEB_FORK_EPOCH:
let execution_header = deneb.ExecutionPayloadHeader(
parent_hash: header.execution.parent_hash,
fee_recipient: header.execution.fee_recipient,
state_root: header.execution.state_root,
receipts_root: header.execution.receipts_root,
logs_bloom: header.execution.logs_bloom,
prev_randao: header.execution.prev_randao,
block_number: header.execution.block_number,
gas_limit: header.execution.gas_limit,
gas_used: header.execution.gas_used,
timestamp: header.execution.timestamp,
extra_data: header.execution.extra_data,
base_fee_per_gas: header.execution.base_fee_per_gas,
block_hash: header.execution.block_hash,
transactions_root: header.execution.transactions_root,
withdrawals_root: header.execution.withdrawals_root,
blob_gas_used: header.execution.blob_gas_used,
excess_blob_gas: header.execution.excess_blob_gas)
return hash_tree_root(execution_header)
if epoch >= cfg.CAPELLA_FORK_EPOCH:
let execution_header = capella.ExecutionPayloadHeader(
parent_hash: header.execution.parent_hash,
fee_recipient: header.execution.fee_recipient,
state_root: header.execution.state_root,
receipts_root: header.execution.receipts_root,
logs_bloom: header.execution.logs_bloom,
prev_randao: header.execution.prev_randao,
block_number: header.execution.block_number,
gas_limit: header.execution.gas_limit,
gas_used: header.execution.gas_used,
timestamp: header.execution.timestamp,
extra_data: header.execution.extra_data,
base_fee_per_gas: header.execution.base_fee_per_gas,
block_hash: header.execution.block_hash,
transactions_root: header.execution.transactions_root,
withdrawals_root: header.execution.withdrawals_root)
return hash_tree_root(execution_header)
ZERO_HASH
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/sync-protocol.md#modified-is_valid_light_client_header
func is_valid_light_client_header*(
header: LightClientHeader, cfg: RuntimeConfig): bool =
let epoch = header.beacon.slot.epoch
# [New in Electra:EIP6110:EIP7002:EIP7251]
if epoch < cfg.ELECTRA_FORK_EPOCH:
if not header.execution.deposit_requests_root.isZero or
not header.execution.withdrawal_requests_root.isZero or
not header.execution.consolidation_requests_root.isZero:
return false
if epoch < cfg.DENEB_FORK_EPOCH:
if header.execution.blob_gas_used != 0 or
header.execution.excess_blob_gas != 0:
return false
if epoch < cfg.CAPELLA_FORK_EPOCH:
return
header.execution == default(ExecutionPayloadHeader) and
header.execution_branch == default(ExecutionBranch)
is_valid_merkle_branch(
get_lc_execution_root(header, cfg),
header.execution_branch,
log2trunc(EXECUTION_PAYLOAD_GINDEX),
get_subtree_index(EXECUTION_PAYLOAD_GINDEX),
header.beacon.body_root)
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/fork.md#normalize_merkle_branch
func normalize_merkle_branch*[N](
branch: array[N, Eth2Digest],
gindex: static GeneralizedIndex): array[log2trunc(gindex), Eth2Digest] =
const depth = log2trunc(gindex)
var res: array[depth, Eth2Digest]
when depth >= branch.len:
const num_extra = depth - branch.len
res[num_extra ..< depth] = branch
else:
const num_extra = branch.len - depth
for node in branch[0 ..< num_extra]:
doAssert node.isZero, "Truncation of Merkle branch cannot lose info"
res[0 ..< depth] = branch[num_extra ..< branch.len]
res
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/fork.md#upgrading-light-client-data
func upgrade_lc_header_to_electra*(
pre: deneb.LightClientHeader): LightClientHeader =
LightClientHeader(
beacon: pre.beacon,
execution: ExecutionPayloadHeader(
parent_hash: pre.execution.parent_hash,
fee_recipient: pre.execution.fee_recipient,
state_root: pre.execution.state_root,
receipts_root: pre.execution.receipts_root,
logs_bloom: pre.execution.logs_bloom,
prev_randao: pre.execution.prev_randao,
block_number: pre.execution.block_number,
gas_limit: pre.execution.gas_limit,
gas_used: pre.execution.gas_used,
timestamp: pre.execution.timestamp,
extra_data: pre.execution.extra_data,
base_fee_per_gas: pre.execution.base_fee_per_gas,
block_hash: pre.execution.block_hash,
transactions_root: pre.execution.transactions_root,
withdrawals_root: pre.execution.withdrawals_root,
blob_gas_used: pre.execution.blob_gas_used,
excess_blob_gas: pre.execution.blob_gas_used,
deposit_requests_root: ZERO_HASH, # [New in Electra:EIP6110]
withdrawal_requests_root: ZERO_HASH, # [New in Electra:EIP7002:EIP7251]
consolidation_requests_root: ZERO_HASH), # [New in Electra:EIP7251]
execution_branch: pre.execution_branch)
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/fork.md#upgrading-light-client-data
func upgrade_lc_bootstrap_to_electra*(
pre: deneb.LightClientBootstrap): LightClientBootstrap =
LightClientBootstrap(
header: upgrade_lc_header_to_electra(pre.header),
current_sync_committee: pre.current_sync_committee,
current_sync_committee_branch: normalize_merkle_branch(
pre.current_sync_committee_branch, CURRENT_SYNC_COMMITTEE_GINDEX))
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/fork.md#upgrading-light-client-data
func upgrade_lc_update_to_electra*(
pre: deneb.LightClientUpdate): LightClientUpdate =
LightClientUpdate(
attested_header: upgrade_lc_header_to_electra(pre.attested_header),
next_sync_committee: pre.next_sync_committee,
next_sync_committee_branch: normalize_merkle_branch(
pre.next_sync_committee_branch, NEXT_SYNC_COMMITTEE_GINDEX),
finalized_header: upgrade_lc_header_to_electra(pre.finalized_header),
finality_branch: normalize_merkle_branch(
pre.finality_branch, FINALIZED_ROOT_GINDEX),
sync_aggregate: pre.sync_aggregate,
signature_slot: pre.signature_slot)
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/fork.md#upgrading-light-client-data
func upgrade_lc_finality_update_to_electra*(
pre: deneb.LightClientFinalityUpdate): LightClientFinalityUpdate =
LightClientFinalityUpdate(
attested_header: upgrade_lc_header_to_electra(pre.attested_header),
finalized_header: upgrade_lc_header_to_electra(pre.finalized_header),
finality_branch: normalize_merkle_branch(
pre.finality_branch, FINALIZED_ROOT_GINDEX),
sync_aggregate: pre.sync_aggregate,
signature_slot: pre.signature_slot)
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/fork.md#upgrading-light-client-data
func upgrade_lc_optimistic_update_to_electra*(
pre: deneb.LightClientOptimisticUpdate): LightClientOptimisticUpdate =
LightClientOptimisticUpdate(
attested_header: upgrade_lc_header_to_electra(pre.attested_header),
sync_aggregate: pre.sync_aggregate,
signature_slot: pre.signature_slot)
func shortLog*(v: LightClientHeader): auto =
(
beacon: shortLog(v.beacon),
execution: (
block_hash: v.execution.block_hash,
block_number: v.execution.block_number)
)
func shortLog*(v: LightClientBootstrap): auto =
(
header: shortLog(v.header)
)
func shortLog*(v: LightClientUpdate): auto =
(
attested: shortLog(v.attested_header),
has_next_sync_committee:
v.next_sync_committee != default(typeof(v.next_sync_committee)),
finalized: shortLog(v.finalized_header),
num_active_participants: v.sync_aggregate.num_active_participants,
signature_slot: v.signature_slot
)
func shortLog*(v: LightClientFinalityUpdate): auto =
(
attested: shortLog(v.attested_header),
finalized: shortLog(v.finalized_header),
num_active_participants: v.sync_aggregate.num_active_participants,
signature_slot: v.signature_slot
)
func shortLog*(v: LightClientOptimisticUpdate): auto =
(
attested: shortLog(v.attested_header),
num_active_participants: v.sync_aggregate.num_active_participants,
signature_slot: v.signature_slot,
)
chronicles.formatIt LightClientBootstrap: shortLog(it)
chronicles.formatIt LightClientUpdate: shortLog(it)
chronicles.formatIt LightClientFinalityUpdate: shortLog(it)
chronicles.formatIt LightClientOptimisticUpdate: shortLog(it)
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/fork.md#upgrading-the-store
func upgrade_lc_store_to_electra*(
pre: deneb.LightClientStore): LightClientStore =
let best_valid_update =
if pre.best_valid_update.isNone:
Opt.none(LightClientUpdate)
else:
Opt.some upgrade_lc_update_to_electra(pre.best_valid_update.get)
LightClientStore(
finalized_header: upgrade_lc_header_to_electra(pre.finalized_header),
current_sync_committee: pre.current_sync_committee,
next_sync_committee: pre.next_sync_committee,
best_valid_update: best_valid_update,
optimistic_header: upgrade_lc_header_to_electra(pre.optimistic_header),
previous_max_active_participants: pre.previous_max_active_participants,
current_max_active_participants: pre.current_max_active_participants)
template asSigned*(
x: SigVerifiedSignedBeaconBlock |
MsgTrustedSignedBeaconBlock |

View File

@ -252,6 +252,11 @@ RestJson.useDefaultSerializationFor(
electra.ExecutionPayload,
electra.ExecutionPayloadHeader,
electra.IndexedAttestation,
electra.LightClientBootstrap,
electra.LightClientFinalityUpdate,
electra.LightClientHeader,
electra.LightClientOptimisticUpdate,
electra.LightClientUpdate,
electra.SignedBeaconBlock,
electra.TrustedAttestation,
electra_mev.BlindedBeaconBlock,

View File

@ -1325,8 +1325,10 @@ func forkVersion*(cfg: RuntimeConfig, consensusFork: ConsensusFork): Version =
func lcDataForkAtConsensusFork*(
consensusFork: ConsensusFork): LightClientDataFork =
static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb
if consensusFork >= ConsensusFork.Deneb:
static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
if consensusFork >= ConsensusFork.Electra:
LightClientDataFork.Electra
elif consensusFork >= ConsensusFork.Deneb:
LightClientDataFork.Deneb
elif consensusFork >= ConsensusFork.Capella:
LightClientDataFork.Capella

View File

@ -16,32 +16,42 @@ type
None = 0, # only use non-0 in DB to detect accidentally uninitialized data
Altair = 1,
Capella = 2,
Deneb = 3
Deneb = 3,
Electra = 4
ForkyCurrentSyncCommitteeBranch* =
altair.CurrentSyncCommitteeBranch |
electra.CurrentSyncCommitteeBranch
ForkyLightClientHeader* =
altair.LightClientHeader |
capella.LightClientHeader |
deneb.LightClientHeader
deneb.LightClientHeader |
electra.LightClientHeader
ForkyLightClientBootstrap* =
altair.LightClientBootstrap |
capella.LightClientBootstrap |
deneb.LightClientBootstrap
deneb.LightClientBootstrap |
electra.LightClientBootstrap
ForkyLightClientUpdate* =
altair.LightClientUpdate |
capella.LightClientUpdate |
deneb.LightClientUpdate
deneb.LightClientUpdate |
electra.LightClientUpdate
ForkyLightClientFinalityUpdate* =
altair.LightClientFinalityUpdate |
capella.LightClientFinalityUpdate |
deneb.LightClientFinalityUpdate
deneb.LightClientFinalityUpdate |
electra.LightClientFinalityUpdate
ForkyLightClientOptimisticUpdate* =
altair.LightClientOptimisticUpdate |
capella.LightClientOptimisticUpdate |
deneb.LightClientOptimisticUpdate
deneb.LightClientOptimisticUpdate |
electra.LightClientOptimisticUpdate
SomeForkyLightClientUpdateWithSyncCommittee* =
ForkyLightClientUpdate
@ -62,7 +72,8 @@ type
ForkyLightClientStore* =
altair.LightClientStore |
capella.LightClientStore |
deneb.LightClientStore
deneb.LightClientStore |
electra.LightClientStore
ForkedLightClientHeader* = object
case kind*: LightClientDataFork
@ -74,6 +85,8 @@ type
capellaData*: capella.LightClientHeader
of LightClientDataFork.Deneb:
denebData*: deneb.LightClientHeader
of LightClientDataFork.Electra:
electraData*: electra.LightClientHeader
ForkedLightClientBootstrap* = object
case kind*: LightClientDataFork
@ -85,6 +98,8 @@ type
capellaData*: capella.LightClientBootstrap
of LightClientDataFork.Deneb:
denebData*: deneb.LightClientBootstrap
of LightClientDataFork.Electra:
electraData*: electra.LightClientBootstrap
ForkedLightClientUpdate* = object
case kind*: LightClientDataFork
@ -96,6 +111,8 @@ type
capellaData*: capella.LightClientUpdate
of LightClientDataFork.Deneb:
denebData*: deneb.LightClientUpdate
of LightClientDataFork.Electra:
electraData*: electra.LightClientUpdate
ForkedLightClientFinalityUpdate* = object
case kind*: LightClientDataFork
@ -107,6 +124,8 @@ type
capellaData*: capella.LightClientFinalityUpdate
of LightClientDataFork.Deneb:
denebData*: deneb.LightClientFinalityUpdate
of LightClientDataFork.Electra:
electraData*: electra.LightClientFinalityUpdate
ForkedLightClientOptimisticUpdate* = object
case kind*: LightClientDataFork
@ -118,6 +137,8 @@ type
capellaData*: capella.LightClientOptimisticUpdate
of LightClientDataFork.Deneb:
denebData*: deneb.LightClientOptimisticUpdate
of LightClientDataFork.Electra:
electraData*: electra.LightClientOptimisticUpdate
SomeForkedLightClientUpdateWithSyncCommittee* =
ForkedLightClientUpdate
@ -145,11 +166,15 @@ type
capellaData*: capella.LightClientStore
of LightClientDataFork.Deneb:
denebData*: deneb.LightClientStore
of LightClientDataFork.Electra:
electraData*: electra.LightClientStore
func lcDataForkAtEpoch*(
cfg: RuntimeConfig, epoch: Epoch): LightClientDataFork =
static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb
if epoch >= cfg.DENEB_FORK_EPOCH:
static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
if epoch >= cfg.ELECTRA_FORK_EPOCH:
LightClientDataFork.Electra
elif epoch >= cfg.DENEB_FORK_EPOCH:
LightClientDataFork.Deneb
elif epoch >= cfg.CAPELLA_FORK_EPOCH:
LightClientDataFork.Capella
@ -188,8 +213,71 @@ template kind*(
deneb.LightClientStore]): LightClientDataFork =
LightClientDataFork.Deneb
template kind*(
x: typedesc[ # `SomeLightClientObject` doesn't work here (Nim 1.6)
electra.LightClientHeader |
electra.LightClientBootstrap |
electra.LightClientUpdate |
electra.LightClientFinalityUpdate |
electra.LightClientOptimisticUpdate |
electra.LightClientStore]): LightClientDataFork =
LightClientDataFork.Electra
template FINALIZED_ROOT_GINDEX*(
kind: static LightClientDataFork): GeneralizedIndex =
when kind >= LightClientDataFork.Electra:
electra.FINALIZED_ROOT_GINDEX
elif kind >= LightClientDataFork.Altair:
altair.FINALIZED_ROOT_GINDEX
else:
static: raiseAssert "Unreachable"
template FinalityBranch*(kind: static LightClientDataFork): auto =
when kind >= LightClientDataFork.Electra:
typedesc[electra.FinalityBranch]
elif kind >= LightClientDataFork.Altair:
typedesc[altair.FinalityBranch]
else:
static: raiseAssert "Unreachable"
template CURRENT_SYNC_COMMITTEE_GINDEX*(
kind: static LightClientDataFork): GeneralizedIndex =
when kind >= LightClientDataFork.Electra:
electra.CURRENT_SYNC_COMMITTEE_GINDEX
elif kind >= LightClientDataFork.Altair:
altair.CURRENT_SYNC_COMMITTEE_GINDEX
else:
static: raiseAssert "Unreachable"
template CurrentSyncCommitteeBranch*(kind: static LightClientDataFork): auto =
when kind >= LightClientDataFork.Electra:
typedesc[electra.CurrentSyncCommitteeBranch]
elif kind >= LightClientDataFork.Altair:
typedesc[altair.CurrentSyncCommitteeBranch]
else:
static: raiseAssert "Unreachable"
template NEXT_SYNC_COMMITTEE_GINDEX*(
kind: static LightClientDataFork): GeneralizedIndex =
when kind >= LightClientDataFork.Electra:
electra.NEXT_SYNC_COMMITTEE_GINDEX
elif kind >= LightClientDataFork.Altair:
altair.NEXT_SYNC_COMMITTEE_GINDEX
else:
static: raiseAssert "Unreachable"
template NextSyncCommitteeBranch*(kind: static LightClientDataFork): auto =
when kind >= LightClientDataFork.Electra:
typedesc[electra.NextSyncCommitteeBranch]
elif kind >= LightClientDataFork.Altair:
typedesc[altair.NextSyncCommitteeBranch]
else:
static: raiseAssert "Unreachable"
template LightClientHeader*(kind: static LightClientDataFork): auto =
when kind == LightClientDataFork.Deneb:
when kind == LightClientDataFork.Electra:
typedesc[electra.LightClientHeader]
elif kind == LightClientDataFork.Deneb:
typedesc[deneb.LightClientHeader]
elif kind == LightClientDataFork.Capella:
typedesc[capella.LightClientHeader]
@ -199,7 +287,9 @@ template LightClientHeader*(kind: static LightClientDataFork): auto =
static: raiseAssert "Unreachable"
template LightClientBootstrap*(kind: static LightClientDataFork): auto =
when kind == LightClientDataFork.Deneb:
when kind == LightClientDataFork.Electra:
typedesc[electra.LightClientBootstrap]
elif kind == LightClientDataFork.Deneb:
typedesc[deneb.LightClientBootstrap]
elif kind == LightClientDataFork.Capella:
typedesc[capella.LightClientBootstrap]
@ -209,7 +299,9 @@ template LightClientBootstrap*(kind: static LightClientDataFork): auto =
static: raiseAssert "Unreachable"
template LightClientUpdate*(kind: static LightClientDataFork): auto =
when kind == LightClientDataFork.Deneb:
when kind == LightClientDataFork.Electra:
typedesc[electra.LightClientUpdate]
elif kind == LightClientDataFork.Deneb:
typedesc[deneb.LightClientUpdate]
elif kind == LightClientDataFork.Capella:
typedesc[capella.LightClientUpdate]
@ -219,7 +311,9 @@ template LightClientUpdate*(kind: static LightClientDataFork): auto =
static: raiseAssert "Unreachable"
template LightClientFinalityUpdate*(kind: static LightClientDataFork): auto =
when kind == LightClientDataFork.Deneb:
when kind == LightClientDataFork.Electra:
typedesc[electra.LightClientFinalityUpdate]
elif kind == LightClientDataFork.Deneb:
typedesc[deneb.LightClientFinalityUpdate]
elif kind == LightClientDataFork.Capella:
typedesc[capella.LightClientFinalityUpdate]
@ -229,7 +323,9 @@ template LightClientFinalityUpdate*(kind: static LightClientDataFork): auto =
static: raiseAssert "Unreachable"
template LightClientOptimisticUpdate*(kind: static LightClientDataFork): auto =
when kind == LightClientDataFork.Deneb:
when kind == LightClientDataFork.Electra:
typedesc[electra.LightClientOptimisticUpdate]
elif kind == LightClientDataFork.Deneb:
typedesc[deneb.LightClientOptimisticUpdate]
elif kind == LightClientDataFork.Capella:
typedesc[capella.LightClientOptimisticUpdate]
@ -239,7 +335,9 @@ template LightClientOptimisticUpdate*(kind: static LightClientDataFork): auto =
static: raiseAssert "Unreachable"
template LightClientStore*(kind: static LightClientDataFork): auto =
when kind == LightClientDataFork.Deneb:
when kind == LightClientDataFork.Electra:
typedesc[electra.LightClientStore]
elif kind == LightClientDataFork.Deneb:
typedesc[deneb.LightClientStore]
elif kind == LightClientDataFork.Capella:
typedesc[capella.LightClientStore]
@ -298,7 +396,10 @@ template Forked*(x: typedesc[ForkyLightClientStore]): auto =
template withAll*(
x: typedesc[LightClientDataFork], body: untyped): untyped =
static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb
static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
block:
const lcDataFork {.inject, used.} = LightClientDataFork.Electra
body
block:
const lcDataFork {.inject, used.} = LightClientDataFork.Deneb
body
@ -315,6 +416,9 @@ template withAll*(
template withLcDataFork*(
x: LightClientDataFork, body: untyped): untyped =
case x
of LightClientDataFork.Electra:
const lcDataFork {.inject, used.} = LightClientDataFork.Electra
body
of LightClientDataFork.Deneb:
const lcDataFork {.inject, used.} = LightClientDataFork.Deneb
body
@ -331,6 +435,10 @@ template withLcDataFork*(
template withForkyHeader*(
x: ForkedLightClientHeader, body: untyped): untyped =
case x.kind
of LightClientDataFork.Electra:
const lcDataFork {.inject, used.} = LightClientDataFork.Electra
template forkyHeader: untyped {.inject, used.} = x.electraData
body
of LightClientDataFork.Deneb:
const lcDataFork {.inject, used.} = LightClientDataFork.Deneb
template forkyHeader: untyped {.inject, used.} = x.denebData
@ -350,6 +458,10 @@ template withForkyHeader*(
template withForkyBootstrap*(
x: ForkedLightClientBootstrap, body: untyped): untyped =
case x.kind
of LightClientDataFork.Electra:
const lcDataFork {.inject, used.} = LightClientDataFork.Electra
template forkyBootstrap: untyped {.inject, used.} = x.electraData
body
of LightClientDataFork.Deneb:
const lcDataFork {.inject, used.} = LightClientDataFork.Deneb
template forkyBootstrap: untyped {.inject, used.} = x.denebData
@ -369,6 +481,10 @@ template withForkyBootstrap*(
template withForkyUpdate*(
x: ForkedLightClientUpdate, body: untyped): untyped =
case x.kind
of LightClientDataFork.Electra:
const lcDataFork {.inject, used.} = LightClientDataFork.Electra
template forkyUpdate: untyped {.inject, used.} = x.electraData
body
of LightClientDataFork.Deneb:
const lcDataFork {.inject, used.} = LightClientDataFork.Deneb
template forkyUpdate: untyped {.inject, used.} = x.denebData
@ -388,6 +504,10 @@ template withForkyUpdate*(
template withForkyFinalityUpdate*(
x: ForkedLightClientFinalityUpdate, body: untyped): untyped =
case x.kind
of LightClientDataFork.Electra:
const lcDataFork {.inject, used.} = LightClientDataFork.Electra
template forkyFinalityUpdate: untyped {.inject, used.} = x.electraData
body
of LightClientDataFork.Deneb:
const lcDataFork {.inject, used.} = LightClientDataFork.Deneb
template forkyFinalityUpdate: untyped {.inject, used.} = x.denebData
@ -407,6 +527,10 @@ template withForkyFinalityUpdate*(
template withForkyOptimisticUpdate*(
x: ForkedLightClientOptimisticUpdate, body: untyped): untyped =
case x.kind
of LightClientDataFork.Electra:
const lcDataFork {.inject, used.} = LightClientDataFork.Electra
template forkyOptimisticUpdate: untyped {.inject, used.} = x.electraData
body
of LightClientDataFork.Deneb:
const lcDataFork {.inject, used.} = LightClientDataFork.Deneb
template forkyOptimisticUpdate: untyped {.inject, used.} = x.denebData
@ -426,6 +550,10 @@ template withForkyOptimisticUpdate*(
template withForkyObject*(
x: SomeForkedLightClientObject, body: untyped): untyped =
case x.kind
of LightClientDataFork.Electra:
const lcDataFork {.inject, used.} = LightClientDataFork.Electra
template forkyObject: untyped {.inject, used.} = x.electraData
body
of LightClientDataFork.Deneb:
const lcDataFork {.inject, used.} = LightClientDataFork.Deneb
template forkyObject: untyped {.inject, used.} = x.denebData
@ -445,6 +573,10 @@ template withForkyObject*(
template withForkyStore*(
x: ForkedLightClientStore, body: untyped): untyped =
case x.kind
of LightClientDataFork.Electra:
const lcDataFork {.inject, used.} = LightClientDataFork.Electra
template forkyStore: untyped {.inject, used.} = x.electraData
body
of LightClientDataFork.Deneb:
const lcDataFork {.inject, used.} = LightClientDataFork.Deneb
template forkyStore: untyped {.inject, used.} = x.denebData
@ -473,7 +605,9 @@ func init*(
type ResultType = typeof(forkyData).Forked
static: doAssert ResultType is x
const kind = typeof(forkyData).kind
when kind == LightClientDataFork.Deneb:
when kind == LightClientDataFork.Electra:
ResultType(kind: kind, electraData: forkyData)
elif kind == LightClientDataFork.Deneb:
ResultType(kind: kind, denebData: forkyData)
elif kind == LightClientDataFork.Capella:
ResultType(kind: kind, capellaData: forkyData)
@ -488,7 +622,9 @@ template forky*(
SomeForkedLightClientObject |
ForkedLightClientStore,
kind: static LightClientDataFork): untyped =
when kind == LightClientDataFork.Deneb:
when kind == LightClientDataFork.Electra:
x.electraData
elif kind == LightClientDataFork.Deneb:
x.denebData
elif kind == LightClientDataFork.Capella:
x.capellaData
@ -641,7 +777,15 @@ func migrateToDataFork*(
denebData: upgrade_lc_header_to_deneb(
x.forky(LightClientDataFork.Capella)))
static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb
# Upgrade to Electra
when newKind >= LightClientDataFork.Electra:
if x.kind == LightClientDataFork.Deneb:
x = ForkedLightClientHeader(
kind: LightClientDataFork.Electra,
electraData: upgrade_lc_header_to_electra(
x.forky(LightClientDataFork.Deneb)))
static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
doAssert x.kind == newKind
func migrateToDataFork*(
@ -676,7 +820,15 @@ func migrateToDataFork*(
denebData: upgrade_lc_bootstrap_to_deneb(
x.forky(LightClientDataFork.Capella)))
static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb
# Upgrade to Electra
when newKind >= LightClientDataFork.Electra:
if x.kind == LightClientDataFork.Deneb:
x = ForkedLightClientBootstrap(
kind: LightClientDataFork.Electra,
electraData: upgrade_lc_bootstrap_to_electra(
x.forky(LightClientDataFork.Deneb)))
static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
doAssert x.kind == newKind
func migrateToDataFork*(
@ -711,7 +863,15 @@ func migrateToDataFork*(
denebData: upgrade_lc_update_to_deneb(
x.forky(LightClientDataFork.Capella)))
static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb
# Upgrade to Electra
when newKind >= LightClientDataFork.Electra:
if x.kind == LightClientDataFork.Deneb:
x = ForkedLightClientUpdate(
kind: LightClientDataFork.Electra,
electraData: upgrade_lc_update_to_electra(
x.forky(LightClientDataFork.Deneb)))
static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
doAssert x.kind == newKind
func migrateToDataFork*(
@ -746,7 +906,15 @@ func migrateToDataFork*(
denebData: upgrade_lc_finality_update_to_deneb(
x.forky(LightClientDataFork.Capella)))
static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb
# Upgrade to Electra
when newKind >= LightClientDataFork.Electra:
if x.kind == LightClientDataFork.Deneb:
x = ForkedLightClientFinalityUpdate(
kind: LightClientDataFork.Electra,
electraData: upgrade_lc_finality_update_to_electra(
x.forky(LightClientDataFork.Deneb)))
static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
doAssert x.kind == newKind
func migrateToDataFork*(
@ -781,7 +949,15 @@ func migrateToDataFork*(
denebData: upgrade_lc_optimistic_update_to_deneb(
x.forky(LightClientDataFork.Capella)))
static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb
# Upgrade to Electra
when newKind >= LightClientDataFork.Electra:
if x.kind == LightClientDataFork.Deneb:
x = ForkedLightClientOptimisticUpdate(
kind: LightClientDataFork.Electra,
electraData: upgrade_lc_optimistic_update_to_electra(
x.forky(LightClientDataFork.Deneb)))
static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
doAssert x.kind == newKind
func migrateToDataFork*(
@ -816,7 +992,15 @@ func migrateToDataFork*(
denebData: upgrade_lc_store_to_deneb(
x.forky(LightClientDataFork.Capella)))
static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb
# Upgrade to Electra
when newKind >= LightClientDataFork.Electra:
if x.kind == LightClientDataFork.Deneb:
x = ForkedLightClientStore(
kind: LightClientDataFork.Electra,
electraData: upgrade_lc_store_to_electra(
x.forky(LightClientDataFork.Deneb)))
static: doAssert LightClientDataFork.high == LightClientDataFork.Electra
doAssert x.kind == newKind
func migratingToDataFork*[
@ -951,6 +1135,108 @@ func toDenebLightClientHeader(
execution_branch: blck.message.body.build_proof(
capella.EXECUTION_PAYLOAD_GINDEX).get)
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/full-node.md#modified-block_to_light_client_header
func toElectraLightClientHeader(
blck: # `SomeSignedBeaconBlock` doesn't work here (Nim 1.6)
phase0.SignedBeaconBlock | phase0.TrustedSignedBeaconBlock |
altair.SignedBeaconBlock | altair.TrustedSignedBeaconBlock |
bellatrix.SignedBeaconBlock | bellatrix.TrustedSignedBeaconBlock
): electra.LightClientHeader =
# Note that during fork transitions, `finalized_header` may still
# point to earlier forks. While Bellatrix blocks also contain an
# `ExecutionPayload` (minus `withdrawals_root`), it was not included
# in the corresponding light client data. To ensure compatibility
# with legacy data going through `upgrade_lc_header_to_capella`,
# leave out execution data.
electra.LightClientHeader(
beacon: blck.message.toBeaconBlockHeader())
func toElectraLightClientHeader(
blck: # `SomeSignedBeaconBlock` doesn't work here (Nim 1.6)
capella.SignedBeaconBlock | capella.TrustedSignedBeaconBlock
): electra.LightClientHeader =
template payload: untyped = blck.message.body.execution_payload
electra.LightClientHeader(
beacon: blck.message.toBeaconBlockHeader(),
execution: electra.ExecutionPayloadHeader(
parent_hash: payload.parent_hash,
fee_recipient: payload.fee_recipient,
state_root: payload.state_root,
receipts_root: payload.receipts_root,
logs_bloom: payload.logs_bloom,
prev_randao: payload.prev_randao,
block_number: payload.block_number,
gas_limit: payload.gas_limit,
gas_used: payload.gas_used,
timestamp: payload.timestamp,
extra_data: payload.extra_data,
base_fee_per_gas: payload.base_fee_per_gas,
block_hash: payload.block_hash,
transactions_root: hash_tree_root(payload.transactions),
withdrawals_root: hash_tree_root(payload.withdrawals)),
execution_branch: blck.message.body.build_proof(
capella.EXECUTION_PAYLOAD_GINDEX).get)
func toElectraLightClientHeader(
blck: # `SomeSignedBeaconBlock` doesn't work here (Nim 1.6)
deneb.SignedBeaconBlock | deneb.TrustedSignedBeaconBlock
): electra.LightClientHeader =
template payload: untyped = blck.message.body.execution_payload
electra.LightClientHeader(
beacon: blck.message.toBeaconBlockHeader(),
execution: electra.ExecutionPayloadHeader(
parent_hash: payload.parent_hash,
fee_recipient: payload.fee_recipient,
state_root: payload.state_root,
receipts_root: payload.receipts_root,
logs_bloom: payload.logs_bloom,
prev_randao: payload.prev_randao,
block_number: payload.block_number,
gas_limit: payload.gas_limit,
gas_used: payload.gas_used,
timestamp: payload.timestamp,
extra_data: payload.extra_data,
base_fee_per_gas: payload.base_fee_per_gas,
block_hash: payload.block_hash,
transactions_root: hash_tree_root(payload.transactions),
withdrawals_root: hash_tree_root(payload.withdrawals),
blob_gas_used: payload.blob_gas_used,
excess_blob_gas: payload.excess_blob_gas),
execution_branch: blck.message.body.build_proof(
capella.EXECUTION_PAYLOAD_GINDEX).get)
func toElectraLightClientHeader(
blck: # `SomeSignedBeaconBlock` doesn't work here (Nim 1.6)
electra.SignedBeaconBlock | electra.TrustedSignedBeaconBlock
): electra.LightClientHeader =
template payload: untyped = blck.message.body.execution_payload
electra.LightClientHeader(
beacon: blck.message.toBeaconBlockHeader(),
execution: electra.ExecutionPayloadHeader(
parent_hash: payload.parent_hash,
fee_recipient: payload.fee_recipient,
state_root: payload.state_root,
receipts_root: payload.receipts_root,
logs_bloom: payload.logs_bloom,
prev_randao: payload.prev_randao,
block_number: payload.block_number,
gas_limit: payload.gas_limit,
gas_used: payload.gas_used,
timestamp: payload.timestamp,
extra_data: payload.extra_data,
base_fee_per_gas: payload.base_fee_per_gas,
block_hash: payload.block_hash,
transactions_root: hash_tree_root(payload.transactions),
withdrawals_root: hash_tree_root(payload.withdrawals),
blob_gas_used: payload.blob_gas_used,
excess_blob_gas: payload.excess_blob_gas,
deposit_requests_root: hash_tree_root(payload.deposit_requests),
withdrawal_requests_root: hash_tree_root(payload.withdrawal_requests),
consolidation_requests_root:
hash_tree_root(payload.consolidation_requests)),
execution_branch: blck.message.body.build_proof(
capella.EXECUTION_PAYLOAD_GINDEX).get)
func toLightClientHeader*(
blck: # `SomeSignedBeaconBlock` doesn't work here (Nim 1.6)
phase0.SignedBeaconBlock | phase0.TrustedSignedBeaconBlock |
@ -960,9 +1246,8 @@ func toLightClientHeader*(
deneb.SignedBeaconBlock | deneb.TrustedSignedBeaconBlock |
electra.SignedBeaconBlock | electra.TrustedSignedBeaconBlock,
kind: static LightClientDataFork): auto =
when blck is electra.SignedBeaconBlock or blck is electra.TrustedSignedBeaconBlock:
debugComment "toLightClientHeader electra missing"
default(deneb.LightClientHeader)
when kind == LightClientDataFork.Electra:
blck.toElectraLightClientHeader()
elif kind == LightClientDataFork.Deneb:
blck.toDenebLightClientHeader()
elif kind == LightClientDataFork.Capella:
@ -990,9 +1275,13 @@ func shortLog*[
capellaData: typeof(x.capellaData.shortLog())
of LightClientDataFork.Deneb:
denebData: typeof(x.denebData.shortLog())
of LightClientDataFork.Electra:
electraData: typeof(x.electraData.shortLog())
let xKind = x.kind # Nim 1.6.12: Using `kind: x.kind` inside case is broken
case xKind
of LightClientDataFork.Electra:
ResultType(kind: xKind, electraData: x.electraData.shortLog())
of LightClientDataFork.Deneb:
ResultType(kind: xKind, denebData: x.denebData.shortLog())
of LightClientDataFork.Capella:

View File

@ -15,6 +15,21 @@ import
from ../consensus_object_pools/block_pools_types import VerifierError
export block_pools_types.VerifierError
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/sync-protocol.md#is_valid_normalized_merkle_branch
func is_valid_normalized_merkle_branch[N](
leaf: Eth2Digest,
branch: array[N, Eth2Digest],
gindex: static GeneralizedIndex,
root: Eth2Digest): bool =
const
depth = log2trunc(gindex)
index = get_subtree_index(gindex)
num_extra = branch.len - depth
for i in 0 ..< num_extra:
if not branch[i].isZero:
return false
is_valid_merkle_branch(leaf, branch[num_extra .. ^1], depth, index, root)
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.5/specs/altair/light-client/sync-protocol.md#initialize_light_client_store
func initialize_light_client_store*(
trusted_block_root: Eth2Digest,
@ -29,13 +44,15 @@ func initialize_light_client_store*(
if hash_tree_root(bootstrap.header.beacon) != trusted_block_root:
return ResultType.err(VerifierError.Invalid)
if not is_valid_merkle_branch(
hash_tree_root(bootstrap.current_sync_committee),
bootstrap.current_sync_committee_branch,
log2trunc(altair.CURRENT_SYNC_COMMITTEE_GINDEX),
get_subtree_index(altair.CURRENT_SYNC_COMMITTEE_GINDEX),
bootstrap.header.beacon.state_root):
return ResultType.err(VerifierError.Invalid)
withLcDataFork(lcDataForkAtConsensusFork(
cfg.consensusForkAtEpoch(bootstrap.header.beacon.slot.epoch))):
when lcDataFork > LightClientDataFork.None:
if not is_valid_normalized_merkle_branch(
hash_tree_root(bootstrap.current_sync_committee),
bootstrap.current_sync_committee_branch,
lcDataFork.CURRENT_SYNC_COMMITTEE_GINDEX,
bootstrap.header.beacon.state_root):
return ResultType.err(VerifierError.Invalid)
return ResultType.ok(typeof(bootstrap).kind.LightClientStore(
finalized_header: bootstrap.header,
@ -109,13 +126,15 @@ proc validate_light_client_update*(
finalized_root.reset()
else:
return err(VerifierError.Invalid)
if not is_valid_merkle_branch(
finalized_root,
update.finality_branch,
log2trunc(altair.FINALIZED_ROOT_GINDEX),
get_subtree_index(altair.FINALIZED_ROOT_GINDEX),
update.attested_header.beacon.state_root):
return err(VerifierError.Invalid)
withLcDataFork(lcDataForkAtConsensusFork(
cfg.consensusForkAtEpoch(update.attested_header.beacon.slot.epoch))):
when lcDataFork > LightClientDataFork.None:
if not is_valid_normalized_merkle_branch(
finalized_root,
update.finality_branch,
lcDataFork.FINALIZED_ROOT_GINDEX,
update.attested_header.beacon.state_root):
return err(VerifierError.Invalid)
# Verify that the `next_sync_committee`, if present, actually is the
# next sync committee saved in the state of the `attested_header`
@ -128,13 +147,15 @@ proc validate_light_client_update*(
if attested_period == store_period and is_next_sync_committee_known:
if update.next_sync_committee != store.next_sync_committee:
return err(VerifierError.UnviableFork)
if not is_valid_merkle_branch(
hash_tree_root(update.next_sync_committee),
update.next_sync_committee_branch,
log2trunc(altair.NEXT_SYNC_COMMITTEE_GINDEX),
get_subtree_index(altair.NEXT_SYNC_COMMITTEE_GINDEX),
update.attested_header.beacon.state_root):
return err(VerifierError.Invalid)
withLcDataFork(lcDataForkAtConsensusFork(
cfg.consensusForkAtEpoch(update.attested_header.beacon.slot.epoch))):
when lcDataFork > LightClientDataFork.None:
if not is_valid_normalized_merkle_branch(
hash_tree_root(update.next_sync_committee),
update.next_sync_committee_branch,
lcDataFork.NEXT_SYNC_COMMITTEE_GINDEX,
update.attested_header.beacon.state_root):
return err(VerifierError.Invalid)
# Verify sync committee aggregate signature
let sync_committee =

View File

@ -172,14 +172,15 @@ proc runTest(storeDataFork: static LightClientDataFork) =
# Sync committee signing the attested_header
(sync_aggregate, signature_slot) = get_sync_aggregate(cfg, forked[])
next_sync_committee = SyncCommittee()
next_sync_committee_branch = default(altair.NextSyncCommitteeBranch)
next_sync_committee_branch =
default(storeDataFork.NextSyncCommitteeBranch)
# Ensure that finality checkpoint is genesis
check state.finalized_checkpoint.epoch == 0
# Finality is unchanged
let
finality_header = default(storeDataFork.LightClientHeader)
finality_branch = default(altair.FinalityBranch)
finality_branch = default(storeDataFork.FinalityBranch)
update = storeDataFork.LightClientUpdate(
attested_header: attested_header,
@ -228,11 +229,12 @@ proc runTest(storeDataFork: static LightClientDataFork) =
# Sync committee signing the attested_header
(sync_aggregate, signature_slot) = get_sync_aggregate(cfg, forked[])
next_sync_committee = SyncCommittee()
next_sync_committee_branch = default(altair.NextSyncCommitteeBranch)
next_sync_committee_branch =
default(storeDataFork.NextSyncCommitteeBranch)
# Finality is unchanged
finality_header = default(storeDataFork.LightClientHeader)
finality_branch = default(altair.FinalityBranch)
finality_branch = default(storeDataFork.FinalityBranch)
update = storeDataFork.LightClientUpdate(
attested_header: attested_header,
@ -283,12 +285,13 @@ proc runTest(storeDataFork: static LightClientDataFork) =
# Sync committee is updated
template next_sync_committee(): auto = state.next_sync_committee
let
next_sync_committee_branch =
state.build_proof(altair.NEXT_SYNC_COMMITTEE_GINDEX).get
next_sync_committee_branch = normalize_merkle_branch(
state.build_proof(altair.NEXT_SYNC_COMMITTEE_GINDEX).get,
storeDataFork.NEXT_SYNC_COMMITTEE_GINDEX)
# Finality is unchanged
finality_header = default(storeDataFork.LightClientHeader)
finality_branch = default(altair.FinalityBranch)
finality_branch = default(storeDataFork.FinalityBranch)
update = storeDataFork.LightClientUpdate(
attested_header: attested_header,
@ -345,7 +348,8 @@ proc runTest(storeDataFork: static LightClientDataFork) =
# Updated sync_committee and finality
next_sync_committee = SyncCommittee()
next_sync_committee_branch = default(altair.NextSyncCommitteeBranch)
next_sync_committee_branch =
default(storeDataFork.NextSyncCommitteeBranch)
finalized_block = blocks[SLOTS_PER_EPOCH - 1].altairData
finalized_header = finalized_block.toLightClientHeader(storeDataFork)
check:
@ -354,7 +358,9 @@ proc runTest(storeDataFork: static LightClientDataFork) =
finalized_header.beacon.hash_tree_root() ==
state.finalized_checkpoint.root
let
finality_branch = state.build_proof(altair.FINALIZED_ROOT_GINDEX).get
finality_branch = normalize_merkle_branch(
state.build_proof(altair.FINALIZED_ROOT_GINDEX).get,
storeDataFork.FINALIZED_ROOT_GINDEX)
update = storeDataFork.LightClientUpdate(
attested_header: attested_header,