Add Capella & Deneb light client support (#4946)

* rebase and add comment

* conditional test

* test

* optimistic chould be working now

* finality should be working now

* try again

* try again

* clippy fix

* add lc bootstrap beacon api

* add lc optimistic/finality update to events

* fmt

* That error isn't occuring on my computer but I think this should fix it

* Merge branch 'unstable' into light_client_beacon_api_1

# Conflicts:
#	beacon_node/beacon_chain/src/events.rs
#	beacon_node/http_api/src/lib.rs
#	beacon_node/http_api/src/test_utils.rs
#	beacon_node/http_api/tests/main.rs
#	beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs
#	beacon_node/lighthouse_network/src/rpc/methods.rs
#	beacon_node/lighthouse_network/src/service/api_types.rs
#	beacon_node/network/src/beacon_processor/worker/rpc_methods.rs
#	beacon_node/tests/test.rs
#	common/eth2/src/types.rs
#	lighthouse/src/main.rs

* Add missing test file

* Update light client types to comply with Altair light client spec.

* Fix test compilation

* Merge branch 'unstable' into light_client_beacon_api_1

* Support deserializing light client structures for the Bellatrix fork

* Move `get_light_client_bootstrap` logic to `BeaconChain`. `LightClientBootstrap` API to return `ForkVersionedResponse`.

* Misc fixes.
- log cleanup
- move http_api config mutation to `config::get_config` for consistency
- fix light client API responses

* Add light client bootstrap API test and fix existing ones.

* Merge branch 'unstable' into light_client_beacon_api_1

* Fix test for `light-client-server` http api config.

* Appease clippy

* Add Altair light client SSZ tests

* Merge branch 'unstable' of https://github.com/sigp/lighthouse into light_client_beacon_api_1

* updates to light client header

* light client header from signed beacon block

* using options

* implement helper functions

* placeholder conversion from vec hash256 to exec branch

* add deneb

* using fixed vector

* remove unwraps

* by epoch

* compute merkle proof

* merkle proof

* update comments

* resolve merge conflicts

* linting

* Merge branch 'unstable' into light-client-ssz-tests

# Conflicts:
#	beacon_node/beacon_chain/src/beacon_chain.rs
#	consensus/types/src/light_client_bootstrap.rs
#	consensus/types/src/light_client_header.rs

* superstruct attempt

* superstruct changes

* lint

* altair

* update

* update

* changes to light_client_optimistic_ and finality

* merge unstable

* refactor

* resolved merge conflicts

* Merge branch 'unstable' of https://github.com/sigp/lighthouse into capella_deneb_light_client_types

* block_to_light_client_header fork aware

* fmt

* comment fix

* comment fix

* include merge fork, update deserialize_by_fork, refactor

* fmt

* pass by ref to prevent clone

* rename merkle proof fn

* add FIXME

* LightClientHeader TestRandom

* fix comments

* fork version deserialize

* merge unstable

* move fn arguments, fork name calc

* use task executor

* remove unneeded fns

* remove dead code

* add manual ssz decoding/encoding and add ssz_tests_by_fork macro

* merge deneb types with tests

* merge ssz tests, revert code deletion, cleanup

* move chainspec

* update ssz tests

* fmt

* light client ssz tests

* change to superstruct

* changes from feedback

* linting

* Merge branch 'unstable' of https://github.com/sigp/lighthouse into capella_deneb_light_client_types

* test fix

* cleanup

* Remove unused `derive`.

* Merge branch 'unstable' of https://github.com/sigp/lighthouse into capella_deneb_light_client_types

* beta compiler fix

* merge
This commit is contained in:
Eitan Seri-Levi
2024-03-25 13:01:56 +02:00
committed by GitHub
parent 19b0db2cdf
commit e4d4e439cb
24 changed files with 1090 additions and 330 deletions

View File

@@ -15,6 +15,13 @@ pub type KzgCommitments<T> =
pub type KzgCommitmentOpts<T> =
FixedVector<Option<KzgCommitment>, <T as EthSpec>::MaxBlobsPerBlock>;
/// The number of leaves (including padding) on the `BeaconBlockBody` Merkle tree.
///
/// ## Note
///
/// This constant is set with the assumption that there are `> 8` and `<= 16` fields on the
/// `BeaconBlockBody`. **Tree hashing will fail if this value is set incorrectly.**
pub const NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES: usize = 16;
/// Index of the `blob_kzg_commitments` leaf in the `BeaconBlockBody` tree post-deneb.
pub const BLOB_KZG_COMMITMENTS_INDEX: usize = 11;
@@ -591,6 +598,56 @@ impl<E: EthSpec> From<BeaconBlockBody<E, FullPayload<E>>>
}
}
impl<T: EthSpec> BeaconBlockBody<T> {
pub fn block_body_merkle_proof(&self, generalized_index: usize) -> Result<Vec<Hash256>, Error> {
let field_index = match generalized_index {
light_client_update::EXECUTION_PAYLOAD_INDEX => {
// Execution payload is a top-level field, subtract off the generalized indices
// for the internal nodes. Result should be 9, the field offset of the execution
// payload in the `BeaconBlockBody`:
// https://github.com/ethereum/consensus-specs/blob/dev/specs/deneb/beacon-chain.md#beaconblockbody
generalized_index
.checked_sub(NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES)
.ok_or(Error::IndexNotSupported(generalized_index))?
}
_ => return Err(Error::IndexNotSupported(generalized_index)),
};
let mut leaves = vec![
self.randao_reveal().tree_hash_root(),
self.eth1_data().tree_hash_root(),
self.graffiti().tree_hash_root(),
self.proposer_slashings().tree_hash_root(),
self.attester_slashings().tree_hash_root(),
self.attestations().tree_hash_root(),
self.deposits().tree_hash_root(),
self.voluntary_exits().tree_hash_root(),
];
if let Ok(sync_aggregate) = self.sync_aggregate() {
leaves.push(sync_aggregate.tree_hash_root())
}
if let Ok(execution_payload) = self.execution_payload() {
leaves.push(execution_payload.tree_hash_root())
}
if let Ok(bls_to_execution_changes) = self.bls_to_execution_changes() {
leaves.push(bls_to_execution_changes.tree_hash_root())
}
if let Ok(blob_kzg_commitments) = self.blob_kzg_commitments() {
leaves.push(blob_kzg_commitments.tree_hash_root())
}
let depth = light_client_update::EXECUTION_PAYLOAD_PROOF_LEN;
let tree = merkle_proof::MerkleTree::create(&leaves, depth);
let (_, proof) = tree.generate_proof(field_index, depth)?;
Ok(proof)
}
}
/// Util method helpful for logging.
pub fn format_kzg_commitments(commitments: &[KzgCommitment]) -> String {
let commitment_strings: Vec<String> = commitments.iter().map(|x| x.to_string()).collect();

View File

@@ -152,11 +152,25 @@ pub use crate::fork_versioned_response::{ForkVersionDeserialize, ForkVersionedRe
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_bootstrap::LightClientBootstrap;
pub use crate::light_client_finality_update::LightClientFinalityUpdate;
pub use crate::light_client_header::LightClientHeader;
pub use crate::light_client_optimistic_update::LightClientOptimisticUpdate;
pub use crate::light_client_update::{Error as LightClientError, LightClientUpdate};
pub use crate::light_client_bootstrap::{
LightClientBootstrap, LightClientBootstrapAltair, LightClientBootstrapCapella,
LightClientBootstrapDeneb,
};
pub use crate::light_client_finality_update::{
LightClientFinalityUpdate, LightClientFinalityUpdateAltair, LightClientFinalityUpdateCapella,
LightClientFinalityUpdateDeneb,
};
pub use crate::light_client_header::{
LightClientHeader, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb,
};
pub use crate::light_client_optimistic_update::{
LightClientOptimisticUpdate, LightClientOptimisticUpdateAltair,
LightClientOptimisticUpdateCapella, LightClientOptimisticUpdateDeneb,
};
pub use crate::light_client_update::{
Error as LightClientError, LightClientUpdate, LightClientUpdateAltair,
LightClientUpdateCapella, LightClientUpdateDeneb,
};
pub use crate::participation_flags::ParticipationFlags;
pub use crate::participation_list::ParticipationList;
pub use crate::payload::{

View File

@@ -1,68 +1,147 @@
use super::{BeaconState, EthSpec, FixedVector, Hash256, SyncCommittee};
use crate::{
light_client_update::*, test_utils::TestRandom, ForkName, ForkVersionDeserialize,
LightClientHeader,
light_client_update::*, test_utils::TestRandom, ChainSpec, ForkName, ForkVersionDeserialize,
LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, SignedBeaconBlock,
Slot,
};
use derivative::Derivative;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use ssz::Decode;
use ssz_derive::{Decode, Encode};
use std::sync::Arc;
use superstruct::superstruct;
use test_random_derive::TestRandom;
use tree_hash_derive::TreeHash;
/// A LightClientBootstrap is the initializer we send over to light_client nodes
/// that are trying to generate their basic storage when booting up.
#[derive(
Debug,
Clone,
PartialEq,
Serialize,
Deserialize,
Encode,
Decode,
TestRandom,
arbitrary::Arbitrary,
#[superstruct(
variants(Altair, Capella, Deneb),
variant_attributes(
derive(
Debug,
Clone,
PartialEq,
Serialize,
Deserialize,
Derivative,
Decode,
Encode,
TestRandom,
arbitrary::Arbitrary,
TreeHash,
),
serde(bound = "E: EthSpec", deny_unknown_fields),
arbitrary(bound = "E: EthSpec"),
)
)]
#[serde(bound = "T: EthSpec")]
#[arbitrary(bound = "T: EthSpec")]
pub struct LightClientBootstrap<T: EthSpec> {
#[derive(
Debug, Clone, Serialize, TreeHash, Encode, Deserialize, arbitrary::Arbitrary, PartialEq,
)]
#[serde(untagged)]
#[tree_hash(enum_behaviour = "transparent")]
#[ssz(enum_behaviour = "transparent")]
#[serde(bound = "E: EthSpec", deny_unknown_fields)]
#[arbitrary(bound = "E: EthSpec")]
pub struct LightClientBootstrap<E: EthSpec> {
/// The requested beacon block header.
pub header: LightClientHeader,
#[superstruct(only(Altair), partial_getter(rename = "header_altair"))]
pub header: LightClientHeaderAltair<E>,
#[superstruct(only(Capella), partial_getter(rename = "header_capella"))]
pub header: LightClientHeaderCapella<E>,
#[superstruct(only(Deneb), partial_getter(rename = "header_deneb"))]
pub header: LightClientHeaderDeneb<E>,
/// The `SyncCommittee` used in the requested period.
pub current_sync_committee: Arc<SyncCommittee<T>>,
pub current_sync_committee: Arc<SyncCommittee<E>>,
/// 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: &mut BeaconState<T>) -> Result<Self, Error> {
impl<E: EthSpec> LightClientBootstrap<E> {
pub fn get_slot<'a>(&'a self) -> Slot {
map_light_client_bootstrap_ref!(&'a _, self.to_ref(), |inner, cons| {
cons(inner);
inner.header.beacon.slot
})
}
pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result<Self, ssz::DecodeError> {
let bootstrap = match fork_name {
ForkName::Altair | ForkName::Merge => {
let header = LightClientBootstrapAltair::from_ssz_bytes(bytes)?;
Self::Altair(header)
}
ForkName::Capella => {
let header = LightClientBootstrapCapella::from_ssz_bytes(bytes)?;
Self::Capella(header)
}
ForkName::Deneb => {
let header = LightClientBootstrapDeneb::from_ssz_bytes(bytes)?;
Self::Deneb(header)
}
ForkName::Base => {
return Err(ssz::DecodeError::BytesInvalid(format!(
"LightClientBootstrap decoding for {fork_name} not implemented"
)))
}
};
Ok(bootstrap)
}
pub fn from_beacon_state(
beacon_state: &mut BeaconState<E>,
block: &SignedBeaconBlock<E>,
chain_spec: &ChainSpec,
) -> Result<Self, Error> {
let mut header = beacon_state.latest_block_header().clone();
header.state_root = beacon_state.update_tree_hash_cache()?;
let current_sync_committee_branch =
beacon_state.compute_merkle_proof(CURRENT_SYNC_COMMITTEE_INDEX)?;
Ok(LightClientBootstrap {
header: header.into(),
current_sync_committee: beacon_state.current_sync_committee()?.clone(),
current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?,
})
FixedVector::new(beacon_state.compute_merkle_proof(CURRENT_SYNC_COMMITTEE_INDEX)?)?;
let current_sync_committee = beacon_state.current_sync_committee()?.clone();
let light_client_bootstrap = match block
.fork_name(chain_spec)
.map_err(|_| Error::InconsistentFork)?
{
ForkName::Base => return Err(Error::AltairForkNotActive),
ForkName::Altair | ForkName::Merge => Self::Altair(LightClientBootstrapAltair {
header: LightClientHeaderAltair::block_to_light_client_header(block)?,
current_sync_committee,
current_sync_committee_branch,
}),
ForkName::Capella => Self::Capella(LightClientBootstrapCapella {
header: LightClientHeaderCapella::block_to_light_client_header(block)?,
current_sync_committee,
current_sync_committee_branch,
}),
ForkName::Deneb => Self::Deneb(LightClientBootstrapDeneb {
header: LightClientHeaderDeneb::block_to_light_client_header(block)?,
current_sync_committee,
current_sync_committee_branch,
}),
};
Ok(light_client_bootstrap)
}
}
impl<T: EthSpec> ForkVersionDeserialize for LightClientBootstrap<T> {
impl<E: EthSpec> ForkVersionDeserialize for LightClientBootstrap<E> {
fn deserialize_by_fork<'de, D: Deserializer<'de>>(
value: Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
match fork_name {
ForkName::Altair | ForkName::Merge => {
Ok(serde_json::from_value::<LightClientBootstrap<T>>(value)
ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => {
Ok(serde_json::from_value::<LightClientBootstrap<E>>(value)
.map_err(serde::de::Error::custom))?
}
ForkName::Base | ForkName::Capella | ForkName::Deneb => {
Err(serde::de::Error::custom(format!(
"LightClientBootstrap failed to deserialize: unsupported fork '{}'",
fork_name
)))
}
ForkName::Base => Err(serde::de::Error::custom(format!(
"LightClientBootstrap failed to deserialize: unsupported fork '{}'",
fork_name
))),
}
}
}
@@ -72,5 +151,5 @@ mod tests {
use super::*;
use crate::MainnetEthSpec;
ssz_tests!(LightClientBootstrap<MainnetEthSpec>);
ssz_tests!(LightClientBootstrapDeneb<MainnetEthSpec>);
}

View File

@@ -1,101 +1,177 @@
use super::{
EthSpec, FixedVector, Hash256, SignedBeaconBlock, SignedBlindedBeaconBlock, Slot, SyncAggregate,
};
use super::{EthSpec, FixedVector, Hash256, Slot, SyncAggregate};
use crate::ChainSpec;
use crate::{
light_client_update::*, test_utils::TestRandom, BeaconState, ChainSpec, ForkName,
ForkVersionDeserialize, LightClientHeader,
light_client_update::*, test_utils::TestRandom, ForkName, ForkVersionDeserialize,
LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, SignedBeaconBlock,
};
use derivative::Derivative;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use ssz_derive::{Decode, Encode};
use ssz::Decode;
use ssz_derive::Decode;
use ssz_derive::Encode;
use superstruct::superstruct;
use test_random_derive::TestRandom;
use tree_hash::TreeHash;
use tree_hash_derive::TreeHash;
/// A LightClientFinalityUpdate is the update light_client request or received by a gossip that
/// signal a new finalized beacon block header for the light client sync protocol.
#[derive(
Debug,
Clone,
PartialEq,
Serialize,
Deserialize,
Encode,
Decode,
TestRandom,
arbitrary::Arbitrary,
#[superstruct(
variants(Altair, Capella, Deneb),
variant_attributes(
derive(
Debug,
Clone,
PartialEq,
Serialize,
Deserialize,
Derivative,
Decode,
Encode,
TestRandom,
arbitrary::Arbitrary,
TreeHash,
),
serde(bound = "E: EthSpec", deny_unknown_fields),
arbitrary(bound = "E: EthSpec"),
)
)]
#[serde(bound = "T: EthSpec")]
#[arbitrary(bound = "T: EthSpec")]
pub struct LightClientFinalityUpdate<T: EthSpec> {
#[derive(
Debug, Clone, Serialize, Encode, TreeHash, Deserialize, arbitrary::Arbitrary, PartialEq,
)]
#[serde(untagged)]
#[tree_hash(enum_behaviour = "transparent")]
#[ssz(enum_behaviour = "transparent")]
#[serde(bound = "E: EthSpec", deny_unknown_fields)]
#[arbitrary(bound = "E: EthSpec")]
pub struct LightClientFinalityUpdate<E: EthSpec> {
/// The last `BeaconBlockHeader` from the last attested block by the sync committee.
pub attested_header: LightClientHeader,
#[superstruct(only(Altair), partial_getter(rename = "attested_header_altair"))]
pub attested_header: LightClientHeaderAltair<E>,
#[superstruct(only(Capella), partial_getter(rename = "attested_header_capella"))]
pub attested_header: LightClientHeaderCapella<E>,
#[superstruct(only(Deneb), partial_getter(rename = "attested_header_deneb"))]
pub attested_header: LightClientHeaderDeneb<E>,
/// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch).
pub finalized_header: LightClientHeader,
#[superstruct(only(Altair), partial_getter(rename = "finalized_header_altair"))]
pub finalized_header: LightClientHeaderAltair<E>,
#[superstruct(only(Capella), partial_getter(rename = "finalized_header_capella"))]
pub finalized_header: LightClientHeaderCapella<E>,
#[superstruct(only(Deneb), partial_getter(rename = "finalized_header_deneb"))]
pub finalized_header: LightClientHeaderDeneb<E>,
/// Merkle proof attesting finalized header.
pub finality_branch: FixedVector<Hash256, FinalizedRootProofLen>,
/// current sync aggreggate
pub sync_aggregate: SyncAggregate<T>,
pub sync_aggregate: SyncAggregate<E>,
/// Slot of the sync aggregated singature
pub signature_slot: Slot,
}
impl<T: EthSpec> LightClientFinalityUpdate<T> {
impl<E: EthSpec> LightClientFinalityUpdate<E> {
pub fn new(
attested_block: &SignedBeaconBlock<E>,
finalized_block: &SignedBeaconBlock<E>,
finality_branch: FixedVector<Hash256, FinalizedRootProofLen>,
sync_aggregate: SyncAggregate<E>,
signature_slot: Slot,
chain_spec: &ChainSpec,
beacon_state: &BeaconState<T>,
block: &SignedBeaconBlock<T>,
attested_state: &mut BeaconState<T>,
finalized_block: &SignedBlindedBeaconBlock<T>,
) -> Result<Self, Error> {
let altair_fork_epoch = chain_spec
.altair_fork_epoch
.ok_or(Error::AltairForkNotActive)?;
if beacon_state.slot().epoch(T::slots_per_epoch()) < altair_fork_epoch {
return Err(Error::AltairForkNotActive);
}
let finality_update = match attested_block
.fork_name(chain_spec)
.map_err(|_| Error::InconsistentFork)?
{
ForkName::Altair | ForkName::Merge => {
let finality_update = LightClientFinalityUpdateAltair {
attested_header: LightClientHeaderAltair::block_to_light_client_header(
attested_block,
)?,
finalized_header: LightClientHeaderAltair::block_to_light_client_header(
finalized_block,
)?,
finality_branch,
sync_aggregate,
signature_slot,
};
Self::Altair(finality_update)
}
ForkName::Capella => {
let finality_update = LightClientFinalityUpdateCapella {
attested_header: LightClientHeaderCapella::block_to_light_client_header(
attested_block,
)?,
finalized_header: LightClientHeaderCapella::block_to_light_client_header(
finalized_block,
)?,
finality_branch,
sync_aggregate,
signature_slot,
};
Self::Capella(finality_update)
}
ForkName::Deneb => {
let finality_update = LightClientFinalityUpdateDeneb {
attested_header: LightClientHeaderDeneb::block_to_light_client_header(
attested_block,
)?,
finalized_header: LightClientHeaderDeneb::block_to_light_client_header(
finalized_block,
)?,
finality_branch,
sync_aggregate,
signature_slot,
};
Self::Deneb(finality_update)
}
ForkName::Base => return Err(Error::AltairForkNotActive),
};
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);
}
Ok(finality_update)
}
// Compute and validate attested header.
let mut attested_header = attested_state.latest_block_header().clone();
attested_header.state_root = attested_state.update_tree_hash_cache()?;
// Build finalized header from finalized block
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.into(),
finalized_header: finalized_header.into(),
finality_branch: FixedVector::new(finality_branch)?,
sync_aggregate: sync_aggregate.clone(),
signature_slot: block.slot(),
pub fn get_attested_header_slot<'a>(&'a self) -> Slot {
map_light_client_finality_update_ref!(&'a _, self.to_ref(), |inner, cons| {
cons(inner);
inner.attested_header.beacon.slot
})
}
pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result<Self, ssz::DecodeError> {
let finality_update = match fork_name {
ForkName::Altair | ForkName::Merge => {
let finality_update = LightClientFinalityUpdateAltair::from_ssz_bytes(bytes)?;
Self::Altair(finality_update)
}
ForkName::Capella => {
let finality_update = LightClientFinalityUpdateCapella::from_ssz_bytes(bytes)?;
Self::Capella(finality_update)
}
ForkName::Deneb => {
let finality_update = LightClientFinalityUpdateDeneb::from_ssz_bytes(bytes)?;
Self::Deneb(finality_update)
}
ForkName::Base => {
return Err(ssz::DecodeError::BytesInvalid(format!(
"LightClientFinalityUpdate decoding for {fork_name} not implemented"
)))
}
};
Ok(finality_update)
}
}
impl<T: EthSpec> ForkVersionDeserialize for LightClientFinalityUpdate<T> {
impl<E: EthSpec> ForkVersionDeserialize for LightClientFinalityUpdate<E> {
fn deserialize_by_fork<'de, D: Deserializer<'de>>(
value: Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
match fork_name {
ForkName::Altair | ForkName::Merge => Ok(serde_json::from_value::<
LightClientFinalityUpdate<T>,
>(value)
.map_err(serde::de::Error::custom))?,
ForkName::Base | ForkName::Capella | ForkName::Deneb => {
Err(serde::de::Error::custom(format!(
"LightClientFinalityUpdate failed to deserialize: unsupported fork '{}'",
fork_name
)))
}
ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => Ok(
serde_json::from_value::<LightClientFinalityUpdate<E>>(value)
.map_err(serde::de::Error::custom),
)?,
ForkName::Base => Err(serde::de::Error::custom(format!(
"LightClientFinalityUpdate failed to deserialize: unsupported fork '{}'",
fork_name
))),
}
}
}
@@ -105,5 +181,5 @@ mod tests {
use super::*;
use crate::MainnetEthSpec;
ssz_tests!(LightClientFinalityUpdate<MainnetEthSpec>);
ssz_tests!(LightClientFinalityUpdateDeneb<MainnetEthSpec>);
}

View File

@@ -1,26 +1,209 @@
use crate::test_utils::TestRandom;
use crate::BeaconBlockHeader;
use crate::ChainSpec;
use crate::ForkName;
use crate::ForkVersionDeserialize;
use crate::{light_client_update::*, BeaconBlockBody};
use crate::{
test_utils::TestRandom, EthSpec, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb,
FixedVector, Hash256, SignedBeaconBlock,
};
use derivative::Derivative;
use serde::{Deserialize, Serialize};
use ssz::Decode;
use ssz_derive::{Decode, Encode};
use std::marker::PhantomData;
use superstruct::superstruct;
use test_random_derive::TestRandom;
use tree_hash_derive::TreeHash;
#[derive(
Debug,
Clone,
PartialEq,
Serialize,
Deserialize,
Encode,
Decode,
TestRandom,
arbitrary::Arbitrary,
#[superstruct(
variants(Altair, Capella, Deneb),
variant_attributes(
derive(
Debug,
Clone,
PartialEq,
Serialize,
Deserialize,
Derivative,
Decode,
Encode,
TestRandom,
arbitrary::Arbitrary,
TreeHash,
),
serde(bound = "E: EthSpec", deny_unknown_fields),
arbitrary(bound = "E: EthSpec"),
)
)]
pub struct LightClientHeader {
#[derive(
Debug, Clone, Serialize, TreeHash, Encode, Deserialize, arbitrary::Arbitrary, PartialEq,
)]
#[serde(untagged)]
#[tree_hash(enum_behaviour = "transparent")]
#[ssz(enum_behaviour = "transparent")]
#[serde(bound = "E: EthSpec", deny_unknown_fields)]
#[arbitrary(bound = "E: EthSpec")]
pub struct LightClientHeader<E: EthSpec> {
pub beacon: BeaconBlockHeader,
#[superstruct(
only(Capella),
partial_getter(rename = "execution_payload_header_capella")
)]
pub execution: ExecutionPayloadHeaderCapella<E>,
#[superstruct(only(Deneb), partial_getter(rename = "execution_payload_header_deneb"))]
pub execution: ExecutionPayloadHeaderDeneb<E>,
#[superstruct(only(Capella, Deneb))]
pub execution_branch: FixedVector<Hash256, ExecutionPayloadProofLen>,
#[ssz(skip_serializing, skip_deserializing)]
#[tree_hash(skip_hashing)]
#[serde(skip)]
#[arbitrary(default)]
pub _phantom_data: PhantomData<E>,
}
impl From<BeaconBlockHeader> for LightClientHeader {
fn from(beacon: BeaconBlockHeader) -> Self {
LightClientHeader { beacon }
impl<E: EthSpec> LightClientHeader<E> {
pub fn block_to_light_client_header(
block: &SignedBeaconBlock<E>,
chain_spec: &ChainSpec,
) -> Result<Self, Error> {
let header = match block
.fork_name(chain_spec)
.map_err(|_| Error::InconsistentFork)?
{
ForkName::Base => return Err(Error::AltairForkNotActive),
ForkName::Altair | ForkName::Merge => LightClientHeader::Altair(
LightClientHeaderAltair::block_to_light_client_header(block)?,
),
ForkName::Capella => LightClientHeader::Capella(
LightClientHeaderCapella::block_to_light_client_header(block)?,
),
ForkName::Deneb => LightClientHeader::Deneb(
LightClientHeaderDeneb::block_to_light_client_header(block)?,
),
};
Ok(header)
}
pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result<Self, ssz::DecodeError> {
let header = match fork_name {
ForkName::Altair | ForkName::Merge => {
let header = LightClientHeaderAltair::from_ssz_bytes(bytes)?;
LightClientHeader::Altair(header)
}
ForkName::Capella => {
let header = LightClientHeaderCapella::from_ssz_bytes(bytes)?;
LightClientHeader::Capella(header)
}
ForkName::Deneb => {
let header = LightClientHeaderDeneb::from_ssz_bytes(bytes)?;
LightClientHeader::Deneb(header)
}
ForkName::Base => {
return Err(ssz::DecodeError::BytesInvalid(format!(
"LightClientHeader decoding for {fork_name} not implemented"
)))
}
};
Ok(header)
}
/// Custom SSZ decoder that takes a `ForkName` as context.
pub fn from_ssz_bytes_for_fork(
bytes: &[u8],
fork_name: ForkName,
) -> Result<Self, ssz::DecodeError> {
Self::from_ssz_bytes(bytes, fork_name)
}
}
impl<E: EthSpec> LightClientHeaderAltair<E> {
pub fn block_to_light_client_header(block: &SignedBeaconBlock<E>) -> Result<Self, Error> {
Ok(LightClientHeaderAltair {
beacon: block.message().block_header(),
_phantom_data: PhantomData,
})
}
}
impl<E: EthSpec> LightClientHeaderCapella<E> {
pub fn block_to_light_client_header(block: &SignedBeaconBlock<E>) -> Result<Self, Error> {
let payload = block
.message()
.execution_payload()?
.execution_payload_capella()?;
let header = ExecutionPayloadHeaderCapella::from(payload);
let beacon_block_body = BeaconBlockBody::from(
block
.message()
.body_capella()
.map_err(|_| Error::BeaconBlockBodyError)?
.to_owned(),
);
let execution_branch =
beacon_block_body.block_body_merkle_proof(EXECUTION_PAYLOAD_INDEX)?;
return Ok(LightClientHeaderCapella {
beacon: block.message().block_header(),
execution: header,
execution_branch: FixedVector::new(execution_branch)?,
_phantom_data: PhantomData,
});
}
}
impl<E: EthSpec> LightClientHeaderDeneb<E> {
pub fn block_to_light_client_header(block: &SignedBeaconBlock<E>) -> Result<Self, Error> {
let payload = block
.message()
.execution_payload()?
.execution_payload_deneb()?;
let header = ExecutionPayloadHeaderDeneb::from(payload);
let beacon_block_body = BeaconBlockBody::from(
block
.message()
.body_deneb()
.map_err(|_| Error::BeaconBlockBodyError)?
.to_owned(),
);
let execution_branch =
beacon_block_body.block_body_merkle_proof(EXECUTION_PAYLOAD_INDEX)?;
Ok(LightClientHeaderDeneb {
beacon: block.message().block_header(),
execution: header,
execution_branch: FixedVector::new(execution_branch)?,
_phantom_data: PhantomData,
})
}
}
impl<T: EthSpec> ForkVersionDeserialize for LightClientHeader<T> {
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
match fork_name {
ForkName::Altair | ForkName::Merge => serde_json::from_value(value)
.map(|light_client_header| Self::Altair(light_client_header))
.map_err(serde::de::Error::custom),
ForkName::Capella => serde_json::from_value(value)
.map(|light_client_header| Self::Capella(light_client_header))
.map_err(serde::de::Error::custom),
ForkName::Deneb => serde_json::from_value(value)
.map(|light_client_header| Self::Deneb(light_client_header))
.map_err(serde::de::Error::custom),
ForkName::Base => Err(serde::de::Error::custom(format!(
"LightClientHeader deserialization for {fork_name} not implemented"
))),
}
}
}

View File

@@ -1,65 +1,155 @@
use super::{EthSpec, ForkName, ForkVersionDeserialize, Slot, SyncAggregate};
use crate::light_client_header::LightClientHeader;
use crate::test_utils::TestRandom;
use crate::{
light_client_update::Error, test_utils::TestRandom, BeaconState, ChainSpec, SignedBeaconBlock,
light_client_update::*, ChainSpec, LightClientHeaderAltair, LightClientHeaderCapella,
LightClientHeaderDeneb, SignedBeaconBlock,
};
use derivative::Derivative;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use ssz_derive::{Decode, Encode};
use ssz::Decode;
use ssz_derive::Decode;
use ssz_derive::Encode;
use superstruct::superstruct;
use test_random_derive::TestRandom;
use tree_hash::TreeHash;
use tree_hash::Hash256;
use tree_hash_derive::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.
#[derive(
Debug,
Clone,
PartialEq,
Serialize,
Deserialize,
Encode,
Decode,
TestRandom,
arbitrary::Arbitrary,
#[superstruct(
variants(Altair, Capella, Deneb),
variant_attributes(
derive(
Debug,
Clone,
PartialEq,
Serialize,
Deserialize,
Derivative,
Decode,
Encode,
TestRandom,
arbitrary::Arbitrary,
TreeHash,
),
serde(bound = "E: EthSpec", deny_unknown_fields),
arbitrary(bound = "E: EthSpec"),
)
)]
#[serde(bound = "T: EthSpec")]
#[arbitrary(bound = "T: EthSpec")]
pub struct LightClientOptimisticUpdate<T: EthSpec> {
#[derive(
Debug, Clone, Serialize, Encode, TreeHash, Deserialize, arbitrary::Arbitrary, PartialEq,
)]
#[serde(untagged)]
#[tree_hash(enum_behaviour = "transparent")]
#[ssz(enum_behaviour = "transparent")]
#[serde(bound = "E: EthSpec", deny_unknown_fields)]
#[arbitrary(bound = "E: EthSpec")]
pub struct LightClientOptimisticUpdate<E: EthSpec> {
/// The last `BeaconBlockHeader` from the last attested block by the sync committee.
pub attested_header: LightClientHeader,
#[superstruct(only(Altair), partial_getter(rename = "attested_header_altair"))]
pub attested_header: LightClientHeaderAltair<E>,
#[superstruct(only(Capella), partial_getter(rename = "attested_header_capella"))]
pub attested_header: LightClientHeaderCapella<E>,
#[superstruct(only(Deneb), partial_getter(rename = "attested_header_deneb"))]
pub attested_header: LightClientHeaderDeneb<E>,
/// current sync aggreggate
pub sync_aggregate: SyncAggregate<T>,
pub sync_aggregate: SyncAggregate<E>,
/// Slot of the sync aggregated singature
pub signature_slot: Slot,
}
impl<T: EthSpec> LightClientOptimisticUpdate<T> {
impl<E: EthSpec> LightClientOptimisticUpdate<E> {
pub fn new(
attested_block: &SignedBeaconBlock<E>,
sync_aggregate: SyncAggregate<E>,
signature_slot: Slot,
chain_spec: &ChainSpec,
block: &SignedBeaconBlock<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 optimistic_update = match attested_block
.fork_name(chain_spec)
.map_err(|_| Error::InconsistentFork)?
{
ForkName::Altair | ForkName::Merge => {
let optimistic_update = LightClientOptimisticUpdateAltair {
attested_header: LightClientHeaderAltair::block_to_light_client_header(
attested_block,
)?,
sync_aggregate,
signature_slot,
};
Self::Altair(optimistic_update)
}
ForkName::Capella => {
let optimistic_update = LightClientOptimisticUpdateCapella {
attested_header: LightClientHeaderCapella::block_to_light_client_header(
attested_block,
)?,
sync_aggregate,
signature_slot,
};
Self::Capella(optimistic_update)
}
ForkName::Deneb => {
let optimistic_update = LightClientOptimisticUpdateDeneb {
attested_header: LightClientHeaderDeneb::block_to_light_client_header(
attested_block,
)?,
sync_aggregate,
signature_slot,
};
Self::Deneb(optimistic_update)
}
ForkName::Base => return Err(Error::AltairForkNotActive),
};
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);
}
Ok(optimistic_update)
}
// 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: attested_header.into(),
sync_aggregate: sync_aggregate.clone(),
signature_slot: block.slot(),
pub fn get_slot<'a>(&'a self) -> Slot {
map_light_client_optimistic_update_ref!(&'a _, self.to_ref(), |inner, cons| {
cons(inner);
inner.attested_header.beacon.slot
})
}
pub fn get_canonical_root<'a>(&'a self) -> Hash256 {
map_light_client_optimistic_update_ref!(&'a _, self.to_ref(), |inner, cons| {
cons(inner);
inner.attested_header.beacon.canonical_root()
})
}
pub fn get_parent_root<'a>(&'a self) -> Hash256 {
map_light_client_optimistic_update_ref!(&'a _, self.to_ref(), |inner, cons| {
cons(inner);
inner.attested_header.beacon.parent_root
})
}
pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result<Self, ssz::DecodeError> {
let optimistic_update = match fork_name {
ForkName::Altair | ForkName::Merge => {
let optimistic_update = LightClientOptimisticUpdateAltair::from_ssz_bytes(bytes)?;
Self::Altair(optimistic_update)
}
ForkName::Capella => {
let optimistic_update = LightClientOptimisticUpdateCapella::from_ssz_bytes(bytes)?;
Self::Capella(optimistic_update)
}
ForkName::Deneb => {
let optimistic_update = LightClientOptimisticUpdateDeneb::from_ssz_bytes(bytes)?;
Self::Deneb(optimistic_update)
}
ForkName::Base => {
return Err(ssz::DecodeError::BytesInvalid(format!(
"LightClientOptimisticUpdate decoding for {fork_name} not implemented"
)))
}
};
Ok(optimistic_update)
}
}
impl<T: EthSpec> ForkVersionDeserialize for LightClientOptimisticUpdate<T> {
@@ -68,16 +158,14 @@ impl<T: EthSpec> ForkVersionDeserialize for LightClientOptimisticUpdate<T> {
fork_name: ForkName,
) -> Result<Self, D::Error> {
match fork_name {
ForkName::Altair | ForkName::Merge => Ok(serde_json::from_value::<
LightClientOptimisticUpdate<T>,
>(value)
.map_err(serde::de::Error::custom))?,
ForkName::Base | ForkName::Capella | ForkName::Deneb => {
Err(serde::de::Error::custom(format!(
"LightClientOptimisticUpdate failed to deserialize: unsupported fork '{}'",
fork_name
)))
}
ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => Ok(
serde_json::from_value::<LightClientOptimisticUpdate<T>>(value)
.map_err(serde::de::Error::custom),
)?,
ForkName::Base => Err(serde::de::Error::custom(format!(
"LightClientOptimisticUpdate failed to deserialize: unsupported fork '{}'",
fork_name
))),
}
}
}
@@ -87,5 +175,5 @@ mod tests {
use super::*;
use crate::MainnetEthSpec;
ssz_tests!(LightClientOptimisticUpdate<MainnetEthSpec>);
ssz_tests!(LightClientOptimisticUpdateDeneb<MainnetEthSpec>);
}

View File

@@ -1,28 +1,38 @@
use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee};
use super::{EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee};
use crate::{
beacon_state, test_utils::TestRandom, BeaconBlock, BeaconState, ChainSpec, ForkName,
ForkVersionDeserialize, LightClientHeader,
beacon_state, test_utils::TestRandom, BeaconBlock, BeaconBlockHeader, BeaconState, ChainSpec,
ForkName, ForkVersionDeserialize, LightClientHeaderAltair, LightClientHeaderCapella,
LightClientHeaderDeneb, SignedBeaconBlock,
};
use derivative::Derivative;
use safe_arith::ArithError;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use ssz_derive::{Decode, Encode};
use ssz_types::typenum::{U5, U6};
use ssz::Decode;
use ssz_derive::Decode;
use ssz_derive::Encode;
use ssz_types::typenum::{U4, U5, U6};
use std::sync::Arc;
use superstruct::superstruct;
use test_random_derive::TestRandom;
use tree_hash::TreeHash;
use tree_hash_derive::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 const EXECUTION_PAYLOAD_INDEX: usize = 25;
pub type FinalizedRootProofLen = U6;
pub type CurrentSyncCommitteeProofLen = U5;
pub type ExecutionPayloadProofLen = U4;
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;
pub const EXECUTION_PAYLOAD_PROOF_LEN: usize = 4;
#[derive(Debug, PartialEq, Clone)]
pub enum Error {
@@ -33,6 +43,8 @@ pub enum Error {
NotEnoughSyncCommitteeParticipants,
MismatchingPeriods,
InvalidFinalizedBlock,
BeaconBlockBodyError,
InconsistentFork,
}
impl From<ssz_types::Error> for Error {
@@ -53,77 +65,114 @@ impl From<ArithError> for Error {
}
}
/// A LightClientUpdate is the update we request solely to either complete the bootstraping process,
/// A LightClientUpdate is the update we request solely to either complete the bootstrapping 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].
#[derive(
Debug,
Clone,
PartialEq,
Serialize,
Deserialize,
Encode,
Decode,
TestRandom,
arbitrary::Arbitrary,
#[superstruct(
variants(Altair, Capella, Deneb),
variant_attributes(
derive(
Debug,
Clone,
PartialEq,
Serialize,
Deserialize,
Derivative,
Decode,
Encode,
TestRandom,
arbitrary::Arbitrary,
TreeHash,
),
serde(bound = "E: EthSpec", deny_unknown_fields),
arbitrary(bound = "E: EthSpec"),
)
)]
#[serde(bound = "T: EthSpec")]
#[arbitrary(bound = "T: EthSpec")]
pub struct LightClientUpdate<T: EthSpec> {
#[derive(
Debug, Clone, Serialize, Encode, TreeHash, Deserialize, arbitrary::Arbitrary, PartialEq,
)]
#[serde(untagged)]
#[tree_hash(enum_behaviour = "transparent")]
#[ssz(enum_behaviour = "transparent")]
#[serde(bound = "E: EthSpec", deny_unknown_fields)]
#[arbitrary(bound = "E: EthSpec")]
pub struct LightClientUpdate<E: EthSpec> {
/// The last `BeaconBlockHeader` from the last attested block by the sync committee.
pub attested_header: LightClientHeader,
#[superstruct(only(Altair), partial_getter(rename = "attested_header_altair"))]
pub attested_header: LightClientHeaderAltair<E>,
#[superstruct(only(Capella), partial_getter(rename = "attested_header_capella"))]
pub attested_header: LightClientHeaderCapella<E>,
#[superstruct(only(Deneb), partial_getter(rename = "attested_header_deneb"))]
pub attested_header: LightClientHeaderDeneb<E>,
/// The `SyncCommittee` used in the next period.
pub next_sync_committee: Arc<SyncCommittee<T>>,
pub next_sync_committee: Arc<SyncCommittee<E>>,
/// 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: LightClientHeader,
#[superstruct(only(Altair), partial_getter(rename = "finalized_header_altair"))]
pub finalized_header: LightClientHeaderAltair<E>,
#[superstruct(only(Capella), partial_getter(rename = "finalized_header_capella"))]
pub finalized_header: LightClientHeaderCapella<E>,
#[superstruct(only(Deneb), partial_getter(rename = "finalized_header_deneb"))]
pub finalized_header: LightClientHeaderDeneb<E>,
/// 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 sync_aggregate: SyncAggregate<E>,
/// Slot of the sync aggregated signature
pub signature_slot: Slot,
}
impl<T: EthSpec> LightClientUpdate<T> {
pub fn new(
chain_spec: ChainSpec,
beacon_state: BeaconState<T>,
block: BeaconBlock<T>,
attested_state: &mut 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);
impl<E: EthSpec> ForkVersionDeserialize for LightClientUpdate<E> {
fn deserialize_by_fork<'de, D: Deserializer<'de>>(
value: Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
match fork_name {
ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => {
Ok(serde_json::from_value::<LightClientUpdate<E>>(value)
.map_err(serde::de::Error::custom))?
}
ForkName::Base => Err(serde::de::Error::custom(format!(
"LightClientUpdate failed to deserialize: unsupported fork '{}'",
fork_name
))),
}
}
}
impl<E: EthSpec> LightClientUpdate<E> {
pub fn new(
beacon_state: BeaconState<E>,
block: BeaconBlock<E>,
attested_state: &mut BeaconState<E>,
attested_block: &SignedBeaconBlock<E>,
finalized_block: &SignedBeaconBlock<E>,
chain_spec: &ChainSpec,
) -> Result<Self, Error> {
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)?;
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)?;
.epoch(E::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(),
proposer_index: finalized_block.message().proposer_index(),
parent_root: finalized_block.parent_root(),
state_root: finalized_block.state_root(),
body_root: finalized_block.body_root(),
body_root: finalized_block.message().body_root(),
};
if finalized_header.tree_hash_root() != beacon_state.finalized_checkpoint().root {
return Err(Error::InvalidFinalizedBlock);
@@ -131,35 +180,84 @@ impl<T: EthSpec> LightClientUpdate<T> {
let next_sync_committee_branch =
attested_state.compute_merkle_proof(NEXT_SYNC_COMMITTEE_INDEX)?;
let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?;
Ok(Self {
attested_header: attested_header.into(),
next_sync_committee: attested_state.next_sync_committee()?.clone(),
next_sync_committee_branch: FixedVector::new(next_sync_committee_branch)?,
finalized_header: finalized_header.into(),
finality_branch: FixedVector::new(finality_branch)?,
sync_aggregate: sync_aggregate.clone(),
signature_slot: block.slot(),
})
}
}
impl<T: EthSpec> ForkVersionDeserialize for LightClientUpdate<T> {
fn deserialize_by_fork<'de, D: Deserializer<'de>>(
value: Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
match fork_name {
let light_client_update = match attested_block
.fork_name(chain_spec)
.map_err(|_| Error::InconsistentFork)?
{
ForkName::Base => return Err(Error::AltairForkNotActive),
ForkName::Altair | ForkName::Merge => {
Ok(serde_json::from_value::<LightClientUpdate<T>>(value)
.map_err(serde::de::Error::custom))?
let attested_header =
LightClientHeaderAltair::block_to_light_client_header(attested_block)?;
let finalized_header =
LightClientHeaderAltair::block_to_light_client_header(finalized_block)?;
Self::Altair(LightClientUpdateAltair {
attested_header,
next_sync_committee: attested_state.next_sync_committee()?.clone(),
next_sync_committee_branch: FixedVector::new(next_sync_committee_branch)?,
finalized_header,
finality_branch: FixedVector::new(finality_branch)?,
sync_aggregate: sync_aggregate.clone(),
signature_slot: block.slot(),
})
}
ForkName::Base | ForkName::Capella | ForkName::Deneb => {
Err(serde::de::Error::custom(format!(
"LightClientUpdate failed to deserialize: unsupported fork '{}'",
fork_name
ForkName::Capella => {
let attested_header =
LightClientHeaderCapella::block_to_light_client_header(attested_block)?;
let finalized_header =
LightClientHeaderCapella::block_to_light_client_header(finalized_block)?;
Self::Capella(LightClientUpdateCapella {
attested_header,
next_sync_committee: attested_state.next_sync_committee()?.clone(),
next_sync_committee_branch: FixedVector::new(next_sync_committee_branch)?,
finalized_header,
finality_branch: FixedVector::new(finality_branch)?,
sync_aggregate: sync_aggregate.clone(),
signature_slot: block.slot(),
})
}
ForkName::Deneb => {
let attested_header =
LightClientHeaderDeneb::block_to_light_client_header(attested_block)?;
let finalized_header =
LightClientHeaderDeneb::block_to_light_client_header(finalized_block)?;
Self::Deneb(LightClientUpdateDeneb {
attested_header,
next_sync_committee: attested_state.next_sync_committee()?.clone(),
next_sync_committee_branch: FixedVector::new(next_sync_committee_branch)?,
finalized_header,
finality_branch: FixedVector::new(finality_branch)?,
sync_aggregate: sync_aggregate.clone(),
signature_slot: block.slot(),
})
}
};
Ok(light_client_update)
}
pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result<Self, ssz::DecodeError> {
let update = match fork_name {
ForkName::Altair | ForkName::Merge => {
let update = LightClientUpdateAltair::from_ssz_bytes(bytes)?;
Self::Altair(update)
}
ForkName::Capella => {
let update = LightClientUpdateCapella::from_ssz_bytes(bytes)?;
Self::Capella(update)
}
ForkName::Deneb => {
let update = LightClientUpdateDeneb::from_ssz_bytes(bytes)?;
Self::Deneb(update)
}
ForkName::Base => {
return Err(ssz::DecodeError::BytesInvalid(format!(
"LightClientUpdate decoding for {fork_name} not implemented"
)))
}
}
};
Ok(update)
}
}
@@ -169,7 +267,7 @@ mod tests {
use crate::MainnetEthSpec;
use ssz_types::typenum::Unsigned;
ssz_tests!(LightClientUpdate<MainnetEthSpec>);
ssz_tests!(LightClientUpdateDeneb<MainnetEthSpec>);
#[test]
fn finalized_root_params() {

View File

@@ -20,7 +20,6 @@ macro_rules! ssz_tests {
let original = <$type>::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
println!("bytes length: {}", bytes.len());
let decoded = <$type>::from_ssz_bytes(&bytes).unwrap();
assert_eq!(original, decoded);