diff --git a/beacon_chain/consensus_object_pools/blockchain_dag_light_client.nim b/beacon_chain/consensus_object_pools/blockchain_dag_light_client.nim index 5c92add17..3b00095d5 100644 --- a/beacon_chain/consensus_object_pools/blockchain_dag_light_client.nim +++ b/beacon_chain/consensus_object_pools/blockchain_dag_light_client.nim @@ -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() diff --git a/beacon_chain/consensus_object_pools/light_client_pool.nim b/beacon_chain/consensus_object_pools/light_client_pool.nim index aaafe532a..b54cbbc8a 100644 --- a/beacon_chain/consensus_object_pools/light_client_pool.nim +++ b/beacon_chain/consensus_object_pools/light_client_pool.nim @@ -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`. diff --git a/beacon_chain/gossip_processing/gossip_validation.nim b/beacon_chain/gossip_processing/gossip_validation.nim index 3fe7e0fdd..c2ed5e948 100644 --- a/beacon_chain/gossip_processing/gossip_validation.nim +++ b/beacon_chain/gossip_processing/gossip_validation.nim @@ -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 diff --git a/beacon_chain/spec/datatypes/altair.nim b/beacon_chain/spec/datatypes/altair.nim index 33ced1551..ddfd97c92 100644 --- a/beacon_chain/spec/datatypes/altair.nim +++ b/beacon_chain/spec/datatypes/altair.nim @@ -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) diff --git a/beacon_chain/spec/helpers.nim b/beacon_chain/spec/helpers.nim index 3b1df40e1..e49619e99 100644 --- a/beacon_chain/spec/helpers.nim +++ b/beacon_chain/spec/helpers.nim @@ -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: diff --git a/beacon_chain/validators/beacon_validators.nim b/beacon_chain/validators/beacon_validators.nim index df1c744f2..929c7e6dc 100644 --- a/beacon_chain/validators/beacon_validators.nim +++ b/beacon_chain/validators/beacon_validators.nim @@ -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()