From 4919a3b4d7a4cb71f4d1dcd5c2fd2f8a6aeab972 Mon Sep 17 00:00:00 2001 From: jacobkaufmann Date: Tue, 26 Nov 2024 20:15:00 -0700 Subject: [PATCH] add initial IL gossip verification --- .../src/inclusion_list_verification.rs | 104 ++++++++++++++++++ beacon_node/beacon_chain/src/lib.rs | 1 + .../gossip_methods.rs | 36 +++++- consensus/types/src/chain_spec.rs | 23 ++++ consensus/types/src/inclusion_list.rs | 4 +- 5 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 beacon_node/beacon_chain/src/inclusion_list_verification.rs diff --git a/beacon_node/beacon_chain/src/inclusion_list_verification.rs b/beacon_node/beacon_chain/src/inclusion_list_verification.rs new file mode 100644 index 0000000000..092be0d185 --- /dev/null +++ b/beacon_node/beacon_chain/src/inclusion_list_verification.rs @@ -0,0 +1,104 @@ +use crate::{BeaconChain, BeaconChainError, BeaconChainTypes}; + +use slot_clock::SlotClock; +use strum::AsRefStr; +use types::{Domain, EthSpec, SignedInclusionList, SignedRoot, Slot}; + +#[derive(Debug, AsRefStr)] +pub enum GossipInclusionListError { + FutureSlot { + message_slot: Slot, + latest_permissible_slot: Slot, + }, + PastSlot { + message_slot: Slot, + earliest_permissible_slot: Slot, + }, + InvalidCommitteeRoot, + ValidatorNotInCommittee, + TooManyTransactions, + InvalidSignature, + BeaconChainError(BeaconChainError), + // TODO: equivocation e.g. PriorInclusionListKnown +} + +impl From for GossipInclusionListError { + fn from(value: BeaconChainError) -> Self { + Self::BeaconChainError(value) + } +} + +pub struct GossipVerifiedInclusionList { + signed_il: SignedInclusionList, +} + +impl GossipVerifiedInclusionList { + pub fn verify( + signed_il: &SignedInclusionList, + chain: &BeaconChain, + ) -> Result { + // the slot is equal to the previous slot or the current slot + let message_slot = signed_il.message.slot; + let earliest_permissible_slot = chain + .slot_clock + .now_with_past_tolerance(chain.spec.maximum_gossip_clock_disparity()) + .ok_or(BeaconChainError::UnableToReadSlot)?; + if message_slot < earliest_permissible_slot { + return Err(GossipInclusionListError::PastSlot { + message_slot, + earliest_permissible_slot, + }); + } + let latest_permissible_slot = chain + .slot_clock + .now_with_future_tolerance(chain.spec.maximum_gossip_clock_disparity()) + .ok_or(BeaconChainError::UnableToReadSlot)?; + if message_slot > latest_permissible_slot { + return Err(GossipInclusionListError::FutureSlot { + message_slot, + latest_permissible_slot, + }); + } + + // TODO: the slot is equal to the current slot or the previous slot and the current time is + // not past the attestation deadline + + // TODO: the IL committee root is equal to the hash tree root of the expected committee + + // TODO: the validator index is contained in the committee corresponding to the committee + // root + + // the transaction length is less than or equal to the specified maximum + if signed_il.message.transactions.len() > T::EthSpec::max_transactions_per_inclusion_list() + { + return Err(GossipInclusionListError::TooManyTransactions); + } + + // TODO: the message is the first or second valid message received from the validator + // corresponding to the validator index + + // the signature is valid w.r.t. the validator index + let epoch = chain.epoch()?; + let fork = chain.spec.fork_at_epoch(epoch); + let genesis_validators_root = chain.genesis_validators_root; + let domain = chain.spec.get_domain( + epoch, + Domain::InclusionListCommittee, + &fork, + genesis_validators_root, + ); + let message = signed_il.message.signing_root(domain); + let validator_index = signed_il.message.validator_index as usize; + let pubkey = chain.validator_pubkey(validator_index)?; + let Some(pubkey) = pubkey else { + return Err(GossipInclusionListError::BeaconChainError( + BeaconChainError::ValidatorIndexUnknown(validator_index), + )); + }; + signed_il.signature.verify(&pubkey, message); + + Ok(Self { + signed_il: signed_il.clone(), + }) + } +} diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 2953516fb1..fcb6ffa124 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -34,6 +34,7 @@ pub mod fork_revert; pub mod graffiti_calculator; mod head_tracker; pub mod historical_blocks; +pub mod inclusion_list_verification; pub mod kzg_utils; pub mod light_client_finality_update_verification; pub mod light_client_optimistic_update_verification; diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index e92f450476..71e02fa15b 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -7,6 +7,9 @@ use crate::{ use beacon_chain::blob_verification::{GossipBlobError, GossipVerifiedBlob}; use beacon_chain::block_verification_types::AsBlock; use beacon_chain::data_column_verification::{GossipDataColumnError, GossipVerifiedDataColumn}; +use beacon_chain::inclusion_list_verification::{ + GossipInclusionListError, GossipVerifiedInclusionList, +}; use beacon_chain::store::Error; use beacon_chain::{ attestation_verification::{self, Error as AttnError, VerifiedAttestation}, @@ -36,8 +39,8 @@ use types::{ DataColumnSidecar, DataColumnSubnetId, EthSpec, Hash256, IndexedAttestation, LightClientFinalityUpdate, LightClientOptimisticUpdate, ProposerSlashing, SignedAggregateAndProof, SignedBeaconBlock, SignedBlsToExecutionChange, - SignedContributionAndProof, SignedVoluntaryExit, Slot, SubnetId, SyncCommitteeMessage, - SyncSubnetId, + SignedContributionAndProof, SignedInclusionList, SignedVoluntaryExit, Slot, SubnetId, + SyncCommitteeMessage, SyncSubnetId, }; use beacon_processor::{ @@ -2139,6 +2142,35 @@ impl NetworkBeaconProcessor { }; } + pub fn process_gossip_inclusion_list( + self: &Arc, + message_id: MessageId, + peer_id: PeerId, + il: SignedInclusionList, + seen_timestamp: Duration, + ) { + match GossipVerifiedInclusionList::verify(&il, &self.chain) { + Ok(gossip_verified_il) => { + debug!(self.log, "Successfully verified gossip inclusion list"); + } + Err(err) => match err { + GossipInclusionListError::FutureSlot { .. } + | GossipInclusionListError::PastSlot { .. } + | GossipInclusionListError::ValidatorNotInCommittee + | GossipInclusionListError::TooManyTransactions + | GossipInclusionListError::InvalidSignature => { + debug!(self.log, "Could not verify inclusion list for gossip. Rejecting the inclusion list"; "error" => ?err); + } + GossipInclusionListError::InvalidCommitteeRoot => { + debug!(self.log, "Could not verify inclusion list for gossip. Ignoring the inclusion list"; "error" => ?err); + } + GossipInclusionListError::BeaconChainError(_) => { + crit!(self.log, "Internal error when verifying inclusion list"; "error" => ?err); + } + }, + } + } + /// Handle an error whilst verifying an `Attestation` or `SignedAggregateAndProof` from the /// network. fn handle_attestation_verification_failure( diff --git a/consensus/types/src/chain_spec.rs b/consensus/types/src/chain_spec.rs index 1c4effb4ae..46fbd11f2d 100644 --- a/consensus/types/src/chain_spec.rs +++ b/consensus/types/src/chain_spec.rs @@ -27,6 +27,7 @@ pub enum Domain { ContributionAndProof, SyncCommitteeSelectionProof, ApplicationMask(ApplicationDomain), + InclusionListCommittee, } /// Lighthouse's internal configuration struct. @@ -187,6 +188,11 @@ pub struct ChainSpec { pub min_per_epoch_churn_limit_electra: u64, pub max_per_epoch_activation_exit_churn_limit: u64, + /* + * FOCIL params + */ + pub domain_inclusion_list_committee: u32, + /* * DAS params */ @@ -475,6 +481,7 @@ impl ChainSpec { Domain::SyncCommitteeSelectionProof => self.domain_sync_committee_selection_proof, Domain::ApplicationMask(application_domain) => application_domain.get_domain_constant(), Domain::BlsToExecutionChange => self.domain_bls_to_execution_change, + Domain::InclusionListCommittee => self.domain_inclusion_list_committee, } } @@ -797,6 +804,11 @@ impl ChainSpec { }) .expect("calculation does not overflow"), + /* + * FOCIL params + */ + domain_inclusion_list_committee: 13, + /* * DAS params */ @@ -1115,6 +1127,11 @@ impl ChainSpec { }) .expect("calculation does not overflow"), + /* + * FOCIL params + */ + domain_inclusion_list_committee: 13, + /* * DAS params */ @@ -1951,6 +1968,12 @@ mod tests { spec.domain_bls_to_execution_change, &spec, ); + + test_domain( + Domain::InclusionListCommittee, + spec.domain_inclusion_list_committee, + &spec, + ); } fn apply_bit_mask(domain_bytes: [u8; 4], spec: &ChainSpec) -> u32 { diff --git a/consensus/types/src/inclusion_list.rs b/consensus/types/src/inclusion_list.rs index 3a8bb19970..016d0bc2bb 100644 --- a/consensus/types/src/inclusion_list.rs +++ b/consensus/types/src/inclusion_list.rs @@ -1,5 +1,5 @@ use crate::test_utils::TestRandom; -use crate::{EthSpec, Hash256, Signature, Slot, Transaction}; +use crate::{EthSpec, Hash256, Signature, SignedRoot, Slot, Transaction}; use derivative::Derivative; use serde::{Deserialize, Serialize}; @@ -33,6 +33,8 @@ pub struct InclusionList { VariableList, E::MaxTransactionsPerInclusionList>, } +impl SignedRoot for InclusionList {} + #[derive( Debug, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, Derivative, arbitrary::Arbitrary, )]