Skip to content

Commit 8b340dd

Browse files
committed
serve libp2p protocol for light client sync
This extends the `--serve-light-client-data` launch option to serve locally collected light client data via libp2p. See ethereum/consensus-specs#2802 Backfill of historic best `LightClientUpdate` data is not implemented. To test, in `conf.nim` change `serveLightClientData`'s `defaultValue` to `true`, then run this command: ``` scripts/launch_local_testnet.sh --kill-old-processes --preset minimal \ --nodes 4 --disable-htop --stop-at-epoch 7 ``` The log files of the beacon nodes will be in the `local_testnet_data` directory. They are named `log0.txt` through `log3.txt`. The logs can be browsed for light client related messages.
1 parent 37d344a commit 8b340dd

File tree

10 files changed

+177
-12
lines changed

10 files changed

+177
-12
lines changed

beacon_chain/gossip_processing/eth2_processor.nim

+24
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ declareCounter beacon_sync_committee_contributions_received,
5858
"Number of valid sync committee contributions processed by this node"
5959
declareCounter beacon_sync_committee_contributions_dropped,
6060
"Number of invalid sync committee contributions dropped by this node", labels = ["reason"]
61+
declareCounter beacon_optimistic_light_client_updates_received,
62+
"Number of valid optimistic light client updates processed by this node"
63+
declareCounter beacon_optimistic_light_client_updates_dropped,
64+
"Number of invalid optimistic light client updates dropped by this node", labels = ["reason"]
6165

6266
const delayBuckets = [2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, Inf]
6367

@@ -528,3 +532,23 @@ proc contributionValidator*(
528532
beacon_sync_committee_contributions_dropped.inc(1, [$v.error[0]])
529533

530534
err(v.error())
535+
536+
proc optimisticLightClientUpdateValidator*(
537+
self: var Eth2Processor, src: MsgSource,
538+
optimistic_update: altair.OptimisticLightClientUpdate
539+
): Result[void, ValidationError] =
540+
logScope:
541+
optimisticUpdate = shortLog(optimistic_update)
542+
543+
debug "Optimistic light client update received"
544+
545+
let v = self.dag.validateOptimisticLightClientUpdate(optimistic_update)
546+
if v.isOk():
547+
trace "Optimistic light client update validated"
548+
549+
beacon_optimistic_light_client_updates_received.inc()
550+
else:
551+
debug "Dropping optimistic light client update", error = v.error
552+
beacon_optimistic_light_client_updates_dropped.inc(1, [$v.error[0]])
553+
554+
v

beacon_chain/gossip_processing/gossip_validation.nim

+17
Original file line numberDiff line numberDiff line change
@@ -984,3 +984,20 @@ proc validateContribution*(
984984
sig.get()
985985

986986
return ok((sig, participants))
987+
988+
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/sync-protocol.md#optimistic_light_client_update
989+
proc validateOptimisticLightClientUpdate*(
990+
dag: ChainDAGRef, optimistic_update: altair.OptimisticLightClientUpdate):
991+
Result[void, ValidationError] =
992+
template latest_local_update(): auto = dag.optimisticLightClientUpdate
993+
994+
if optimistic_update != latest_local_update:
995+
# [IGNORE] The optimistic update is not attesting to the latest block's
996+
# parent block.
997+
if optimistic_update.attested_header != latest_local_update.attested_header:
998+
return errIgnore("OptimisticLightClientUpdate: different attested block")
999+
1000+
# [REJECT] The optimistic update does not match the expected value.
1001+
return errReject("OptimisticLightClientUpdate: update does not match block")
1002+
1003+
ok()

beacon_chain/networking/eth2_network.nim

+14
Original file line numberDiff line numberDiff line change
@@ -2225,3 +2225,17 @@ proc broadcastSignedContributionAndProof*(
22252225
node: Eth2Node, msg: SignedContributionAndProof) =
22262226
let topic = getSyncCommitteeContributionAndProofTopic(node.forkDigests.altair)
22272227
node.broadcast(topic, msg)
2228+
2229+
proc broadcastOptimisticLightClientUpdate*(
2230+
node: Eth2Node, msg: altair.OptimisticLightClientUpdate) =
2231+
let
2232+
forkDigest =
2233+
if msg.fork_version == node.cfg.SHARDING_FORK_VERSION:
2234+
node.forkDigests.sharding
2235+
elif msg.fork_version == node.cfg.BELLATRIX_FORK_VERSION:
2236+
node.forkDigests.bellatrix
2237+
else:
2238+
doAssert msg.fork_version == node.cfg.ALTAIR_FORK_VERSION
2239+
node.forkDigests.altair
2240+
topic = getOptimisticLightClientUpdateTopic(forkDigest)
2241+
node.broadcast(topic, msg)

beacon_chain/nimbus_beacon_node.nim

+19
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,10 @@ proc addAltairMessageHandlers(node: BeaconNode, forkDigest: ForkDigest, slot: Sl
730730
getSyncCommitteeContributionAndProofTopic(forkDigest), basicParams)
731731
node.network.updateSyncnetsMetadata(syncnets)
732732

733+
if node.config.serveLightClientData:
734+
node.network.subscribe(
735+
getOptimisticLightClientUpdateTopic(forkDigest), basicParams)
736+
733737
proc removeAltairMessageHandlers(node: BeaconNode, forkDigest: ForkDigest) =
734738
node.removePhase0MessageHandlers(forkDigest)
735739

@@ -742,6 +746,9 @@ proc removeAltairMessageHandlers(node: BeaconNode, forkDigest: ForkDigest) =
742746
node.network.unsubscribe(
743747
getSyncCommitteeContributionAndProofTopic(forkDigest))
744748

749+
if node.config.serveLightClientData:
750+
node.network.unsubscribe(getOptimisticLightClientUpdateTopic(forkDigest))
751+
745752
proc trackSyncCommitteeTopics*(node: BeaconNode) =
746753
# TODO
747754
discard
@@ -1149,6 +1156,18 @@ proc installMessageValidators(node: BeaconNode) =
11491156
installSyncCommitteeeValidators(node.dag.forkDigests.altair)
11501157
installSyncCommitteeeValidators(node.dag.forkDigests.bellatrix)
11511158

1159+
if node.config.serveLightClientData:
1160+
template installOptimisticLightClientUpdateValidator(digest: auto) =
1161+
node.network.addValidator(
1162+
getOptimisticLightClientUpdateTopic(digest),
1163+
proc(msg: altair.OptimisticLightClientUpdate): ValidationResult =
1164+
toValidationResult(
1165+
node.processor[].optimisticLightClientUpdateValidator(
1166+
MsgSource.gossip, msg)))
1167+
1168+
installOptimisticLightClientUpdateValidator(node.dag.forkDigests.altair)
1169+
installOptimisticLightClientUpdateValidator(node.dag.forkDigests.bellatrix)
1170+
11521171
proc stop(node: BeaconNode) =
11531172
bnStatus = BeaconNodeStatus.Stopping
11541173
notice "Graceful shutdown"

beacon_chain/spec/beacon_time.nim

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# beacon_chain
2-
# Copyright (c) 2018-2021 Status Research & Development GmbH
2+
# Copyright (c) 2018-2022 Status Research & Development GmbH
33
# Licensed and distributed under either of
44
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
55
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@@ -146,6 +146,9 @@ const
146146
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/validator.md#broadcast-sync-committee-contribution
147147
syncContributionSlotOffset* = TimeDiff(nanoseconds:
148148
NANOSECONDS_PER_SLOT.int64 * 2 div INTERVALS_PER_SLOT)
149+
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/sync-protocol.md#block-proposal
150+
optimisticLightClientUpdateSlotOffset* = TimeDiff(nanoseconds:
151+
NANOSECONDS_PER_SLOT.int64 div INTERVALS_PER_SLOT)
149152

150153
func toFloatSeconds*(t: TimeDiff): float =
151154
float(t.nanoseconds) / 1_000_000_000.0
@@ -167,6 +170,8 @@ func sync_committee_message_deadline*(s: Slot): BeaconTime =
167170
s.start_beacon_time + syncCommitteeMessageSlotOffset
168171
func sync_contribution_deadline*(s: Slot): BeaconTime =
169172
s.start_beacon_time + syncContributionSlotOffset
173+
func optimistic_light_client_update_deadline*(s: Slot): BeaconTime =
174+
s.start_beacon_time + optimisticLightClientUpdateSlotOffset
170175

171176
func slotOrZero*(time: BeaconTime): Slot =
172177
let exSlot = time.toSlot

beacon_chain/spec/datatypes/altair.nim

+16-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import
2828
std/[typetraits, sets, hashes],
2929
chronicles,
30-
stew/[assign2, bitops2],
30+
stew/[assign2, bitops2, objects],
3131
"."/[base, phase0]
3232

3333
export base, sets
@@ -601,9 +601,24 @@ chronicles.formatIt SyncCommitteeContribution: shortLog(it)
601601
chronicles.formatIt ContributionAndProof: shortLog(it)
602602
chronicles.formatIt SignedContributionAndProof: shortLog(it)
603603

604+
func shortLog*(v: LightClientUpdate): auto =
605+
(
606+
attested_header: shortLog(v.attested_header),
607+
finalized_header: shortLog(v.finalized_header),
608+
num_active_participants: countOnes(v.sync_aggregate.sync_committee_bits),
609+
is_signed_by_next: v.next_sync_committee.isZeroMemory
610+
)
611+
604612
template hash*(x: LightClientUpdate): Hash =
605613
hash(x.header)
606614

615+
func shortLog*(v: OptimisticLightClientUpdate): auto =
616+
(
617+
attested_header: shortLog(v.attested_header),
618+
num_active_participants: countOnes(v.sync_aggregate.sync_committee_bits),
619+
is_signed_by_next: v.is_signed_by_next_sync_committee
620+
)
621+
607622
func clear*(info: var EpochInfo) =
608623
info.validators.setLen(0)
609624
info.balances = UnslashedParticipatingBalances()

beacon_chain/spec/network.nim

+5
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ func getSyncCommitteeContributionAndProofTopic*(forkDigest: ForkDigest): string
9494
## For subscribing and unsubscribing to/from a subnet.
9595
eth2Prefix(forkDigest) & "sync_committee_contribution_and_proof/ssz_snappy"
9696

97+
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/altair/sync-protocol.md#optimistic_light_client_update
98+
func getOptimisticLightClientUpdateTopic*(forkDigest: ForkDigest): string =
99+
## For broadcasting the latest `OptimisticLightClientUpdate` to light clients.
100+
eth2Prefix(forkDigest) & "optimistic_light_client_update/ssz_snappy"
101+
97102
func getENRForkID*(cfg: RuntimeConfig,
98103
epoch: Epoch,
99104
genesis_validators_root: Eth2Digest): ENRForkID =

beacon_chain/spec/ssz_codec.nim

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# beacon_chain
2-
# Copyright (c) 2018-2021 Status Research & Development GmbH
2+
# Copyright (c) 2018-2022 Status Research & Development GmbH
33
# Licensed and distributed under either of
44
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
55
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
@@ -17,7 +17,7 @@ export codec, base, typetraits
1717

1818
# Coding and decoding of SSZ to spec-specific types
1919

20-
template toSszType*(v: Slot|Epoch): auto = uint64(v)
20+
template toSszType*(v: Slot|Epoch|SyncCommitteePeriod): auto = uint64(v)
2121
template toSszType*(v: BlsCurveType): auto = toRaw(v)
2222
template toSszType*(v: ForkDigest|GraffitiBytes): auto = distinctBase(v)
2323
template toSszType*(v: Version): auto = distinctBase(v)
@@ -34,6 +34,9 @@ template fromSszBytes*(T: type Slot, bytes: openArray[byte]): T =
3434
template fromSszBytes*(T: type Epoch, bytes: openArray[byte]): T =
3535
T fromSszBytes(uint64, bytes)
3636

37+
template fromSszBytes*(T: type SyncCommitteePeriod, bytes: openArray[byte]): T =
38+
T fromSszBytes(uint64, bytes)
39+
3740
func fromSszBytes*(T: type ForkDigest, bytes: openArray[byte]): T {.raisesssz.} =
3841
if bytes.len != sizeof(result):
3942
raiseIncorrectSize T

beacon_chain/sync/sync_protocol.nim

+42
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@ logScope:
2121

2222
const
2323
MAX_REQUEST_BLOCKS = 1024
24+
MAX_REQUEST_LIGHT_CLIENT_UPDATES = 128
2425

2526
blockByRootLookupCost = allowedOpsPerSecondCost(50)
2627
blockResponseCost = allowedOpsPerSecondCost(100)
2728
blockByRangeLookupCost = allowedOpsPerSecondCost(20)
29+
lightClientUpdateResponseCost = allowedOpsPerSecondCost(100)
30+
lightClientUpdateByRangeLookupCost = allowedOpsPerSecondCost(20)
2831

2932
type
3033
StatusMsg* = object
@@ -356,6 +359,45 @@ p2pProtocol BeaconSync(version = 1,
356359
debug "Block root request done",
357360
peer, roots = blockRoots.len, count, found
358361

362+
proc bestLightClientUpdatesByRange(
363+
peer: Peer,
364+
startPeriod: SyncCommitteePeriod,
365+
reqCount: uint64,
366+
reqStep: uint64,
367+
response: MultipleChunksResponse[altair.LightClientUpdate])
368+
{.async, libp2pProtocol("best_light_client_updates_by_range", 1).} =
369+
trace "Received BestLightClientUpdatesByRange request",
370+
peer, startPeriod, count = reqCount, step = reqStep
371+
if reqCount > 0'u64 and reqStep > 0'u64:
372+
let
373+
dag = peer.networkState.dag
374+
headPeriod = dag.head.slot.sync_committee_period
375+
# Limit number of updates in response
376+
count =
377+
if startPeriod < headPeriod:
378+
0'u64
379+
else:
380+
min(reqCount,
381+
min(1 + (headPeriod - startPeriod) div reqStep,
382+
MAX_REQUEST_LIGHT_CLIENT_UPDATES))
383+
onePastPeriod = startPeriod + reqStep * count
384+
peer.updateRequestQuota(
385+
lightClientUpdateByRangeLookupCost +
386+
count.float * lightClientUpdateResponseCost)
387+
peer.awaitNonNegativeRequestQuota()
388+
389+
var found = 0
390+
for period in startPeriod..<onePastPeriod:
391+
let update = dag.getBestLightClientUpdateForPeriod(period)
392+
if update.isSome():
393+
await response.write(update.get)
394+
inc found
395+
396+
debug "BestLightClientUpdatesByRange request done",
397+
peer, startPeriod, count, reqStep, found
398+
else:
399+
raise newException(InvalidInputsError, "Empty range requested")
400+
359401
proc goodbye(peer: Peer,
360402
reason: uint64)
361403
{.async, libp2pProtocol("goodbye", 1).} =

beacon_chain/validators/validator_duties.nim

+29-8
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,18 @@ proc handleSyncCommitteeMessages(node: BeaconNode, head: BlockRef, slot: Slot) =
739739
asyncSpawn createAndSendSyncCommitteeMessage(node, slot, validator,
740740
subcommitteeIdx, head)
741741

742+
proc handleOptimisticLightClientUpdates(
743+
node: BeaconNode, head: BlockRef, slot: Slot, didPropose: bool) =
744+
if not didPropose:
745+
return
746+
let msg = node.dag.optimisticLightClientUpdate
747+
if msg.attested_header.slot != head.parent.bid.slot:
748+
notice "No optimistic light client update for proposed block",
749+
slot = slot, block_root = shortLog(head.root)
750+
return
751+
node.network.broadcastOptimisticLightClientUpdate(msg)
752+
notice "Sent optimistic light client update", message = shortLog(msg)
753+
742754
proc signAndSendContribution(node: BeaconNode,
743755
validator: AttachedValidator,
744756
contribution: SyncCommitteeContribution,
@@ -841,14 +853,14 @@ proc handleSyncCommitteeContributions(node: BeaconNode,
841853
slot, head, subnet_id = candidateAggregators[i].subcommitteeIdx
842854

843855
proc handleProposal(node: BeaconNode, head: BlockRef, slot: Slot):
844-
Future[BlockRef] {.async.} =
856+
Future[tuple[head: BlockRef, didPropose: bool]] {.async.} =
845857
## Perform the proposal for the given slot, iff we have a validator attached
846858
## that is supposed to do so, given the shuffling at that slot for the given
847859
## head - to compute the proposer, we need to advance a state to the given
848860
## slot
849861
let proposer = node.dag.getProposer(head, slot)
850862
if proposer.isNone():
851-
return head
863+
return (head: head, didPropose: false)
852864

853865
let
854866
proposerKey = node.dag.validatorKey(proposer.get).get().toPubKey
@@ -862,9 +874,12 @@ proc handleProposal(node: BeaconNode, head: BlockRef, slot: Slot):
862874
proposer_index = proposer.get(),
863875
proposer = shortLog(proposerKey)
864876

865-
head
877+
(head: head, didPropose: false)
866878
else:
867-
await proposeBlock(node, validator, proposer.get(), head, slot)
879+
(
880+
head: await proposeBlock(node, validator, proposer.get(), head, slot),
881+
didPropose: true
882+
)
868883

869884
proc makeAggregateAndProof*(
870885
pool: var AttestationPool, epochRef: EpochRef, slot: Slot, index: CommitteeIndex,
@@ -1052,6 +1067,7 @@ proc handleValidatorDuties*(node: BeaconNode, lastSlot, slot: Slot) {.async.} =
10521067

10531068
# Start by checking if there's work we should have done in the past that we
10541069
# can still meaningfully do
1070+
var didPropose = false
10551071
while curSlot < slot:
10561072
notice "Catching up on validator duties",
10571073
curSlot = shortLog(curSlot),
@@ -1061,7 +1077,7 @@ proc handleValidatorDuties*(node: BeaconNode, lastSlot, slot: Slot) {.async.} =
10611077
# For every slot we're catching up, we'll propose then send
10621078
# attestations - head should normally be advancing along the same branch
10631079
# in this case
1064-
head = await handleProposal(node, head, curSlot)
1080+
(head, didPropose) = await handleProposal(node, head, curSlot)
10651081

10661082
# For each slot we missed, we need to send out attestations - if we were
10671083
# proposing during this time, we'll use the newly proposed head, else just
@@ -1071,7 +1087,7 @@ proc handleValidatorDuties*(node: BeaconNode, lastSlot, slot: Slot) {.async.} =
10711087

10721088
curSlot += 1
10731089

1074-
head = await handleProposal(node, head, slot)
1090+
(head, didPropose) = await handleProposal(node, head, slot)
10751091

10761092
let
10771093
# The latest point in time when we'll be sending out attestations
@@ -1116,11 +1132,16 @@ proc handleValidatorDuties*(node: BeaconNode, lastSlot, slot: Slot) {.async.} =
11161132
node.consensusManager[].updateHead(slot)
11171133
head = node.dag.head
11181134

1119-
static: doAssert attestationSlotOffset == syncCommitteeMessageSlotOffset
1120-
1135+
static:
1136+
doAssert attestationSlotOffset == syncCommitteeMessageSlotOffset
11211137
handleAttestations(node, head, slot)
11221138
handleSyncCommitteeMessages(node, head, slot)
11231139

1140+
if node.config.serveLightClientData:
1141+
static:
1142+
doAssert attestationSlotOffset == optimisticLightClientUpdateSlotOffset
1143+
handleOptimisticLightClientUpdates(node, head, slot, didPropose)
1144+
11241145
updateValidatorMetrics(node) # the important stuff is done, update the vanity numbers
11251146

11261147
# https://github.com/ethereum/consensus-specs/blob/v1.1.8/specs/phase0/validator.md#broadcast-aggregate

0 commit comments

Comments
 (0)