send LC finality update on event stream on supermajority (#5602)

When new finality is reached without supermajority sync committee
support, trigger another event push on beacon-API and libp2p once
the finality gains supermajority support.

- https://github.com/ethereum/consensus-specs/pull/3549
This commit is contained in:
Etan Kissling 2023-11-16 19:57:15 -08:00 committed by GitHub
parent 98e969084d
commit b9a693d5fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 57 additions and 11 deletions

View File

@ -563,6 +563,11 @@ proc createLightClientUpdates(
var finalized_slot = attested_data.finalized_slot
if finalized_slot == forkyLatest.finalized_header.beacon.slot:
forkyLatest.finality_branch = attested_data.finality_branch
let old_num_active_participants =
forkyLatest.sync_aggregate.num_active_participants.uint64
if not hasSupermajoritySyncParticipation(old_num_active_participants) and
hasSupermajoritySyncParticipation(num_active_participants):
newFinality = true
elif finalized_slot < dag.tail.slot or
not load_finalized_bid(finalized_slot):
forkyLatest.finalized_header.reset()

View File

@ -19,6 +19,10 @@ type
## Latest finality update that was forwarded on libp2p gossip.
## Tracks `finality_update.finalized_header.beacon.slot`.
latestForwardedFinalityHasSupermajority*: bool
## Whether or not the latest finality update that was forwarded on
## libp2p gossip had supermajority (> 2/3) sync committee participation.
latestForwardedOptimisticSlot*: Slot
## Latest optimistic update that was forwarded on libp2p gossip.
## Tracks `optimistic_update.attested_header.beacon.slot`.

View File

@ -1371,15 +1371,28 @@ proc validateLightClientFinalityUpdate*(
pool: var LightClientPool, dag: ChainDAGRef,
finality_update: ForkedLightClientFinalityUpdate,
wallTime: BeaconTime): Result[void, ValidationError] =
# [IGNORE] The `finalized_header.beacon.slot` is greater than that of all
# previously forwarded `finality_update`s, or it matches the highest
# previously forwarded slot and also has a `sync_aggregate` indicating
# supermajority (> 2/3) sync committee participation while the previously
# forwarded `finality_update` for that slot did not indicate supermajority
let finalized_slot = withForkyFinalityUpdate(finality_update):
when lcDataFork > LightClientDataFork.None:
forkyFinalityUpdate.finalized_header.beacon.slot
else:
GENESIS_SLOT
if finalized_slot <= pool.latestForwardedFinalitySlot:
# [IGNORE] The `finalized_header.beacon.slot` is greater than that of all
# previously forwarded `finality_update`s
if finalized_slot < pool.latestForwardedFinalitySlot:
return errIgnore("LightClientFinalityUpdate: slot already forwarded")
let has_supermajority = withForkyFinalityUpdate(finality_update):
when lcDataFork > LightClientDataFork.None:
forkyFinalityUpdate.sync_aggregate.hasSupermajoritySyncParticipation
else:
false
if finalized_slot == pool.latestForwardedFinalitySlot:
if pool.latestForwardedFinalityHasSupermajority:
return errIgnore("LightClientFinalityUpdate: already have supermajority")
if not has_supermajority:
return errIgnore("LightClientFinalityUpdate: no new supermajority")
let
signature_slot = withForkyFinalityUpdate(finality_update):
@ -1400,6 +1413,7 @@ proc validateLightClientFinalityUpdate*(
return errIgnore("LightClientFinalityUpdate: not matching local")
pool.latestForwardedFinalitySlot = finalized_slot
pool.latestForwardedFinalityHasSupermajority = has_supermajority
ok()
# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.4/specs/altair/light-client/p2p-interface.md#light_client_optimistic_update

View File

@ -638,6 +638,14 @@ func init*(T: type SyncAggregate): SyncAggregate =
func num_active_participants*(v: SomeSyncAggregate): int =
countOnes(v.sync_committee_bits)
func hasSupermajoritySyncParticipation*(
num_active_participants: uint64): bool =
const max_active_participants = SYNC_COMMITTEE_SIZE.uint64
num_active_participants * 3 >= static(max_active_participants * 2)
func hasSupermajoritySyncParticipation*(v: SomeSyncAggregate): bool =
hasSupermajoritySyncParticipation(v.num_active_participants.uint64)
func shortLog*(v: SyncAggregate): auto =
$(v.sync_committee_bits)

View File

@ -303,12 +303,11 @@ template toMeta*(update: ForkedLightClientUpdate): LightClientUpdateMetadata =
func is_better_data*(new_meta, old_meta: LightClientUpdateMetadata): bool =
# Compare supermajority (> 2/3) sync committee participation
const max_active_participants = SYNC_COMMITTEE_SIZE.uint64
let
new_has_supermajority =
new_meta.num_active_participants * 3 >= max_active_participants * 2
hasSupermajoritySyncParticipation(new_meta.num_active_participants)
old_has_supermajority =
old_meta.num_active_participants * 3 >= max_active_participants * 2
hasSupermajoritySyncParticipation(old_meta.num_active_participants)
if new_has_supermajority != old_has_supermajority:
return new_has_supermajority > old_has_supermajority
if not new_has_supermajority:

View File

@ -261,6 +261,8 @@ proc isSynced*(node: BeaconNode, head: BlockRef): bool =
head.slot + node.config.syncHorizon >= wallSlot.slot
proc handleLightClientUpdates*(node: BeaconNode, slot: Slot) {.async.} =
template pool: untyped = node.lightClientPool[]
static: doAssert lightClientFinalityUpdateSlotOffset ==
lightClientOptimisticUpdateSlotOffset
let sendTime = node.beaconClock.fromNow(
@ -280,14 +282,28 @@ proc handleLightClientUpdates*(node: BeaconNode, slot: Slot) {.async.} =
if num_active_participants < MIN_SYNC_COMMITTEE_PARTICIPANTS:
return
let finalized_slot = forkyFinalityUpdate.finalized_header.beacon.slot
if finalized_slot > node.lightClientPool[].latestForwardedFinalitySlot:
let
finalized_slot =
forkyFinalityUpdate.finalized_header.beacon.slot
has_supermajority =
hasSupermajoritySyncParticipation(num_active_participants.uint64)
newFinality =
if finalized_slot > pool.latestForwardedFinalitySlot:
true
elif finalized_slot < pool.latestForwardedFinalitySlot:
false
elif pool.latestForwardedFinalityHasSupermajority:
false
else:
has_supermajority
if newFinality:
template msg(): auto = forkyFinalityUpdate
let sendResult =
await node.network.broadcastLightClientFinalityUpdate(msg)
# Optimization for message with ephemeral validity, whether sent or not
node.lightClientPool[].latestForwardedFinalitySlot = finalized_slot
pool.latestForwardedFinalitySlot = finalized_slot
pool.latestForwardedFinalityHasSupermajority = has_supermajority
if sendResult.isOk:
beacon_light_client_finality_updates_sent.inc()
@ -297,13 +313,13 @@ proc handleLightClientUpdates*(node: BeaconNode, slot: Slot) {.async.} =
error = sendResult.error()
let attested_slot = forkyFinalityUpdate.attested_header.beacon.slot
if attested_slot > node.lightClientPool[].latestForwardedOptimisticSlot:
if attested_slot > pool.latestForwardedOptimisticSlot:
let msg = forkyFinalityUpdate.toOptimistic
let sendResult =
await node.network.broadcastLightClientOptimisticUpdate(msg)
# Optimization for message with ephemeral validity, whether sent or not
node.lightClientPool[].latestForwardedOptimisticSlot = attested_slot
pool.latestForwardedOptimisticSlot = attested_slot
if sendResult.isOk:
beacon_light_client_optimistic_updates_sent.inc()