diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 6f409fdadc0..e8b8a22eb9f 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -22,6 +22,12 @@ use crate::execution_payload::{get_execution_payload, PreparePayloadHandle}; use crate::fork_choice_signal::{ForkChoiceSignalRx, ForkChoiceSignalTx, ForkChoiceWaitResult}; use crate::head_tracker::HeadTracker; use crate::historical_blocks::HistoricalBlockError; +use crate::light_client_finality_update_verification::{ + Error as LightClientFinalityUpdateError, VerifiedLightClientFinalityUpdate, +}; +use crate::light_client_optimistic_update_verification::{ + Error as LightClientOptimisticUpdateError, VerifiedLightClientOptimisticUpdate, +}; use crate::migrate::BackgroundMigrator; use crate::naive_aggregation_pool::{ AggregatedAttestationMap, Error as NaiveAggregationError, NaiveAggregationPool, @@ -335,6 +341,10 @@ pub struct BeaconChain { /// Maintains a record of which validators we've seen attester slashings for. pub(crate) observed_attester_slashings: Mutex, T::EthSpec>>, + /// The most recently validated light client finality update received on gossip. + pub latest_seen_finality_update: Mutex>>, + /// The most recently validated light client optimistic update received on gossip. + pub latest_seen_optimistic_update: Mutex>>, /// Provides information from the Ethereum 1 (PoW) chain. pub eth1_chain: Option>, /// Interfaces with the execution client. @@ -1819,6 +1829,40 @@ impl BeaconChain { }) } + /// Accepts some 'LightClientFinalityUpdate' from the network and attempts to verify it + pub fn verify_finality_update_for_gossip( + self: &Arc, + light_client_finality_update: LightClientFinalityUpdate, + seen_timestamp: Duration, + ) -> Result, LightClientFinalityUpdateError> { + VerifiedLightClientFinalityUpdate::verify( + light_client_finality_update, + self, + seen_timestamp, + ) + .map(|v| { + metrics::inc_counter(&metrics::FINALITY_UPDATE_PROCESSING_SUCCESSES); + v + }) + } + + /// Accepts some 'LightClientOptimisticUpdate' from the network and attempts to verify it + pub fn verify_optimistic_update_for_gossip( + self: &Arc, + light_client_optimistic_update: LightClientOptimisticUpdate, + seen_timestamp: Duration, + ) -> Result, LightClientOptimisticUpdateError> { + VerifiedLightClientOptimisticUpdate::verify( + light_client_optimistic_update, + self, + seen_timestamp, + ) + .map(|v| { + metrics::inc_counter(&metrics::OPTIMISTIC_UPDATE_PROCESSING_SUCCESSES); + v + }) + } + /// Accepts some attestation-type object and attempts to verify it in the context of fork /// choice. If it is valid it is applied to `self.fork_choice`. /// diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index 58bbb2b5c6a..f5bd85dec28 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -780,6 +780,8 @@ where observed_voluntary_exits: <_>::default(), observed_proposer_slashings: <_>::default(), observed_attester_slashings: <_>::default(), + latest_seen_finality_update: <_>::default(), + latest_seen_optimistic_update: <_>::default(), eth1_chain: self.eth1_chain, execution_layer: self.execution_layer, genesis_validators_root, diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 5ead5311e59..0d888f4d33f 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -21,6 +21,8 @@ pub mod fork_choice_signal; pub mod fork_revert; mod head_tracker; pub mod historical_blocks; +pub mod light_client_finality_update_verification; +pub mod light_client_optimistic_update_verification; pub mod merge_readiness; mod metrics; pub mod migrate; diff --git a/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs b/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs new file mode 100644 index 00000000000..7c431ebccca --- /dev/null +++ b/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs @@ -0,0 +1,135 @@ +use crate::{ + beacon_chain::MAXIMUM_GOSSIP_CLOCK_DISPARITY, BeaconChain, BeaconChainError, BeaconChainTypes, +}; +use derivative::Derivative; +use slot_clock::SlotClock; +use std::time::Duration; +use strum::AsRefStr; +use types::{ + light_client_update::Error as LightClientUpdateError, LightClientFinalityUpdate, Slot, +}; + +/// Returned when a light client finality update was not successfully verified. It might not have been verified for +/// two reasons: +/// +/// - The light client finality message is malformed or inappropriate for the context (indicated by all variants +/// other than `BeaconChainError`). +/// - The application encountered an internal error whilst attempting to determine validity +/// (the `BeaconChainError` variant) +#[derive(Debug, AsRefStr)] +pub enum Error { + /// Light client finality update message with a lower or equal finalized_header slot already forwarded. + FinalityUpdateAlreadySeen, + /// The light client finality message was received is prior to one-third of slot duration passage. (with + /// respect to the gossip clock disparity and slot clock duration). + /// + /// ## Peer scoring + /// + /// Assuming the local clock is correct, the peer has sent an invalid message. + TooEarly, + /// Light client finality update message does not match the locally constructed one. + /// + /// ## Peer Scoring + /// + InvalidLightClientFinalityUpdate, + /// Signature slot start time is none. + SigSlotStartIsNone, + /// Failed to construct a LightClientFinalityUpdate from state. + FailedConstructingUpdate, + /// Beacon chain error occured. + BeaconChainError(BeaconChainError), + LightClientUpdateError(LightClientUpdateError), +} + +impl From for Error { + fn from(e: BeaconChainError) -> Self { + Error::BeaconChainError(e) + } +} + +impl From for Error { + fn from(e: LightClientUpdateError) -> Self { + Error::LightClientUpdateError(e) + } +} + +/// Wraps a `LightClientFinalityUpdate` that has been verified for propagation on the gossip network. +#[derive(Derivative)] +#[derivative(Clone(bound = "T: BeaconChainTypes"))] +pub struct VerifiedLightClientFinalityUpdate { + light_client_finality_update: LightClientFinalityUpdate, + seen_timestamp: Duration, +} + +impl VerifiedLightClientFinalityUpdate { + /// Returns `Ok(Self)` if the `light_client_finality_update` is valid to be (re)published on the gossip + /// network. + pub fn verify( + light_client_finality_update: LightClientFinalityUpdate, + chain: &BeaconChain, + seen_timestamp: Duration, + ) -> Result { + let gossiped_finality_slot = light_client_finality_update.finalized_header.slot; + let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0); + let signature_slot = light_client_finality_update.signature_slot; + let start_time = chain.slot_clock.start_of(signature_slot); + let mut latest_seen_finality_update = chain.latest_seen_finality_update.lock(); + + let head = chain.canonical_head.cached_head(); + let head_block = &head.snapshot.beacon_block; + let attested_block_root = head_block.message().parent_root(); + let attested_block = chain + .get_blinded_block(&attested_block_root)? + .ok_or(Error::FailedConstructingUpdate)?; + let mut attested_state = chain + .get_state(&attested_block.state_root(), Some(attested_block.slot()))? + .ok_or(Error::FailedConstructingUpdate)?; + + let finalized_block_root = attested_state.finalized_checkpoint().root; + let finalized_block = chain + .get_blinded_block(&finalized_block_root)? + .ok_or(Error::FailedConstructingUpdate)?; + let latest_seen_finality_update_slot = match latest_seen_finality_update.as_ref() { + Some(update) => update.finalized_header.slot, + None => Slot::new(0), + }; + + // verify that no other finality_update with a lower or equal + // finalized_header.slot was already forwarded on the network + if gossiped_finality_slot <= latest_seen_finality_update_slot { + return Err(Error::FinalityUpdateAlreadySeen); + } + + // verify that enough time has passed for the block to have been propagated + match start_time { + Some(time) => { + if seen_timestamp + MAXIMUM_GOSSIP_CLOCK_DISPARITY < time + one_third_slot_duration + { + return Err(Error::TooEarly); + } + } + None => return Err(Error::SigSlotStartIsNone), + } + + let head_state = &head.snapshot.beacon_state; + let finality_update = LightClientFinalityUpdate::new( + &chain.spec, + head_state, + head_block, + &mut attested_state, + &finalized_block, + )?; + + // verify that the gossiped finality update is the same as the locally constructed one. + if finality_update != light_client_finality_update { + return Err(Error::InvalidLightClientFinalityUpdate); + } + + *latest_seen_finality_update = Some(light_client_finality_update.clone()); + + Ok(Self { + light_client_finality_update, + seen_timestamp, + }) + } +} diff --git a/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs b/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs new file mode 100644 index 00000000000..ec9c90e7355 --- /dev/null +++ b/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs @@ -0,0 +1,125 @@ +use crate::{ + beacon_chain::MAXIMUM_GOSSIP_CLOCK_DISPARITY, BeaconChain, BeaconChainError, BeaconChainTypes, +}; +use derivative::Derivative; +use slot_clock::SlotClock; +use std::time::Duration; +use strum::AsRefStr; +use types::{ + light_client_update::Error as LightClientUpdateError, LightClientOptimisticUpdate, Slot, +}; + +/// Returned when a light client optimistic update was not successfully verified. It might not have been verified for +/// two reasons: +/// +/// - The light client optimistic message is malformed or inappropriate for the context (indicated by all variants +/// other than `BeaconChainError`). +/// - The application encountered an internal error whilst attempting to determine validity +/// (the `BeaconChainError` variant) +#[derive(Debug, AsRefStr)] +pub enum Error { + /// Light client optimistic update message with a lower or equal optimistic_header slot already forwarded. + OptimisticUpdateAlreadySeen, + /// The light client optimistic message was received is prior to one-third of slot duration passage. (with + /// respect to the gossip clock disparity and slot clock duration). + /// + /// ## Peer scoring + /// + /// Assuming the local clock is correct, the peer has sent an invalid message. + TooEarly, + /// Light client optimistic update message does not match the locally constructed one. + /// + /// ## Peer Scoring + /// + InvalidLightClientOptimisticUpdate, + /// Signature slot start time is none. + SigSlotStartIsNone, + /// Failed to construct a LightClientOptimisticUpdate from state. + FailedConstructingUpdate, + /// Beacon chain error occured. + BeaconChainError(BeaconChainError), + LightClientUpdateError(LightClientUpdateError), +} + +impl From for Error { + fn from(e: BeaconChainError) -> Self { + Error::BeaconChainError(e) + } +} + +impl From for Error { + fn from(e: LightClientUpdateError) -> Self { + Error::LightClientUpdateError(e) + } +} + +/// Wraps a `LightClientOptimisticUpdate` that has been verified for propagation on the gossip network. +#[derive(Derivative)] +#[derivative(Clone(bound = "T: BeaconChainTypes"))] +pub struct VerifiedLightClientOptimisticUpdate { + light_client_optimistic_update: LightClientOptimisticUpdate, + seen_timestamp: Duration, +} + +impl VerifiedLightClientOptimisticUpdate { + /// Returns `Ok(Self)` if the `light_client_optimistic_update` is valid to be (re)published on the gossip + /// network. + pub fn verify( + light_client_optimistic_update: LightClientOptimisticUpdate, + chain: &BeaconChain, + seen_timestamp: Duration, + ) -> Result { + let gossiped_optimistic_slot = light_client_optimistic_update.attested_header.slot; + let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0); + let signature_slot = light_client_optimistic_update.signature_slot; + let start_time = chain.slot_clock.start_of(signature_slot); + let mut latest_seen_optimistic_update = chain.latest_seen_optimistic_update.lock(); + + let head = chain.canonical_head.cached_head(); + let head_block = &head.snapshot.beacon_block; + let attested_block_root = head_block.message().parent_root(); + let attested_block = chain + .get_blinded_block(&attested_block_root)? + .ok_or(Error::FailedConstructingUpdate)?; + + let attested_state = chain + .get_state(&attested_block.state_root(), Some(attested_block.slot()))? + .ok_or(Error::FailedConstructingUpdate)?; + let latest_seen_optimistic_update_slot = match latest_seen_optimistic_update.as_ref() { + Some(update) => update.attested_header.slot, + None => Slot::new(0), + }; + + // verify that no other optimistic_update with a lower or equal + // optimistic_header.slot was already forwarded on the network + if gossiped_optimistic_slot <= latest_seen_optimistic_update_slot { + return Err(Error::OptimisticUpdateAlreadySeen); + } + + // verify that enough time has passed for the block to have been propagated + match start_time { + Some(time) => { + if seen_timestamp + MAXIMUM_GOSSIP_CLOCK_DISPARITY < time + one_third_slot_duration + { + return Err(Error::TooEarly); + } + } + None => return Err(Error::SigSlotStartIsNone), + } + + let optimistic_update = + LightClientOptimisticUpdate::new(&chain.spec, head_block, &attested_state)?; + + // verify that the gossiped optimistic update is the same as the locally constructed one. + if optimistic_update != light_client_optimistic_update { + return Err(Error::InvalidLightClientOptimisticUpdate); + } + + *latest_seen_optimistic_update = Some(light_client_optimistic_update.clone()); + + Ok(Self { + light_client_optimistic_update, + seen_timestamp, + }) + } +} diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index ead4a540254..f42ada20f59 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -943,6 +943,24 @@ lazy_static! { ); } +// Fifth lazy-static block is used to account for macro recursion limit. +lazy_static! { + /* + * Light server message verification + */ + pub static ref FINALITY_UPDATE_PROCESSING_SUCCESSES: Result = try_create_int_counter( + "light_client_finality_update_verification_success_total", + "Number of light client finality updates verified for gossip" + ); + /* + * Light server message verification + */ + pub static ref OPTIMISTIC_UPDATE_PROCESSING_SUCCESSES: Result = try_create_int_counter( + "light_client_optimistic_update_verification_success_total", + "Number of light client optimistic updates verified for gossip" + ); +} + /// Scrape the `beacon_chain` for metrics that are not constantly updated (e.g., the present slot, /// head state info, etc) and update the Prometheus `DEFAULT_REGISTRY`. pub fn scrape_for_metrics(beacon_chain: &BeaconChain) { diff --git a/beacon_node/lighthouse_network/src/service/gossip_cache.rs b/beacon_node/lighthouse_network/src/service/gossip_cache.rs index 4842605f7aa..c784191cd30 100644 --- a/beacon_node/lighthouse_network/src/service/gossip_cache.rs +++ b/beacon_node/lighthouse_network/src/service/gossip_cache.rs @@ -34,6 +34,10 @@ pub struct GossipCache { signed_contribution_and_proof: Option, /// Timeout for sync committee messages. sync_committee_message: Option, + /// Timeout for light client finality updates. + light_client_finality_update: Option, + /// Timeout for light client optimistic updates. + light_client_optimistic_update: Option, } #[derive(Default)] @@ -55,6 +59,10 @@ pub struct GossipCacheBuilder { signed_contribution_and_proof: Option, /// Timeout for sync committee messages. sync_committee_message: Option, + /// Timeout for light client finality updates. + light_client_finality_update: Option, + /// Timeout for light client optimistic updates. + light_client_optimistic_update: Option, } #[allow(dead_code)] @@ -113,6 +121,18 @@ impl GossipCacheBuilder { self } + /// Timeout for light client finality update messages. + pub fn light_client_finality_update_timeout(mut self, timeout: Duration) -> Self { + self.light_client_finality_update = Some(timeout); + self + } + + /// Timeout for light client optimistic update messages. + pub fn light_client_optimistic_update_timeout(mut self, timeout: Duration) -> Self { + self.light_client_optimistic_update = Some(timeout); + self + } + pub fn build(self) -> GossipCache { let GossipCacheBuilder { default_timeout, @@ -124,6 +144,8 @@ impl GossipCacheBuilder { attester_slashing, signed_contribution_and_proof, sync_committee_message, + light_client_finality_update, + light_client_optimistic_update, } = self; GossipCache { expirations: DelayQueue::default(), @@ -136,6 +158,8 @@ impl GossipCacheBuilder { attester_slashing: attester_slashing.or(default_timeout), signed_contribution_and_proof: signed_contribution_and_proof.or(default_timeout), sync_committee_message: sync_committee_message.or(default_timeout), + light_client_finality_update: light_client_finality_update.or(default_timeout), + light_client_optimistic_update: light_client_optimistic_update.or(default_timeout), } } } @@ -158,6 +182,8 @@ impl GossipCache { GossipKind::AttesterSlashing => self.attester_slashing, GossipKind::SignedContributionAndProof => self.signed_contribution_and_proof, GossipKind::SyncCommitteeMessage(_) => self.sync_committee_message, + GossipKind::LightClientFinalityUpdate => self.light_client_finality_update, + GossipKind::LightClientOptimisticUpdate => self.light_client_optimistic_update, }; let expire_timeout = match expire_timeout { Some(expire_timeout) => expire_timeout, diff --git a/beacon_node/lighthouse_network/src/service/utils.rs b/beacon_node/lighthouse_network/src/service/utils.rs index 8073ae77683..09a8d1a8636 100644 --- a/beacon_node/lighthouse_network/src/service/utils.rs +++ b/beacon_node/lighthouse_network/src/service/utils.rs @@ -253,6 +253,8 @@ pub(crate) fn create_whitelist_filter( add(ProposerSlashing); add(AttesterSlashing); add(SignedContributionAndProof); + add(LightClientFinalityUpdate); + add(LightClientOptimisticUpdate); for id in 0..attestation_subnet_count { add(Attestation(SubnetId::new(id))); } diff --git a/beacon_node/lighthouse_network/src/types/mod.rs b/beacon_node/lighthouse_network/src/types/mod.rs index ad02e07fb70..2a5ca6c8062 100644 --- a/beacon_node/lighthouse_network/src/types/mod.rs +++ b/beacon_node/lighthouse_network/src/types/mod.rs @@ -16,4 +16,7 @@ pub use globals::NetworkGlobals; pub use pubsub::{PubsubMessage, SnappyTransform}; pub use subnet::{Subnet, SubnetDiscovery}; pub use sync_state::{BackFillState, SyncState}; -pub use topics::{subnet_from_topic_hash, GossipEncoding, GossipKind, GossipTopic, CORE_TOPICS}; +pub use topics::{ + subnet_from_topic_hash, GossipEncoding, GossipKind, GossipTopic, CORE_TOPICS, + LIGHT_CLIENT_GOSSIP_TOPICS, +}; diff --git a/beacon_node/lighthouse_network/src/types/pubsub.rs b/beacon_node/lighthouse_network/src/types/pubsub.rs index a01072f8e4e..b036e558c99 100644 --- a/beacon_node/lighthouse_network/src/types/pubsub.rs +++ b/beacon_node/lighthouse_network/src/types/pubsub.rs @@ -9,10 +9,10 @@ use std::boxed::Box; use std::io::{Error, ErrorKind}; use std::sync::Arc; use types::{ - Attestation, AttesterSlashing, EthSpec, ForkContext, ForkName, ProposerSlashing, - SignedAggregateAndProof, SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, - SignedBeaconBlockMerge, SignedContributionAndProof, SignedVoluntaryExit, SubnetId, - SyncCommitteeMessage, SyncSubnetId, + Attestation, AttesterSlashing, EthSpec, ForkContext, ForkName, LightClientFinalityUpdate, + LightClientOptimisticUpdate, ProposerSlashing, SignedAggregateAndProof, SignedBeaconBlock, + SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockMerge, + SignedContributionAndProof, SignedVoluntaryExit, SubnetId, SyncCommitteeMessage, SyncSubnetId, }; #[derive(Debug, Clone, PartialEq)] @@ -33,6 +33,10 @@ pub enum PubsubMessage { SignedContributionAndProof(Box>), /// Gossipsub message providing notification of unaggregated sync committee signatures with its subnet id. SyncCommitteeMessage(Box<(SyncSubnetId, SyncCommitteeMessage)>), + /// Gossipsub message providing notification of a light client finality update. + LightClientFinalityUpdate(Box>), + /// Gossipsub message providing notification of a light client optimistic update. + LightClientOptimisticUpdate(Box>), } // Implements the `DataTransform` trait of gossipsub to employ snappy compression @@ -115,6 +119,10 @@ impl PubsubMessage { PubsubMessage::AttesterSlashing(_) => GossipKind::AttesterSlashing, PubsubMessage::SignedContributionAndProof(_) => GossipKind::SignedContributionAndProof, PubsubMessage::SyncCommitteeMessage(data) => GossipKind::SyncCommitteeMessage(data.0), + PubsubMessage::LightClientFinalityUpdate(_) => GossipKind::LightClientFinalityUpdate, + PubsubMessage::LightClientOptimisticUpdate(_) => { + GossipKind::LightClientOptimisticUpdate + } } } @@ -206,6 +214,22 @@ impl PubsubMessage { sync_committee, )))) } + GossipKind::LightClientFinalityUpdate => { + let light_client_finality_update = + LightClientFinalityUpdate::from_ssz_bytes(data) + .map_err(|e| format!("{:?}", e))?; + Ok(PubsubMessage::LightClientFinalityUpdate(Box::new( + light_client_finality_update, + ))) + } + GossipKind::LightClientOptimisticUpdate => { + let light_client_optimistic_update = + LightClientOptimisticUpdate::from_ssz_bytes(data) + .map_err(|e| format!("{:?}", e))?; + Ok(PubsubMessage::LightClientOptimisticUpdate(Box::new( + light_client_optimistic_update, + ))) + } } } } @@ -227,6 +251,8 @@ impl PubsubMessage { PubsubMessage::Attestation(data) => data.1.as_ssz_bytes(), PubsubMessage::SignedContributionAndProof(data) => data.as_ssz_bytes(), PubsubMessage::SyncCommitteeMessage(data) => data.1.as_ssz_bytes(), + PubsubMessage::LightClientFinalityUpdate(data) => data.as_ssz_bytes(), + PubsubMessage::LightClientOptimisticUpdate(data) => data.as_ssz_bytes(), } } } @@ -261,6 +287,12 @@ impl std::fmt::Display for PubsubMessage { PubsubMessage::SyncCommitteeMessage(data) => { write!(f, "Sync committee message: subnet_id: {}", *data.0) } + PubsubMessage::LightClientFinalityUpdate(_data) => { + write!(f, "Light CLient Finality Update") + } + PubsubMessage::LightClientOptimisticUpdate(_data) => { + write!(f, "Light CLient Optimistic Update") + } } } } diff --git a/beacon_node/lighthouse_network/src/types/topics.rs b/beacon_node/lighthouse_network/src/types/topics.rs index 47d703c2600..e7e3cf4abbe 100644 --- a/beacon_node/lighthouse_network/src/types/topics.rs +++ b/beacon_node/lighthouse_network/src/types/topics.rs @@ -18,6 +18,8 @@ pub const PROPOSER_SLASHING_TOPIC: &str = "proposer_slashing"; pub const ATTESTER_SLASHING_TOPIC: &str = "attester_slashing"; pub const SIGNED_CONTRIBUTION_AND_PROOF_TOPIC: &str = "sync_committee_contribution_and_proof"; pub const SYNC_COMMITTEE_PREFIX_TOPIC: &str = "sync_committee_"; +pub const LIGHT_CLIENT_FINALITY_UPDATE: &str = "light_client_finality_update"; +pub const LIGHT_CLIENT_OPTIMISTIC_UPDATE: &str = "light_client_optimistic_update"; pub const CORE_TOPICS: [GossipKind; 6] = [ GossipKind::BeaconBlock, @@ -28,6 +30,11 @@ pub const CORE_TOPICS: [GossipKind; 6] = [ GossipKind::SignedContributionAndProof, ]; +pub const LIGHT_CLIENT_GOSSIP_TOPICS: [GossipKind; 2] = [ + GossipKind::LightClientFinalityUpdate, + GossipKind::LightClientOptimisticUpdate, +]; + /// A gossipsub topic which encapsulates the type of messages that should be sent and received over /// the pubsub protocol and the way the messages should be encoded. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] @@ -63,6 +70,10 @@ pub enum GossipKind { /// Topic for publishing unaggregated sync committee signatures on a particular subnet. #[strum(serialize = "sync_committee")] SyncCommitteeMessage(SyncSubnetId), + /// Topic for publishing finality updates for light clients. + LightClientFinalityUpdate, + /// Topic for publishing optimistic updates for light clients. + LightClientOptimisticUpdate, } impl std::fmt::Display for GossipKind { @@ -136,6 +147,8 @@ impl GossipTopic { VOLUNTARY_EXIT_TOPIC => GossipKind::VoluntaryExit, PROPOSER_SLASHING_TOPIC => GossipKind::ProposerSlashing, ATTESTER_SLASHING_TOPIC => GossipKind::AttesterSlashing, + LIGHT_CLIENT_FINALITY_UPDATE => GossipKind::LightClientFinalityUpdate, + LIGHT_CLIENT_OPTIMISTIC_UPDATE => GossipKind::LightClientOptimisticUpdate, topic => match committee_topic_index(topic) { Some(subnet) => match subnet { Subnet::Attestation(s) => GossipKind::Attestation(s), @@ -194,6 +207,8 @@ impl std::fmt::Display for GossipTopic { GossipKind::SyncCommitteeMessage(index) => { format!("{}{}", SYNC_COMMITTEE_PREFIX_TOPIC, *index) } + GossipKind::LightClientFinalityUpdate => LIGHT_CLIENT_FINALITY_UPDATE.into(), + GossipKind::LightClientOptimisticUpdate => LIGHT_CLIENT_OPTIMISTIC_UPDATE.into(), }; write!( f, diff --git a/beacon_node/network/src/beacon_processor/mod.rs b/beacon_node/network/src/beacon_processor/mod.rs index aa4286b9cd5..0a4a587d77e 100644 --- a/beacon_node/network/src/beacon_processor/mod.rs +++ b/beacon_node/network/src/beacon_processor/mod.rs @@ -62,9 +62,9 @@ use std::{cmp, collections::HashSet}; use task_executor::TaskExecutor; use tokio::sync::mpsc; use types::{ - Attestation, AttesterSlashing, Hash256, ProposerSlashing, SignedAggregateAndProof, - SignedBeaconBlock, SignedContributionAndProof, SignedVoluntaryExit, SubnetId, - SyncCommitteeMessage, SyncSubnetId, + Attestation, AttesterSlashing, Hash256, LightClientFinalityUpdate, LightClientOptimisticUpdate, + ProposerSlashing, SignedAggregateAndProof, SignedBeaconBlock, SignedContributionAndProof, + SignedVoluntaryExit, SubnetId, SyncCommitteeMessage, SyncSubnetId, }; use work_reprocessing_queue::{ spawn_reprocess_scheduler, QueuedAggregate, QueuedRpcBlock, QueuedUnaggregate, ReadyWork, @@ -129,6 +129,14 @@ const MAX_GOSSIP_PROPOSER_SLASHING_QUEUE_LEN: usize = 4_096; /// before we start dropping them. const MAX_GOSSIP_ATTESTER_SLASHING_QUEUE_LEN: usize = 4_096; +/// The maximum number of queued `LightClientFinalityUpdate` objects received on gossip that will be stored +/// before we start dropping them. +const MAX_GOSSIP_FINALITY_UPDATE_QUEUE_LEN: usize = 1_024; + +/// The maximum number of queued `LightClientOptimisticUpdate` objects received on gossip that will be stored +/// before we start dropping them. +const MAX_GOSSIP_OPTIMISTIC_UPDATE_QUEUE_LEN: usize = 1_024; + /// The maximum number of queued `SyncCommitteeMessage` objects that will be stored before we start dropping /// them. const MAX_SYNC_MESSAGE_QUEUE_LEN: usize = 2048; @@ -195,6 +203,8 @@ pub const GOSSIP_PROPOSER_SLASHING: &str = "gossip_proposer_slashing"; pub const GOSSIP_ATTESTER_SLASHING: &str = "gossip_attester_slashing"; pub const GOSSIP_SYNC_SIGNATURE: &str = "gossip_sync_signature"; pub const GOSSIP_SYNC_CONTRIBUTION: &str = "gossip_sync_contribution"; +pub const GOSSIP_LIGHT_CLIENT_FINALITY_UPDATE: &str = "light_client_finality_update"; +pub const GOSSIP_LIGHT_CLIENT_OPTIMISTIC_UPDATE: &str = "light_client_optimistic_update"; pub const RPC_BLOCK: &str = "rpc_block"; pub const CHAIN_SEGMENT: &str = "chain_segment"; pub const STATUS_PROCESSING: &str = "status_processing"; @@ -476,6 +486,42 @@ impl WorkEvent { } } + /// Create a new `Work` event for some light client finality update. + pub fn gossip_light_client_finality_update( + message_id: MessageId, + peer_id: PeerId, + light_client_finality_update: Box>, + seen_timestamp: Duration, + ) -> Self { + Self { + drop_during_sync: true, + work: Work::GossipLightClientFinalityUpdate { + message_id, + peer_id, + light_client_finality_update, + seen_timestamp, + }, + } + } + + /// Create a new `Work` event for some light client optimistic update. + pub fn gossip_light_client_optimistic_update( + message_id: MessageId, + peer_id: PeerId, + light_client_optimistic_update: Box>, + seen_timestamp: Duration, + ) -> Self { + Self { + drop_during_sync: true, + work: Work::GossipLightClientOptimisticUpdate { + message_id, + peer_id, + light_client_optimistic_update, + seen_timestamp, + }, + } + } + /// Create a new `Work` event for some attester slashing. pub fn gossip_attester_slashing( message_id: MessageId, @@ -730,6 +776,18 @@ pub enum Work { sync_contribution: Box>, seen_timestamp: Duration, }, + GossipLightClientFinalityUpdate { + message_id: MessageId, + peer_id: PeerId, + light_client_finality_update: Box>, + seen_timestamp: Duration, + }, + GossipLightClientOptimisticUpdate { + message_id: MessageId, + peer_id: PeerId, + light_client_optimistic_update: Box>, + seen_timestamp: Duration, + }, RpcBlock { block_root: Hash256, block: Arc>, @@ -777,6 +835,8 @@ impl Work { Work::GossipAttesterSlashing { .. } => GOSSIP_ATTESTER_SLASHING, Work::GossipSyncSignature { .. } => GOSSIP_SYNC_SIGNATURE, Work::GossipSyncContribution { .. } => GOSSIP_SYNC_CONTRIBUTION, + Work::GossipLightClientFinalityUpdate { .. } => GOSSIP_LIGHT_CLIENT_FINALITY_UPDATE, + Work::GossipLightClientOptimisticUpdate { .. } => GOSSIP_LIGHT_CLIENT_OPTIMISTIC_UPDATE, Work::RpcBlock { .. } => RPC_BLOCK, Work::ChainSegment { .. } => CHAIN_SEGMENT, Work::Status { .. } => STATUS_PROCESSING, @@ -916,6 +976,10 @@ impl BeaconProcessor { let mut gossip_attester_slashing_queue = FifoQueue::new(MAX_GOSSIP_ATTESTER_SLASHING_QUEUE_LEN); + // Using a FIFO queue for light client updates to maintain sequence order. + let mut finality_update_queue = FifoQueue::new(MAX_GOSSIP_FINALITY_UPDATE_QUEUE_LEN); + let mut optimistic_update_queue = FifoQueue::new(MAX_GOSSIP_OPTIMISTIC_UPDATE_QUEUE_LEN); + // Using a FIFO queue since blocks need to be imported sequentially. let mut rpc_block_queue = FifoQueue::new(MAX_RPC_BLOCK_QUEUE_LEN); let mut chain_segment_queue = FifoQueue::new(MAX_CHAIN_SEGMENT_QUEUE_LEN); @@ -1250,6 +1314,12 @@ impl BeaconProcessor { Work::GossipSyncContribution { .. } => { sync_contribution_queue.push(work) } + Work::GossipLightClientFinalityUpdate { .. } => { + finality_update_queue.push(work, work_id, &self.log) + } + Work::GossipLightClientOptimisticUpdate { .. } => { + optimistic_update_queue.push(work, work_id, &self.log) + } Work::RpcBlock { .. } => rpc_block_queue.push(work, work_id, &self.log), Work::ChainSegment { ref process_id, .. } => match process_id { ChainSegmentProcessId::RangeBatchId { .. } @@ -1551,7 +1621,7 @@ impl BeaconProcessor { ) }), /* - * Syn contribution verification. + * Sync contribution verification. */ Work::GossipSyncContribution { message_id, @@ -1566,6 +1636,38 @@ impl BeaconProcessor { seen_timestamp, ) }), + /* + * Light client finality update verification. + */ + Work::GossipLightClientFinalityUpdate { + message_id, + peer_id, + light_client_finality_update, + seen_timestamp, + } => task_spawner.spawn_blocking(move || { + worker.process_gossip_finality_update( + message_id, + peer_id, + *light_client_finality_update, + seen_timestamp, + ) + }), + /* + * Light client optimistic update verification. + */ + Work::GossipLightClientOptimisticUpdate { + message_id, + peer_id, + light_client_optimistic_update, + seen_timestamp, + } => task_spawner.spawn_blocking(move || { + worker.process_gossip_optimistic_update( + message_id, + peer_id, + *light_client_optimistic_update, + seen_timestamp, + ) + }), /* * Verification for beacon blocks received during syncing via RPC. */ diff --git a/beacon_node/network/src/beacon_processor/worker/gossip_methods.rs b/beacon_node/network/src/beacon_processor/worker/gossip_methods.rs index 4f1fd2ceded..2ab18d660b2 100644 --- a/beacon_node/network/src/beacon_processor/worker/gossip_methods.rs +++ b/beacon_node/network/src/beacon_processor/worker/gossip_methods.rs @@ -3,6 +3,8 @@ use crate::{metrics, service::NetworkMessage, sync::SyncMessage}; use beacon_chain::store::Error; use beacon_chain::{ attestation_verification::{self, Error as AttnError, VerifiedAttestation}, + light_client_finality_update_verification::Error as LightClientFinalityUpdateError, + light_client_optimistic_update_verification::Error as LightClientOptimisticUpdateError, observed_operations::ObservationOutcome, sync_committee_verification::{self, Error as SyncCommitteeError}, validator_monitor::get_block_delay_ms, @@ -18,9 +20,10 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use store::hot_cold_store::HotColdDBError; use tokio::sync::mpsc; use types::{ - Attestation, AttesterSlashing, EthSpec, Hash256, IndexedAttestation, ProposerSlashing, - SignedAggregateAndProof, SignedBeaconBlock, SignedContributionAndProof, SignedVoluntaryExit, - Slot, SubnetId, SyncCommitteeMessage, SyncSubnetId, + Attestation, AttesterSlashing, EthSpec, Hash256, IndexedAttestation, LightClientFinalityUpdate, + LightClientOptimisticUpdate, ProposerSlashing, SignedAggregateAndProof, SignedBeaconBlock, + SignedContributionAndProof, SignedVoluntaryExit, Slot, SubnetId, SyncCommitteeMessage, + SyncSubnetId, }; use super::{ @@ -1298,6 +1301,138 @@ impl Worker { metrics::inc_counter(&metrics::BEACON_PROCESSOR_SYNC_CONTRIBUTION_IMPORTED_TOTAL); } + pub fn process_gossip_finality_update( + self, + message_id: MessageId, + peer_id: PeerId, + light_client_finality_update: LightClientFinalityUpdate, + seen_timestamp: Duration, + ) { + match self + .chain + .verify_finality_update_for_gossip(light_client_finality_update, seen_timestamp) + { + Ok(_verified_light_client_finality_update) => { + self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Accept); + } + Err(e) => { + metrics::register_finality_update_error(&e); + match e { + LightClientFinalityUpdateError::InvalidLightClientFinalityUpdate => { + debug!( + self.log, + "LC invalid finality update"; + "peer" => %peer_id, + "error" => ?e, + ); + + self.gossip_penalize_peer( + peer_id, + PeerAction::LowToleranceError, + "light_client_gossip_error", + ); + } + LightClientFinalityUpdateError::TooEarly => { + debug!( + self.log, + "LC finality update too early"; + "peer" => %peer_id, + "error" => ?e, + ); + + self.gossip_penalize_peer( + peer_id, + PeerAction::HighToleranceError, + "light_client_gossip_error", + ); + } + LightClientFinalityUpdateError::FinalityUpdateAlreadySeen => debug!( + self.log, + "LC finality update already seen"; + "peer" => %peer_id, + "error" => ?e, + ), + LightClientFinalityUpdateError::BeaconChainError(_) + | LightClientFinalityUpdateError::LightClientUpdateError(_) + | LightClientFinalityUpdateError::SigSlotStartIsNone + | LightClientFinalityUpdateError::FailedConstructingUpdate => debug!( + self.log, + "LC error constructing finality update"; + "peer" => %peer_id, + "error" => ?e, + ), + } + self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); + } + }; + } + + pub fn process_gossip_optimistic_update( + self, + message_id: MessageId, + peer_id: PeerId, + light_client_optimistic_update: LightClientOptimisticUpdate, + seen_timestamp: Duration, + ) { + match self + .chain + .verify_optimistic_update_for_gossip(light_client_optimistic_update, seen_timestamp) + { + Ok(_verified_light_client_optimistic_update) => { + self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Accept); + } + Err(e) => { + metrics::register_optimistic_update_error(&e); + match e { + LightClientOptimisticUpdateError::InvalidLightClientOptimisticUpdate => { + debug!( + self.log, + "LC invalid optimistic update"; + "peer" => %peer_id, + "error" => ?e, + ); + + self.gossip_penalize_peer( + peer_id, + PeerAction::HighToleranceError, + "light_client_gossip_error", + ) + } + LightClientOptimisticUpdateError::TooEarly => { + debug!( + self.log, + "LC optimistic update too early"; + "peer" => %peer_id, + "error" => ?e, + ); + + self.gossip_penalize_peer( + peer_id, + PeerAction::HighToleranceError, + "light_client_gossip_error", + ); + } + LightClientOptimisticUpdateError::OptimisticUpdateAlreadySeen => debug!( + self.log, + "LC optimistic update already seen"; + "peer" => %peer_id, + "error" => ?e, + ), + LightClientOptimisticUpdateError::BeaconChainError(_) + | LightClientOptimisticUpdateError::LightClientUpdateError(_) + | LightClientOptimisticUpdateError::SigSlotStartIsNone + | LightClientOptimisticUpdateError::FailedConstructingUpdate => debug!( + self.log, + "LC error constructing optimistic update"; + "peer" => %peer_id, + "error" => ?e, + ), + } + self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); + } + }; + } + /// Handle an error whilst verifying an `Attestation` or `SignedAggregateAndProof` from the /// network. fn handle_attestation_verification_failure( diff --git a/beacon_node/network/src/metrics.rs b/beacon_node/network/src/metrics.rs index b4e7a3bace3..b4f3f29f934 100644 --- a/beacon_node/network/src/metrics.rs +++ b/beacon_node/network/src/metrics.rs @@ -1,5 +1,7 @@ use beacon_chain::{ attestation_verification::Error as AttnError, + light_client_finality_update_verification::Error as LightClientFinalityUpdateError, + light_client_optimistic_update_verification::Error as LightClientOptimisticUpdateError, sync_committee_verification::Error as SyncCommitteeError, }; use fnv::FnvHashMap; @@ -252,6 +254,19 @@ lazy_static! { "Gossipsub sync_committee errors per error type", &["type"] ); + pub static ref GOSSIP_FINALITY_UPDATE_ERRORS_PER_TYPE: Result = + try_create_int_counter_vec( + "gossipsub_light_client_finality_update_errors_per_type", + "Gossipsub light_client_finality_update errors per error type", + &["type"] + ); + pub static ref GOSSIP_OPTIMISTIC_UPDATE_ERRORS_PER_TYPE: Result = + try_create_int_counter_vec( + "gossipsub_light_client_optimistic_update_errors_per_type", + "Gossipsub light_client_optimistic_update errors per error type", + &["type"] + ); + /* * Network queue metrics @@ -358,6 +373,14 @@ pub fn update_bandwidth_metrics(bandwidth: Arc) { ); } +pub fn register_finality_update_error(error: &LightClientFinalityUpdateError) { + inc_counter_vec(&GOSSIP_FINALITY_UPDATE_ERRORS_PER_TYPE, &[error.as_ref()]); +} + +pub fn register_optimistic_update_error(error: &LightClientOptimisticUpdateError) { + inc_counter_vec(&GOSSIP_OPTIMISTIC_UPDATE_ERRORS_PER_TYPE, &[error.as_ref()]); +} + pub fn register_attestation_error(error: &AttnError) { inc_counter_vec(&GOSSIP_ATTESTATION_ERRORS_PER_TYPE, &[error.as_ref()]); } diff --git a/beacon_node/network/src/router/mod.rs b/beacon_node/network/src/router/mod.rs index 5df308f2592..ce98337cfed 100644 --- a/beacon_node/network/src/router/mod.rs +++ b/beacon_node/network/src/router/mod.rs @@ -280,6 +280,30 @@ impl Router { sync_committtee_msg.0, ); } + PubsubMessage::LightClientFinalityUpdate(light_client_finality_update) => { + trace!( + self.log, + "Received light client finality update"; + "peer_id" => %peer_id + ); + self.processor.on_light_client_finality_update_gossip( + id, + peer_id, + light_client_finality_update, + ); + } + PubsubMessage::LightClientOptimisticUpdate(light_client_optimistic_update) => { + trace!( + self.log, + "Received light client optimistic update"; + "peer_id" => %peer_id + ); + self.processor.on_light_client_optimistic_update_gossip( + id, + peer_id, + light_client_optimistic_update, + ); + } } } } diff --git a/beacon_node/network/src/router/processor.rs b/beacon_node/network/src/router/processor.rs index 3c9a4a81fb9..999ba29e90a 100644 --- a/beacon_node/network/src/router/processor.rs +++ b/beacon_node/network/src/router/processor.rs @@ -17,8 +17,9 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use store::SyncCommitteeMessage; use tokio::sync::mpsc; use types::{ - Attestation, AttesterSlashing, EthSpec, ProposerSlashing, SignedAggregateAndProof, - SignedBeaconBlock, SignedContributionAndProof, SignedVoluntaryExit, SubnetId, SyncSubnetId, + Attestation, AttesterSlashing, EthSpec, LightClientFinalityUpdate, LightClientOptimisticUpdate, + ProposerSlashing, SignedAggregateAndProof, SignedBeaconBlock, SignedContributionAndProof, + SignedVoluntaryExit, SubnetId, SyncSubnetId, }; /// Processes validated messages from the network. It relays necessary data to the syncing thread @@ -368,6 +369,34 @@ impl Processor { )) } + pub fn on_light_client_finality_update_gossip( + &mut self, + message_id: MessageId, + peer_id: PeerId, + light_client_finality_update: Box>, + ) { + self.send_beacon_processor_work(BeaconWorkEvent::gossip_light_client_finality_update( + message_id, + peer_id, + light_client_finality_update, + timestamp_now(), + )) + } + + pub fn on_light_client_optimistic_update_gossip( + &mut self, + message_id: MessageId, + peer_id: PeerId, + light_client_optimistic_update: Box>, + ) { + self.send_beacon_processor_work(BeaconWorkEvent::gossip_light_client_optimistic_update( + message_id, + peer_id, + light_client_optimistic_update, + timestamp_now(), + )) + } + fn send_beacon_processor_work(&mut self, work: BeaconWorkEvent) { self.beacon_processor_send .try_send(work) diff --git a/beacon_node/network/src/service.rs b/beacon_node/network/src/service.rs index 31c42b860de..4568ed1a229 100644 --- a/beacon_node/network/src/service.rs +++ b/beacon_node/network/src/service.rs @@ -208,6 +208,8 @@ pub struct NetworkService { metrics_update: tokio::time::Interval, /// gossipsub_parameter_update timer gossipsub_parameter_update: tokio::time::Interval, + /// enable_light_client_server indicator + enable_light_client_server: bool, /// The logger for the network service. fork_context: Arc, log: slog::Logger, @@ -345,6 +347,7 @@ impl NetworkService { gossipsub_parameter_update, fork_context, log: network_log, + enable_light_client_server: config.enable_light_client_server, }; network_service.spawn_service(executor); @@ -679,6 +682,7 @@ impl NetworkService { } return; } + let mut subscribed_topics: Vec = vec![]; for topic_kind in lighthouse_network::types::CORE_TOPICS.iter() { for fork_digest in self.required_gossip_fork_digests() { @@ -695,6 +699,25 @@ impl NetworkService { } } + if self.enable_light_client_server { + for light_client_topic_kind in + lighthouse_network::types::LIGHT_CLIENT_GOSSIP_TOPICS.iter() + { + for fork_digest in self.required_gossip_fork_digests() { + let light_client_topic = GossipTopic::new( + light_client_topic_kind.clone(), + GossipEncoding::default(), + fork_digest, + ); + if self.libp2p.subscribe(light_client_topic.clone()) { + subscribed_topics.push(light_client_topic); + } else { + warn!(self.log, "Could not subscribe to topic"; "topic" => %light_client_topic); + } + } + } + } + // If we are to subscribe to all subnets we do it here if self.subscribe_all_subnets { for subnet_id in 0..<::EthSpec as EthSpec>::SubnetBitfieldLength::to_u64() { diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index 4a6cc57b119..37bab8b4806 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -51,6 +51,7 @@ pub mod graffiti; pub mod historical_batch; pub mod indexed_attestation; pub mod light_client_bootstrap; +pub mod light_client_finality_update; pub mod light_client_optimistic_update; pub mod light_client_update; pub mod pending_attestation; @@ -136,6 +137,8 @@ pub use crate::free_attestation::FreeAttestation; pub use crate::graffiti::{Graffiti, GRAFFITI_BYTES_LEN}; pub use crate::historical_batch::HistoricalBatch; pub use crate::indexed_attestation::IndexedAttestation; +pub use crate::light_client_finality_update::LightClientFinalityUpdate; +pub use crate::light_client_optimistic_update::LightClientOptimisticUpdate; pub use crate::participation_flags::ParticipationFlags; pub use crate::participation_list::ParticipationList; pub use crate::payload::{BlindedPayload, BlockType, ExecPayload, FullPayload}; diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index fe26c0fa3eb..cae6266f9e7 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -1,10 +1,10 @@ -use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee}; -use crate::{light_client_update::*, test_utils::TestRandom, BeaconBlock, BeaconState, ChainSpec}; -use safe_arith::ArithError; +use super::{ + BeaconBlockHeader, EthSpec, FixedVector, Hash256, SignedBeaconBlock, SignedBlindedBeaconBlock, + Slot, SyncAggregate, +}; +use crate::{light_client_update::*, test_utils::TestRandom, BeaconState, ChainSpec}; use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; -use ssz_types::typenum::{U5, U6}; -use std::sync::Arc; use test_random_derive::TestRandom; use tree_hash::TreeHash; @@ -28,43 +28,38 @@ pub struct LightClientFinalityUpdate { impl LightClientFinalityUpdate { pub fn new( - chain_spec: ChainSpec, - beacon_state: BeaconState, - block: BeaconBlock, + chain_spec: &ChainSpec, + beacon_state: &BeaconState, + block: &SignedBeaconBlock, attested_state: &mut BeaconState, - finalized_block: BeaconBlock, + finalized_block: &SignedBlindedBeaconBlock, ) -> Result { let altair_fork_epoch = chain_spec .altair_fork_epoch .ok_or(Error::AltairForkNotActive)?; - if attested_state.slot().epoch(T::slots_per_epoch()) < altair_fork_epoch { + if beacon_state.slot().epoch(T::slots_per_epoch()) < altair_fork_epoch { return Err(Error::AltairForkNotActive); } - let sync_aggregate = block.body().sync_aggregate()?; + let sync_aggregate = block.message().body().sync_aggregate()?; if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { return Err(Error::NotEnoughSyncCommitteeParticipants); } // Compute and validate attested header. let mut attested_header = attested_state.latest_block_header().clone(); - attested_header.state_root = attested_state.tree_hash_root(); + attested_header.state_root = attested_state.update_tree_hash_cache()?; // Build finalized header from finalized block - let finalized_header = BeaconBlockHeader { - slot: finalized_block.slot(), - proposer_index: finalized_block.proposer_index(), - parent_root: finalized_block.parent_root(), - state_root: finalized_block.state_root(), - body_root: finalized_block.body_root(), - }; + let finalized_header = finalized_block.message().block_header(); + if finalized_header.tree_hash_root() != beacon_state.finalized_checkpoint().root { return Err(Error::InvalidFinalizedBlock); } let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; Ok(Self { - attested_header: attested_header, - finalized_header: finalized_header, + attested_header, + finalized_header, finality_branch: FixedVector::new(finality_branch)?, sync_aggregate: sync_aggregate.clone(), signature_slot: block.slot(), diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index 9592bf1c23b..8dda8cd5aed 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -1,6 +1,6 @@ use super::{BeaconBlockHeader, EthSpec, Slot, SyncAggregate}; use crate::{ - light_client_update::Error, test_utils::TestRandom, BeaconBlock, BeaconState, ChainSpec, + light_client_update::Error, test_utils::TestRandom, BeaconState, ChainSpec, SignedBeaconBlock, }; use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; @@ -23,9 +23,9 @@ pub struct LightClientOptimisticUpdate { impl LightClientOptimisticUpdate { pub fn new( - chain_spec: ChainSpec, - block: BeaconBlock, - attested_state: BeaconState, + chain_spec: &ChainSpec, + block: &SignedBeaconBlock, + attested_state: &BeaconState, ) -> Result { let altair_fork_epoch = chain_spec .altair_fork_epoch @@ -34,7 +34,7 @@ impl LightClientOptimisticUpdate { return Err(Error::AltairForkNotActive); } - let sync_aggregate = block.body().sync_aggregate()?; + let sync_aggregate = block.message().body().sync_aggregate()?; if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { return Err(Error::NotEnoughSyncCommitteeParticipants); }