mirror of
https://github.com/status-im/nimbus-eth2.git
synced 2025-01-11 06:46:10 +00:00
Light client sync spec functions
This commit is contained in:
parent
7bbe0258d0
commit
add7daa30c
@ -19,26 +19,6 @@ import
|
|||||||
|
|
||||||
export extras, phase0, altair, merge
|
export extras, phase0, altair, merge
|
||||||
|
|
||||||
# https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#is_valid_merkle_branch
|
|
||||||
func is_valid_merkle_branch*(leaf: Eth2Digest, branch: openArray[Eth2Digest],
|
|
||||||
depth: int, index: uint64,
|
|
||||||
root: Eth2Digest): bool {.nbench.}=
|
|
||||||
## Check if ``leaf`` at ``index`` verifies against the Merkle ``root`` and
|
|
||||||
## ``branch``.
|
|
||||||
var
|
|
||||||
value = leaf
|
|
||||||
buf: array[64, byte]
|
|
||||||
|
|
||||||
for i in 0 ..< depth:
|
|
||||||
if (index div (1'u64 shl i)) mod 2 != 0:
|
|
||||||
buf[0..31] = branch[i].data
|
|
||||||
buf[32..63] = value.data
|
|
||||||
else:
|
|
||||||
buf[0..31] = value.data
|
|
||||||
buf[32..63] = branch[i].data
|
|
||||||
value = eth2digest(buf)
|
|
||||||
value == root
|
|
||||||
|
|
||||||
# https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#increase_balance
|
# https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#increase_balance
|
||||||
func increase_balance*(balance: var Gwei, delta: Gwei) =
|
func increase_balance*(balance: var Gwei, delta: Gwei) =
|
||||||
balance += delta
|
balance += delta
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
# stew/byteutils,
|
# stew/byteutils,
|
||||||
|
|
||||||
import
|
import
|
||||||
std/[macros, typetraits],
|
std/[macros, typetraits, sets, hashes],
|
||||||
chronicles,
|
chronicles,
|
||||||
stew/[assign2, bitops2],
|
stew/[assign2, bitops2],
|
||||||
json_serialization/types as jsonTypes
|
json_serialization/types as jsonTypes
|
||||||
@ -174,6 +174,12 @@ type
|
|||||||
fork_version*: Version ##\
|
fork_version*: Version ##\
|
||||||
## Fork version for the aggregate signature
|
## Fork version for the aggregate signature
|
||||||
|
|
||||||
|
# https://github.com/ethereum/consensus-specs/blob/v1.1.0-beta.4/specs/altair/sync-protocol.md#lightclientstore
|
||||||
|
LightClientStore* = object
|
||||||
|
snapshot*: LightClientSnapshot
|
||||||
|
valid_updates*: HashSet[LightClientUpdate]
|
||||||
|
## TODO: This will benefit from being an ordered set
|
||||||
|
|
||||||
# https://github.com/ethereum/consensus-specs/blob/v1.1.0-beta.4/specs/altair/beacon-chain.md#beaconstate
|
# https://github.com/ethereum/consensus-specs/blob/v1.1.0-beta.4/specs/altair/beacon-chain.md#beaconstate
|
||||||
BeaconState* = object
|
BeaconState* = object
|
||||||
# Versioning
|
# Versioning
|
||||||
@ -518,3 +524,7 @@ func shortLog*(v: SyncAggregate): auto =
|
|||||||
$(v.sync_committee_bits)
|
$(v.sync_committee_bits)
|
||||||
|
|
||||||
chronicles.formatIt SyncCommitteeMessage: shortLog(it)
|
chronicles.formatIt SyncCommitteeMessage: shortLog(it)
|
||||||
|
|
||||||
|
func hash*(x: LightClientUpdate): Hash =
|
||||||
|
hash(x.header.state_root.data)
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import
|
|||||||
# Standard lib
|
# Standard lib
|
||||||
std/[math, tables],
|
std/[math, tables],
|
||||||
# Third-party
|
# Third-party
|
||||||
stew/[byteutils, endians2],
|
stew/[byteutils, endians2, bitops2],
|
||||||
# Internal
|
# Internal
|
||||||
./datatypes/[phase0, altair, merge],
|
./datatypes/[phase0, altair, merge],
|
||||||
./eth2_merkleization, ./ssz_codec
|
./eth2_merkleization, ./ssz_codec
|
||||||
@ -47,6 +47,26 @@ template epoch*(slot: Slot): Epoch =
|
|||||||
template isEpoch*(slot: Slot): bool =
|
template isEpoch*(slot: Slot): bool =
|
||||||
(slot mod SLOTS_PER_EPOCH) == 0
|
(slot mod SLOTS_PER_EPOCH) == 0
|
||||||
|
|
||||||
|
# https://github.com/ethereum/consensus-specs/blob/v1.0.1/specs/phase0/beacon-chain.md#is_valid_merkle_branch
|
||||||
|
func is_valid_merkle_branch*(leaf: Eth2Digest, branch: openArray[Eth2Digest],
|
||||||
|
depth: int, index: uint64,
|
||||||
|
root: Eth2Digest): bool =
|
||||||
|
## Check if ``leaf`` at ``index`` verifies against the Merkle ``root`` and
|
||||||
|
## ``branch``.
|
||||||
|
var
|
||||||
|
value = leaf
|
||||||
|
buf: array[64, byte]
|
||||||
|
|
||||||
|
for i in 0 ..< depth:
|
||||||
|
if (index div (1'u64 shl i)) mod 2 != 0:
|
||||||
|
buf[0..31] = branch[i].data
|
||||||
|
buf[32..63] = value.data
|
||||||
|
else:
|
||||||
|
buf[0..31] = value.data
|
||||||
|
buf[32..63] = branch[i].data
|
||||||
|
value = eth2digest(buf)
|
||||||
|
value == root
|
||||||
|
|
||||||
const SLOTS_PER_SYNC_COMMITTEE_PERIOD* =
|
const SLOTS_PER_SYNC_COMMITTEE_PERIOD* =
|
||||||
EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH
|
EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH
|
||||||
|
|
||||||
@ -213,3 +233,8 @@ func add_flag*(flags: ParticipationFlags, flag_index: int): ParticipationFlags =
|
|||||||
func has_flag*(flags: ParticipationFlags, flag_index: int): bool =
|
func has_flag*(flags: ParticipationFlags, flag_index: int): bool =
|
||||||
let flag = ParticipationFlags(1'u8 shl flag_index)
|
let flag = ParticipationFlags(1'u8 shl flag_index)
|
||||||
(flags and flag) == flag
|
(flags and flag) == flag
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-beta.3/specs/altair/sync-protocol.md#get_subtree_index
|
||||||
|
func get_subtree_index*(idx: GeneralizedIndex): uint64 =
|
||||||
|
uint64(idx mod (type(idx)(1) shl log2trunc(idx)))
|
||||||
|
|
||||||
|
113
beacon_chain/spec/light_client_sync.nim
Normal file
113
beacon_chain/spec/light_client_sync.nim
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import
|
||||||
|
std/sets,
|
||||||
|
stew/bitops2,
|
||||||
|
datatypes/altair,
|
||||||
|
helpers
|
||||||
|
|
||||||
|
func branchIsAllZeros(branch: openarray[Eth2Digest]): bool =
|
||||||
|
for node in branch:
|
||||||
|
if node != Eth2Digest():
|
||||||
|
return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
# https://github.com/ethereum/consensus-specs/blob/v1.1.0-beta.3/specs/altair/sync-protocol.md#validate_light_client_update
|
||||||
|
proc validate_light_client_update*(snapshot: LightClientSnapshot,
|
||||||
|
update: LightClientUpdate,
|
||||||
|
genesis_validators_root: Eth2Digest): bool =
|
||||||
|
# Verify update slot is larger than snapshot slot
|
||||||
|
if update.header.slot <= snapshot.header.slot:
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Verify update does not skip a sync committee period
|
||||||
|
var snapshot_period = compute_epoch_at_slot(snapshot.header.slot) div EPOCHS_PER_SYNC_COMMITTEE_PERIOD
|
||||||
|
var update_period = compute_epoch_at_slot(update.header.slot) div EPOCHS_PER_SYNC_COMMITTEE_PERIOD
|
||||||
|
if update_period notin [snapshot_period, snapshot_period + 1]:
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Verify update header root is the finalized root of the finality header, if specified
|
||||||
|
# TODO: Use a view type instead of `unsafeAddr`
|
||||||
|
let signed_header = if update.finality_header == BeaconBlockHeader():
|
||||||
|
if not branchIsAllZeros(update.finality_branch):
|
||||||
|
return false
|
||||||
|
unsafeAddr update.header
|
||||||
|
else:
|
||||||
|
if not is_valid_merkle_branch(hash_tree_root(update.header),
|
||||||
|
update.finality_branch,
|
||||||
|
log2trunc(FINALIZED_ROOT_INDEX),
|
||||||
|
get_subtree_index(FINALIZED_ROOT_INDEX),
|
||||||
|
update.finality_header.state_root):
|
||||||
|
return false
|
||||||
|
unsafeAddr update.finality_header
|
||||||
|
|
||||||
|
# Verify update next sync committee if the update period incremented
|
||||||
|
# TODO: Use a view type instead of `unsafeAddr`
|
||||||
|
let sync_committee = if update_period == snapshot_period:
|
||||||
|
if not branchIsAllZeros(update.next_sync_committee_branch):
|
||||||
|
return false
|
||||||
|
unsafeAddr snapshot.current_sync_committee
|
||||||
|
else:
|
||||||
|
if not is_valid_merkle_branch(hash_tree_root(update.next_sync_committee),
|
||||||
|
update.next_sync_committee_branch,
|
||||||
|
log2trunc(NEXT_SYNC_COMMITTEE_INDEX),
|
||||||
|
get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX),
|
||||||
|
update.header.state_root):
|
||||||
|
return false
|
||||||
|
unsafeAddr snapshot.next_sync_committee
|
||||||
|
|
||||||
|
# Verify sync committee has sufficient participants
|
||||||
|
if countOnes(update.sync_committee_bits) < MIN_SYNC_COMMITTEE_PARTICIPANTS:
|
||||||
|
return false
|
||||||
|
|
||||||
|
# Verify sync committee aggregate signature
|
||||||
|
# participant_pubkeys = [pubkey for (bit, pubkey) in zip(update.sync_committee_bits, sync_committee.pubkeys) if bit]
|
||||||
|
var participant_pubkeys: seq[ValidatorPubKey]
|
||||||
|
for idx, bit in update.sync_committee_bits:
|
||||||
|
if bit:
|
||||||
|
participant_pubkeys.add(sync_committee.pubkeys[idx])
|
||||||
|
|
||||||
|
let domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version, genesis_validators_root)
|
||||||
|
let signing_root = compute_signing_root(signed_header[], domain)
|
||||||
|
|
||||||
|
blsFastAggregateVerify(participant_pubkeys, signing_root.data, update.sync_committee_signature)
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-beta.3/specs/altair/sync-protocol.md#apply_light_client_update
|
||||||
|
proc apply_light_client_update(snapshot: var LightClientSnapshot, update: LightClientUpdate) =
|
||||||
|
let snapshot_period = compute_epoch_at_slot(snapshot.header.slot) div EPOCHS_PER_SYNC_COMMITTEE_PERIOD
|
||||||
|
let update_period = compute_epoch_at_slot(update.header.slot) div EPOCHS_PER_SYNC_COMMITTEE_PERIOD
|
||||||
|
if update_period == snapshot_period + 1:
|
||||||
|
snapshot.current_sync_committee = snapshot.next_sync_committee
|
||||||
|
snapshot.next_sync_committee = update.next_sync_committee
|
||||||
|
snapshot.header = update.header
|
||||||
|
|
||||||
|
# https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-beta.3/specs/altair/sync-protocol.md#process_light_client_update
|
||||||
|
proc process_light_client_update(store: var LightClientStore,
|
||||||
|
update: LightClientUpdate,
|
||||||
|
current_slot: Slot,
|
||||||
|
genesis_validators_root: Eth2Digest): bool =
|
||||||
|
if not validate_light_client_update(store.snapshot, update, genesis_validators_root):
|
||||||
|
return false
|
||||||
|
store.valid_updates.incl(update)
|
||||||
|
|
||||||
|
var update_timeout = SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD
|
||||||
|
let sync_committee_participants_count = countOnes(update.sync_committee_bits)
|
||||||
|
if sync_committee_participants_count * 3 >= update.sync_committee_bits.len * 2 and
|
||||||
|
update.finality_header != BeaconBlockHeader():
|
||||||
|
# Apply update if (1) 2/3 quorum is reached and (2) we have a finality proof.
|
||||||
|
# Note that (2) means that the current light client design needs finality.
|
||||||
|
# It may be changed to re-organizable light client design. See the on-going issue eth2.0-specs#2182.
|
||||||
|
apply_light_client_update(store.snapshot, update)
|
||||||
|
store.valid_updates.clear()
|
||||||
|
elif current_slot > store.snapshot.header.slot + update_timeout:
|
||||||
|
var best_update_participants = 0
|
||||||
|
var best_update: LightClientUpdate
|
||||||
|
for update in store.valid_updates:
|
||||||
|
let update_participants = countOnes(update.sync_committee_bits)
|
||||||
|
if update_participants > best_update_participants:
|
||||||
|
best_update = update
|
||||||
|
best_update_participants = update_participants
|
||||||
|
|
||||||
|
# Forced best update when the update timeout has elapsed
|
||||||
|
apply_light_client_update(store.snapshot, best_update)
|
||||||
|
store.valid_updates.clear()
|
||||||
|
return true
|
@ -22,4 +22,4 @@ const
|
|||||||
# Sync protocol
|
# Sync protocol
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# 1
|
# 1
|
||||||
MIN_SYNC_COMMITTEE_PARTICIPANTS*: uint64 = 1
|
MIN_SYNC_COMMITTEE_PARTICIPANTS* = 1
|
||||||
|
@ -22,4 +22,4 @@ const
|
|||||||
# Sync protocol
|
# Sync protocol
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# 1
|
# 1
|
||||||
MIN_SYNC_COMMITTEE_PARTICIPANTS*: uint64 = 1
|
MIN_SYNC_COMMITTEE_PARTICIPANTS* = 1
|
||||||
|
@ -32,6 +32,8 @@ func binaryTreeHeight*(totalElements: Limit): int =
|
|||||||
bitWidth nextPow2(uint64 totalElements)
|
bitWidth nextPow2(uint64 totalElements)
|
||||||
|
|
||||||
type
|
type
|
||||||
|
GeneralizedIndex* = uint32
|
||||||
|
|
||||||
SszMerkleizerImpl = object
|
SszMerkleizerImpl = object
|
||||||
combinedChunks: ptr UncheckedArray[Eth2Digest]
|
combinedChunks: ptr UncheckedArray[Eth2Digest]
|
||||||
totalChunks: uint64
|
totalChunks: uint64
|
||||||
|
Loading…
x
Reference in New Issue
Block a user