Skip to content

feat: update beacon content types to support Pectra #1801

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 10 additions & 13 deletions bin/portal-bridge/src/bridge/beacon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use alloy::primitives::B256;
use anyhow::{bail, ensure};
use ethportal_api::{
consensus::historical_summaries::{
HistoricalSummariesStateProof, HistoricalSummariesWithProof,
HistoricalSummariesProofDeneb, HistoricalSummariesWithProof,
HistoricalSummariesWithProofDeneb,
},
light_client::update::LightClientUpdate,
types::{
Expand All @@ -17,10 +18,7 @@ use ethportal_api::{
HistoricalSummariesWithProofKey, LightClientFinalityUpdateKey,
LightClientOptimisticUpdateKey,
},
content_value::beacon::{
ForkVersionedHistoricalSummariesWithProof, ForkVersionedLightClientUpdate,
LightClientUpdatesByRange,
},
content_value::beacon::{ForkVersionedLightClientUpdate, LightClientUpdatesByRange},
network::Subnetwork,
portal_wire::OfferTrace,
},
Expand Down Expand Up @@ -478,14 +476,12 @@ impl BeaconBridge {
"Historical summaries proof length is not 5"
);
let historical_summaries = beacon_state.historical_summaries;
let historical_summaries_with_proof = ForkVersionedHistoricalSummariesWithProof {
fork_name: ForkName::Deneb,
historical_summaries_with_proof: HistoricalSummariesWithProof {
let historical_summaries_with_proof =
HistoricalSummariesWithProof::Deneb(HistoricalSummariesWithProofDeneb {
epoch: state_epoch,
historical_summaries,
proof: HistoricalSummariesStateProof::from(historical_summaries_proof),
},
};
proof: HistoricalSummariesProofDeneb::from(historical_summaries_proof),
});
info!(
epoch = %state_epoch,
"Generated HistoricalSummariesWithProof",
Expand All @@ -494,8 +490,9 @@ impl BeaconBridge {
BeaconContentKey::HistoricalSummariesWithProof(HistoricalSummariesWithProofKey {
epoch: state_epoch,
});
let content_value =
BeaconContentValue::HistoricalSummariesWithProof(historical_summaries_with_proof);
let content_value = BeaconContentValue::HistoricalSummariesWithProof(
historical_summaries_with_proof.into(),
);

Self::spawn_offer_tasks(beacon_network, content_key, content_value, metrics, census);
finalized_state_root.lock().await.state_root = latest_finalized_state_root;
Expand Down
46 changes: 46 additions & 0 deletions crates/ethportal-api/src/types/consensus/beacon_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,52 @@ impl BeaconStateDeneb {
}
}

impl BeaconStateElectra {
pub fn build_historical_summaries_proof(&self) -> Vec<B256> {
let leaves = [
self.genesis_time.tree_hash_root(),
self.genesis_validators_root.tree_hash_root(),
self.slot.tree_hash_root(),
self.fork.tree_hash_root(),
self.latest_block_header.tree_hash_root(),
self.block_roots.tree_hash_root(),
self.state_roots.tree_hash_root(),
self.historical_roots.tree_hash_root(),
self.eth1_data.tree_hash_root(),
self.eth1_data_votes.tree_hash_root(),
self.eth1_deposit_index.tree_hash_root(),
self.validators.tree_hash_root(),
self.balances.tree_hash_root(),
self.randao_mixes.tree_hash_root(),
self.slashings.tree_hash_root(),
self.previous_epoch_participation.tree_hash_root(),
self.current_epoch_participation.tree_hash_root(),
self.justification_bits.tree_hash_root(),
self.previous_justified_checkpoint.tree_hash_root(),
self.current_justified_checkpoint.tree_hash_root(),
self.finalized_checkpoint.tree_hash_root(),
self.inactivity_scores.tree_hash_root(),
self.current_sync_committee.tree_hash_root(),
self.next_sync_committee.tree_hash_root(),
self.latest_execution_payload_header.tree_hash_root(),
self.next_withdrawal_index.tree_hash_root(),
self.next_withdrawal_validator_index.tree_hash_root(),
self.historical_summaries.tree_hash_root(),
self.deposit_requests_start_index.tree_hash_root(),
self.deposit_balance_to_consume.tree_hash_root(),
self.exit_balance_to_consume.tree_hash_root(),
self.earliest_exit_epoch.tree_hash_root(),
self.consolidation_balance_to_consume.tree_hash_root(),
self.earliest_consolidation_epoch.tree_hash_root(),
self.pending_deposits.tree_hash_root(),
self.pending_partial_withdrawals.tree_hash_root(),
self.pending_consolidations.tree_hash_root(),
];

build_merkle_proof_for_index(leaves, 27)
}
}

/// Specifies a fork of the `BeaconChain`, to prevent replay attacks.
///
/// Spec v0.12.1
Expand Down
24 changes: 21 additions & 3 deletions crates/ethportal-api/src/types/consensus/historical_summaries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use alloy::primitives::B256;
use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use ssz_types::{typenum, FixedVector, VariableList};
use superstruct::superstruct;
use tree_hash_derive::TreeHash;

/// `HistoricalSummary` matches the components of the phase0 `HistoricalBatch`
Expand All @@ -16,12 +17,29 @@ pub struct HistoricalSummary {
}

pub type HistoricalSummaries = VariableList<HistoricalSummary, typenum::U16777216>;
pub type HistoricalSummariesStateProof = FixedVector<B256, typenum::U5>;

pub const HISTORICAL_SUMMARIES_GINDEX_DENEB: usize = 59;
pub const HISTORICAL_SUMMARIES_GINDEX_ELECTRA: usize = 91;

pub type HistoricalSummariesProofDeneb = FixedVector<B256, typenum::U5>;
pub type HistoricalSummariesProofElectra = FixedVector<B256, typenum::U6>;

/// A historical summaries BeaconState field with proof.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode)]
#[superstruct(
variants(Deneb, Electra),
variant_attributes(
derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Encode, Decode,),
serde(deny_unknown_fields),
)
)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Encode)]
#[serde(untagged)]
#[ssz(enum_behaviour = "transparent")]
pub struct HistoricalSummariesWithProof {
pub epoch: u64,
pub historical_summaries: HistoricalSummaries,
pub proof: HistoricalSummariesStateProof,
#[superstruct(only(Deneb), partial_getter(rename = "proof_deneb"))]
pub proof: HistoricalSummariesProofDeneb,
#[superstruct(only(Electra), partial_getter(rename = "proof_electra"))]
pub proof: HistoricalSummariesProofElectra,
}
112 changes: 79 additions & 33 deletions crates/ethportal-api/src/types/content_value/beacon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ use ssz::{Decode, DecodeError, Encode};
use ssz_types::{typenum::U128, VariableList};

use crate::{
consensus::historical_summaries::{
HistoricalSummariesWithProof, HistoricalSummariesWithProofElectra,
},
light_client::{
bootstrap::{LightClientBootstrapDeneb, LightClientBootstrapElectra},
finality_update::{LightClientFinalityUpdateDeneb, LightClientFinalityUpdateElectra},
Expand All @@ -14,7 +17,7 @@ use crate::{
types::{
consensus::{
fork::{ForkDigest, ForkName},
historical_summaries::HistoricalSummariesWithProof,
historical_summaries::HistoricalSummariesWithProofDeneb,
light_client::{
bootstrap::{
LightClientBootstrap, LightClientBootstrapBellatrix,
Expand Down Expand Up @@ -482,52 +485,92 @@ pub struct ForkVersionedHistoricalSummariesWithProof {
pub historical_summaries_with_proof: HistoricalSummariesWithProof,
}

impl ForkVersionedHistoricalSummariesWithProof {
pub fn encode(&self) -> Vec<u8> {
let fork_digest = self.fork_name.as_fork_digest();

let mut data = fork_digest.to_vec();
data.extend(self.historical_summaries_with_proof.as_ssz_bytes());
data
impl From<HistoricalSummariesWithProof> for ForkVersionedHistoricalSummariesWithProof {
fn from(historical_summaries_with_proof: HistoricalSummariesWithProof) -> Self {
let fork_name = match &historical_summaries_with_proof {
HistoricalSummariesWithProof::Deneb(_) => ForkName::Deneb,
HistoricalSummariesWithProof::Electra(_) => ForkName::Electra,
};
Self {
fork_name,
historical_summaries_with_proof,
}
}
}

pub fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
let fork_digest = ForkDigest::try_from(&buf[0..4]).map_err(|err| {
DecodeError::BytesInvalid(format!("Unable to decode fork digest: {err:?}"))
})?;
let fork_name = ForkName::try_from(fork_digest).map_err(|_| {
DecodeError::BytesInvalid(format!("Unable to decode fork name: {fork_digest:?}"))
})?;
let summaries_with_proof = HistoricalSummariesWithProof::from_ssz_bytes(&buf[4..])?;

Ok(Self {
fork_name,
historical_summaries_with_proof: summaries_with_proof,
})
impl ForkVersionedHistoricalSummariesWithProof {
pub fn epoch(&self) -> u64 {
*self.historical_summaries_with_proof.epoch()
}
}

impl Decode for ForkVersionedHistoricalSummariesWithProof {
impl Encode for ForkVersionedHistoricalSummariesWithProof {
fn is_ssz_fixed_len() -> bool {
false
}

fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
Self::decode(bytes)
fn ssz_append(&self, buf: &mut Vec<u8>) {
self.fork_name.as_fork_digest().ssz_append(buf);
self.historical_summaries_with_proof.ssz_append(buf);
}

fn ssz_fixed_len() -> usize {
ssz::BYTES_PER_LENGTH_OFFSET
}

fn ssz_bytes_len(&self) -> usize {
self.fork_name.as_fork_digest().ssz_bytes_len()
+ self.historical_summaries_with_proof.ssz_bytes_len()
}
}

impl Encode for ForkVersionedHistoricalSummariesWithProof {
impl Decode for ForkVersionedHistoricalSummariesWithProof {
fn is_ssz_fixed_len() -> bool {
false
}

fn ssz_append(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&self.encode());
fn ssz_fixed_len() -> usize {
ssz::BYTES_PER_LENGTH_OFFSET
}

fn ssz_bytes_len(&self) -> usize {
self.encode().len()
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
let fork_digest_len = <ForkDigest as Decode>::ssz_fixed_len();
let Some((fork_digest_bytes, historical_summaries_with_proof_bytes)) =
bytes.split_at_checked(fork_digest_len)
else {
return Err(DecodeError::InvalidByteLength {
len: bytes.len(),
expected: fork_digest_len,
});
};

let fork_digest = ForkDigest::from_ssz_bytes(fork_digest_bytes)?;
let fork_name = match ForkName::try_from(fork_digest) {
Ok(fork_name) => fork_name,
Err(err) => return Err(DecodeError::BytesInvalid(err.to_string())),
};

let historical_summaries_with_proof = match fork_name {
ForkName::Bellatrix | ForkName::Capella => {
return Err(DecodeError::BytesInvalid(format!(
"HistoricalSummariesWithProof not supported for fork digest: {fork_digest:?}"
)))
}
ForkName::Deneb => HistoricalSummariesWithProof::Deneb(
HistoricalSummariesWithProofDeneb::from_ssz_bytes(
historical_summaries_with_proof_bytes,
)?,
),
ForkName::Electra => HistoricalSummariesWithProof::Electra(
HistoricalSummariesWithProofElectra::from_ssz_bytes(
historical_summaries_with_proof_bytes,
)?,
),
};
Ok(Self {
fork_name,
historical_summaries_with_proof,
})
}
}

Expand Down Expand Up @@ -710,11 +753,14 @@ mod test {

match &beacon_content {
BeaconContentValue::HistoricalSummariesWithProof(content) => {
assert_eq!(
expected_epoch,
content.historical_summaries_with_proof.epoch
);
assert_eq!(ForkName::Deneb, content.fork_name);

let Ok(historical_summaries_with_proof) =
content.historical_summaries_with_proof.as_deneb()
else {
panic!("Expected Deneb historical summaries, actual: {content:?}");
};
assert_eq!(expected_epoch, historical_summaries_with_proof.epoch);
}
_ => panic!("Invalid beacon content type!"),
}
Expand Down
17 changes: 11 additions & 6 deletions crates/subnetworks/beacon/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -717,11 +717,12 @@ fn prune_old_bootstrap_data(
#[allow(clippy::unwrap_used)]
mod test {
use ethportal_api::{
consensus::historical_summaries::HistoricalSummariesWithProof,
types::content_key::beacon::{
HistoricalSummariesWithProofKey, LightClientFinalityUpdateKey,
LightClientOptimisticUpdateKey,
},
LightClientBootstrapKey, LightClientUpdatesByRangeKey,
BeaconContentValue, ContentValue, LightClientBootstrapKey, LightClientUpdatesByRangeKey,
};
use tree_hash::TreeHash;
use trin_storage::test_utils::create_test_portal_storage_config_with_capacity;
Expand Down Expand Up @@ -872,14 +873,18 @@ mod test {
fn test_beacon_storage_get_put_historical_summaries() {
let (_temp_dir, config) = create_test_portal_storage_config_with_capacity(10).unwrap();
let mut storage = BeaconStorage::new(config).unwrap();
let (value, _) = test_utils::get_history_summaries_with_proof();
let epoch = value.historical_summaries_with_proof.epoch;
let (historical_summaries_with_proof, _) =
test_utils::get_deneb_historical_summaries_with_proof();
let epoch = historical_summaries_with_proof.epoch;
let value = BeaconContentValue::HistoricalSummariesWithProof(
HistoricalSummariesWithProof::Deneb(historical_summaries_with_proof).into(),
);
let key = BeaconContentKey::HistoricalSummariesWithProof(HistoricalSummariesWithProofKey {
epoch,
});
storage.put(key.clone(), value.as_ssz_bytes()).unwrap();
storage.put(key.clone(), value.encode()).unwrap();
let result = storage.get(&key).unwrap().unwrap();
assert_eq!(result, value.as_ssz_bytes());
assert_eq!(result, value.encode());

// Test is_key_within_radius_and_unavailable for the same epoch
let should_store_content = storage.is_key_within_radius_and_unavailable(&key).unwrap();
Expand Down Expand Up @@ -911,7 +916,7 @@ mod test {
epoch: 0,
});
let result = storage.get(&key).unwrap().unwrap();
assert_eq!(result, value.as_ssz_bytes());
assert_eq!(result, value.encode());
}

#[test]
Expand Down
Loading