-
Notifications
You must be signed in to change notification settings - Fork 745
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added lightclient server side containers (#3655)
## Issue Addressed This PR partially addresses #3651 ## Proposed Changes This PR adds the following containers types from [the lightclient specs](https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md): `LightClientUpdate`, `LightClientFinalityUpdate`, `LightClientOptimisticUpdate` and `LightClientBootstrap`. It also implements the creation of each updates as delined by this [document](https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/full-node.md). ## Additional Info Here is a brief description of what each of these container signify: `LightClientUpdate`: This container is only provided by server (full node) to lightclients when catching up new sync committees beetwen periods and we want possibly one lightclient update ready for each post-altair period the lighthouse node go over. it is needed in the resp/req in method `light_client_update_by_range`. `LightClientFinalityUpdate/LightClientFinalityUpdate`: Lighthouse will need only the latest of each of this kind of updates, so no need to store them in the database, we can just store the latest one of each one in memory and then just supply them via gossip or respreq, only the latest ones are served by a full node. finality updates marks the transition to a new finalized header, while optimistic updates signify new non-finalized header which are imported optimistically. `LightClientBootstrap`: This object is retrieved by lightclients during the bootstrap process after a finalized checkpoint is retrieved, ideally we want to store a LightClientBootstrap for each finalized root and then serve each of them by finalized root in respreq protocol id `light_client_bootstrap`. Little digression to how we implement the creation of each updates: the creation of a optimistic/finality update is just a version of the lightclient_update creation mechanism with less fields being set, there is underlying concept of inheritance, if you look at the specs it becomes very obvious that a lightclient update is just an extension of a finality update and a finality update an extension to an optimistic update. ## Extra note `LightClientStore` is not implemented as it is only useful as internal storage design for the lightclient side.
- Loading branch information
1 parent
6d5a2b5
commit f2f920d
Showing
5 changed files
with
358 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<T: EthSpec> { | ||
/// Requested beacon block header. | ||
pub header: BeaconBlockHeader, | ||
/// The `SyncCommittee` used in the requested period. | ||
pub current_sync_committee: Arc<SyncCommittee<T>>, | ||
/// Merkle proof for sync committee | ||
pub current_sync_committee_branch: FixedVector<Hash256, CurrentSyncCommitteeProofLen>, | ||
} | ||
|
||
impl<T: EthSpec> LightClientBootstrap<T> { | ||
pub fn from_beacon_state(beacon_state: BeaconState<T>) -> Result<Self, Error> { | ||
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<MainnetEthSpec>); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<T: EthSpec> { | ||
/// 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<Hash256, FinalizedRootProofLen>, | ||
/// current sync aggreggate | ||
pub sync_aggregate: SyncAggregate<T>, | ||
/// Slot of the sync aggregated singature | ||
pub signature_slot: Slot, | ||
} | ||
|
||
impl<T: EthSpec> LightClientFinalityUpdate<T> { | ||
pub fn new( | ||
chain_spec: ChainSpec, | ||
beacon_state: BeaconState<T>, | ||
block: BeaconBlock<T>, | ||
attested_state: BeaconState<T>, | ||
finalized_block: BeaconBlock<T>, | ||
) -> Result<Self, Error> { | ||
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<MainnetEthSpec>); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<T: EthSpec> { | ||
/// The last `BeaconBlockHeader` from the last attested block by the sync committee. | ||
pub attested_header: BeaconBlockHeader, | ||
/// current sync aggreggate | ||
pub sync_aggregate: SyncAggregate<T>, | ||
/// Slot of the sync aggregated singature | ||
pub signature_slot: Slot, | ||
} | ||
|
||
impl<T: EthSpec> LightClientOptimisticUpdate<T> { | ||
pub fn new( | ||
chain_spec: ChainSpec, | ||
block: BeaconBlock<T>, | ||
attested_state: BeaconState<T>, | ||
) -> Result<Self, Error> { | ||
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<MainnetEthSpec>); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<ssz_types::Error> for Error { | ||
fn from(e: ssz_types::Error) -> Error { | ||
Error::SszTypesError(e) | ||
} | ||
} | ||
|
||
impl From<beacon_state::Error> for Error { | ||
fn from(e: beacon_state::Error) -> Error { | ||
Error::BeaconStateError(e) | ||
} | ||
} | ||
|
||
impl From<ArithError> 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<T: EthSpec> { | ||
/// 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<SyncCommittee<T>>, | ||
/// Merkle proof for next sync committee | ||
pub next_sync_committee_branch: FixedVector<Hash256, NextSyncCommitteeProofLen>, | ||
/// 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<Hash256, FinalizedRootProofLen>, | ||
/// current sync aggreggate | ||
pub sync_aggregate: SyncAggregate<T>, | ||
/// Slot of the sync aggregated singature | ||
pub signature_slot: Slot, | ||
} | ||
|
||
impl<T: EthSpec> LightClientUpdate<T> { | ||
pub fn new( | ||
chain_spec: ChainSpec, | ||
beacon_state: BeaconState<T>, | ||
block: BeaconBlock<T>, | ||
attested_state: BeaconState<T>, | ||
finalized_block: BeaconBlock<T>, | ||
) -> Result<Self, Error> { | ||
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<MainnetEthSpec>); | ||
|
||
#[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 | ||
); | ||
} | ||
} |