diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index 32300173e..7f618dc34 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -49,6 +49,9 @@ pub mod free_attestation; pub mod graffiti; pub mod historical_batch; pub mod indexed_attestation; +pub mod light_client_bootstrap; +pub mod light_client_optimistic_update; +pub mod light_client_update; pub mod pending_attestation; pub mod proposer_preparation_data; pub mod proposer_slashing; diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs new file mode 100644 index 000000000..406136d54 --- /dev/null +++ b/consensus/types/src/light_client_bootstrap.rs @@ -0,0 +1,45 @@ +use super::{BeaconBlockHeader, BeaconState, EthSpec, FixedVector, Hash256, SyncCommittee}; +use crate::{light_client_update::*, test_utils::TestRandom}; +use serde_derive::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use std::sync::Arc; +use test_random_derive::TestRandom; +use tree_hash::TreeHash; + +/// A LightClientBootstrap is the initializer we send over to lightclient nodes +/// that are trying to generate their basic storage when booting up. +#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom)] +#[serde(bound = "T: EthSpec")] +pub struct LightClientBootstrap { + /// Requested beacon block header. + pub header: BeaconBlockHeader, + /// The `SyncCommittee` used in the requested period. + pub current_sync_committee: Arc>, + /// Merkle proof for sync committee + pub current_sync_committee_branch: FixedVector, +} + +impl LightClientBootstrap { + pub fn from_beacon_state(beacon_state: BeaconState) -> Result { + let mut header = beacon_state.latest_block_header().clone(); + header.state_root = beacon_state.tree_hash_root(); + Ok(LightClientBootstrap { + header, + current_sync_committee: beacon_state.current_sync_committee()?.clone(), + /// TODO(Giulio2002): Generate Merkle Proof, this is just empty hashes + current_sync_committee_branch: FixedVector::new(vec![ + Hash256::zero(); + CURRENT_SYNC_COMMITTEE_PROOF_LEN + ])?, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::MainnetEthSpec; + + ssz_tests!(LightClientBootstrap); +} diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs new file mode 100644 index 000000000..c93d15a1a --- /dev/null +++ b/consensus/types/src/light_client_finality_update.rs @@ -0,0 +1,80 @@ +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 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; + +/// A LightClientFinalityUpdate is the update lightclient request or received by a gossip that +/// signal a new finalized beacon block header for the light client sync protocol. +#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom)] +#[serde(bound = "T: EthSpec")] +pub struct LightClientFinalityUpdate { + /// The last `BeaconBlockHeader` from the last attested block by the sync committee. + pub attested_header: BeaconBlockHeader, + /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). + pub finalized_header: BeaconBlockHeader, + /// Merkle proof attesting finalized header. + pub finality_branch: FixedVector, + /// current sync aggreggate + pub sync_aggregate: SyncAggregate, + /// Slot of the sync aggregated singature + pub signature_slot: Slot, +} + +impl LightClientFinalityUpdate { + pub fn new( + chain_spec: ChainSpec, + beacon_state: BeaconState, + block: BeaconBlock, + attested_state: BeaconState, + finalized_block: BeaconBlock, + ) -> 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 { + return Err(Error::AltairForkNotActive); + } + + let sync_aggregate = block.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(); + // 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(), + }; + if finalized_header.tree_hash_root() != beacon_state.finalized_checkpoint().root { + return Err(Error::InvalidFinalizedBlock); + } + // TODO(Giulio2002): compute proper merkle proofs. + Ok(Self { + attested_header: attested_header, + finalized_header: finalized_header, + finality_branch: FixedVector::new(vec![Hash256::zero(); FINALIZED_ROOT_PROOF_LEN])?, + sync_aggregate: sync_aggregate.clone(), + signature_slot: block.slot(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::MainnetEthSpec; + + ssz_tests!(LightClientFinalityUpdate); +} diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs new file mode 100644 index 000000000..9592bf1c2 --- /dev/null +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -0,0 +1,59 @@ +use super::{BeaconBlockHeader, EthSpec, Slot, SyncAggregate}; +use crate::{ + light_client_update::Error, test_utils::TestRandom, BeaconBlock, BeaconState, ChainSpec, +}; +use serde_derive::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; +use tree_hash::TreeHash; + +/// A LightClientOptimisticUpdate is the update we send on each slot, +/// it is based off the current unfinalized epoch is verified only against BLS signature. +#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom)] +#[serde(bound = "T: EthSpec")] +pub struct LightClientOptimisticUpdate { + /// The last `BeaconBlockHeader` from the last attested block by the sync committee. + pub attested_header: BeaconBlockHeader, + /// current sync aggreggate + pub sync_aggregate: SyncAggregate, + /// Slot of the sync aggregated singature + pub signature_slot: Slot, +} + +impl LightClientOptimisticUpdate { + pub fn new( + chain_spec: ChainSpec, + block: BeaconBlock, + attested_state: BeaconState, + ) -> 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 { + return Err(Error::AltairForkNotActive); + } + + let sync_aggregate = block.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(); + Ok(Self { + attested_header, + sync_aggregate: sync_aggregate.clone(), + signature_slot: block.slot(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::MainnetEthSpec; + + ssz_tests!(LightClientOptimisticUpdate); +} diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs new file mode 100644 index 000000000..38609cf1b --- /dev/null +++ b/consensus/types/src/light_client_update.rs @@ -0,0 +1,171 @@ +use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee}; +use crate::{beacon_state, test_utils::TestRandom, BeaconBlock, BeaconState, ChainSpec}; +use safe_arith::ArithError; +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; + +pub const FINALIZED_ROOT_INDEX: usize = 105; +pub const CURRENT_SYNC_COMMITTEE_INDEX: usize = 54; +pub const NEXT_SYNC_COMMITTEE_INDEX: usize = 55; + +pub type FinalizedRootProofLen = U6; +pub type CurrentSyncCommitteeProofLen = U5; +pub type NextSyncCommitteeProofLen = U5; + +pub const FINALIZED_ROOT_PROOF_LEN: usize = 6; +pub const CURRENT_SYNC_COMMITTEE_PROOF_LEN: usize = 5; +pub const NEXT_SYNC_COMMITTEE_PROOF_LEN: usize = 5; + +#[derive(Debug, PartialEq, Clone)] +pub enum Error { + SszTypesError(ssz_types::Error), + BeaconStateError(beacon_state::Error), + ArithError(ArithError), + AltairForkNotActive, + NotEnoughSyncCommitteeParticipants, + MismatchingPeriods, + InvalidFinalizedBlock, +} + +impl From for Error { + fn from(e: ssz_types::Error) -> Error { + Error::SszTypesError(e) + } +} + +impl From for Error { + fn from(e: beacon_state::Error) -> Error { + Error::BeaconStateError(e) + } +} + +impl From for Error { + fn from(e: ArithError) -> Error { + Error::ArithError(e) + } +} + +/// A LightClientUpdate is the update we request solely to either complete the bootstraping process, +/// or to sync up to the last committee period, we need to have one ready for each ALTAIR period +/// we go over, note: there is no need to keep all of the updates from [ALTAIR_PERIOD, CURRENT_PERIOD]. +#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom)] +#[serde(bound = "T: EthSpec")] +pub struct LightClientUpdate { + /// The last `BeaconBlockHeader` from the last attested block by the sync committee. + pub attested_header: BeaconBlockHeader, + /// The `SyncCommittee` used in the next period. + pub next_sync_committee: Arc>, + /// Merkle proof for next sync committee + pub next_sync_committee_branch: FixedVector, + /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). + pub finalized_header: BeaconBlockHeader, + /// Merkle proof attesting finalized header. + pub finality_branch: FixedVector, + /// current sync aggreggate + pub sync_aggregate: SyncAggregate, + /// Slot of the sync aggregated singature + pub signature_slot: Slot, +} + +impl LightClientUpdate { + pub fn new( + chain_spec: ChainSpec, + beacon_state: BeaconState, + block: BeaconBlock, + attested_state: BeaconState, + finalized_block: BeaconBlock, + ) -> 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 { + return Err(Error::AltairForkNotActive); + } + + let sync_aggregate = block.body().sync_aggregate()?; + if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize { + return Err(Error::NotEnoughSyncCommitteeParticipants); + } + + let signature_period = block.epoch().sync_committee_period(&chain_spec)?; + // Compute and validate attested header. + let mut attested_header = attested_state.latest_block_header().clone(); + attested_header.state_root = attested_state.tree_hash_root(); + let attested_period = attested_header + .slot + .epoch(T::slots_per_epoch()) + .sync_committee_period(&chain_spec)?; + if attested_period != signature_period { + return Err(Error::MismatchingPeriods); + } + // 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(), + }; + if finalized_header.tree_hash_root() != beacon_state.finalized_checkpoint().root { + return Err(Error::InvalidFinalizedBlock); + } + // TODO(Giulio2002): compute proper merkle proofs. + Ok(Self { + attested_header, + next_sync_committee: attested_state.next_sync_committee()?.clone(), + next_sync_committee_branch: FixedVector::new(vec![ + Hash256::zero(); + NEXT_SYNC_COMMITTEE_PROOF_LEN + ])?, + finalized_header, + finality_branch: FixedVector::new(vec![Hash256::zero(); FINALIZED_ROOT_PROOF_LEN])?, + sync_aggregate: sync_aggregate.clone(), + signature_slot: block.slot(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::MainnetEthSpec; + use ssz_types::typenum::Unsigned; + + ssz_tests!(LightClientUpdate); + + #[test] + fn finalized_root_params() { + assert!(2usize.pow(FINALIZED_ROOT_PROOF_LEN as u32) <= FINALIZED_ROOT_INDEX); + assert!(2usize.pow(FINALIZED_ROOT_PROOF_LEN as u32 + 1) > FINALIZED_ROOT_INDEX); + assert_eq!(FinalizedRootProofLen::to_usize(), FINALIZED_ROOT_PROOF_LEN); + } + + #[test] + fn current_sync_committee_params() { + assert!( + 2usize.pow(CURRENT_SYNC_COMMITTEE_PROOF_LEN as u32) <= CURRENT_SYNC_COMMITTEE_INDEX + ); + assert!( + 2usize.pow(CURRENT_SYNC_COMMITTEE_PROOF_LEN as u32 + 1) > CURRENT_SYNC_COMMITTEE_INDEX + ); + assert_eq!( + CurrentSyncCommitteeProofLen::to_usize(), + CURRENT_SYNC_COMMITTEE_PROOF_LEN + ); + } + + #[test] + fn next_sync_committee_params() { + assert!(2usize.pow(NEXT_SYNC_COMMITTEE_PROOF_LEN as u32) <= NEXT_SYNC_COMMITTEE_INDEX); + assert!(2usize.pow(NEXT_SYNC_COMMITTEE_PROOF_LEN as u32 + 1) > NEXT_SYNC_COMMITTEE_INDEX); + assert_eq!( + NextSyncCommitteeProofLen::to_usize(), + NEXT_SYNC_COMMITTEE_PROOF_LEN + ); + } +}