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

Merged
merged 5 commits into from
May 6, 2025
Merged
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
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions bin/portal-bridge/src/api/consensus/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use alloy::primitives::B256;
use anyhow::{anyhow, bail};
use constants::DEFAULT_BEACON_STATE_REQUEST_TIMEOUT;
use ethportal_api::{
consensus::beacon_state::BeaconStateDeneb,
consensus::beacon_state::BeaconStateElectra,
light_client::{
bootstrap::LightClientBootstrapDeneb, finality_update::LightClientFinalityUpdateDeneb,
optimistic_update::LightClientOptimisticUpdateDeneb, update::LightClientUpdateDeneb,
Expand Down Expand Up @@ -130,7 +130,7 @@ impl ConsensusApi {
}

/// Requests the `BeaconState` structure corresponding to the current head of the beacon chain.
pub async fn get_beacon_state(&self) -> anyhow::Result<BeaconStateDeneb> {
pub async fn get_beacon_state(&self) -> anyhow::Result<BeaconStateElectra> {
let endpoint = "/eth/v2/debug/beacon/states/finalized".to_string();
Ok(self
.request(endpoint, Some(DEFAULT_BEACON_STATE_REQUEST_TIMEOUT))
Expand Down
43 changes: 17 additions & 26 deletions bin/portal-bridge/src/bridge/beacon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ use std::{
};

use alloy::primitives::B256;
use anyhow::{bail, ensure};
use anyhow::bail;
use ethportal_api::{
consensus::historical_summaries::{
HistoricalSummariesStateProof, HistoricalSummariesWithProof,
},
consensus::historical_summaries::HistoricalSummariesWithProof,
light_client::update::LightClientUpdate,
types::{
consensus::fork::ForkName,
Expand All @@ -35,6 +33,7 @@ use tokio::{
use tracing::{error, info, warn, Instrument};
use trin_beacon::network::BeaconNetwork;
use trin_metrics::bridge::BridgeMetricsReporter;
use trin_validation::constants::SLOTS_PER_EPOCH;

use super::{constants::SERVE_BLOCK_TIMEOUT, offer_report::OfferReport};
use crate::{
Expand All @@ -45,12 +44,8 @@ use crate::{
utils::{duration_until_next_update, expected_current_slot},
};

/// The number of slots in an epoch.
const SLOTS_PER_EPOCH: u64 = 32;
/// The number of slots in a sync committee period.
const SLOTS_PER_PERIOD: u64 = SLOTS_PER_EPOCH * 256;
/// The historical summaries proof always has a length of 5 hashes.
const HISTORICAL_SUMMARIES_PROOF_LENGTH: usize = 5;

/// A helper struct to hold the finalized beacon state metadata.
#[derive(Clone, Debug, Default)]
Expand Down Expand Up @@ -470,32 +465,28 @@ impl BeaconBridge {
info!("Downloading beacon state for HistoricalSummariesWithProof generation...");
finalized_state_root.lock().await.in_progress = true;
let beacon_state = consensus_api.get_beacon_state().await?;
let state_epoch = beacon_state.slot / SLOTS_PER_EPOCH;
let beacon_state_epoch = beacon_state.slot / SLOTS_PER_EPOCH;

let historical_summaries_proof = beacon_state.build_historical_summaries_proof();
// Ensure the historical summaries proof is of the correct length
ensure!(
historical_summaries_proof.len() == HISTORICAL_SUMMARIES_PROOF_LENGTH,
"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 {
epoch: state_epoch,
historical_summaries,
proof: HistoricalSummariesStateProof::from(historical_summaries_proof),
},
let historical_summaries_with_proof = HistoricalSummariesWithProof {
epoch: beacon_state_epoch,
historical_summaries: beacon_state.historical_summaries,
proof: historical_summaries_proof,
};
info!(
epoch = %state_epoch,
epoch = %beacon_state_epoch,
"Generated HistoricalSummariesWithProof",
);
let content_key =
BeaconContentKey::HistoricalSummariesWithProof(HistoricalSummariesWithProofKey {
epoch: state_epoch,
epoch: beacon_state_epoch,
});
let content_value =
BeaconContentValue::HistoricalSummariesWithProof(historical_summaries_with_proof);
let content_value = BeaconContentValue::HistoricalSummariesWithProof(
ForkVersionedHistoricalSummariesWithProof {
fork_name: ForkName::Electra,
historical_summaries_with_proof,
},
);

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
49 changes: 48 additions & 1 deletion crates/ethportal-api/src/types/consensus/beacon_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::consensus::{
},
fork::ForkName,
header::BeaconBlockHeader,
historical_summaries::HistoricalSummaries,
historical_summaries::{HistoricalSummaries, HistoricalSummariesProof},
participation_flags::ParticipationFlags,
pending_balance_deposit::PendingDeposit,
pending_consolidation::PendingConsolidation,
Expand Down Expand Up @@ -240,6 +240,53 @@ impl BeaconStateDeneb {
}
}

impl BeaconStateElectra {
pub fn build_historical_summaries_proof(&self) -> HistoricalSummariesProof {
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(),
];

HistoricalSummariesProof::new(build_merkle_proof_for_index(leaves, 27))
.expect("Created historical summaries proof for Electra should have correct length")
}
}

/// Specifies a fork of the `BeaconChain`, to prevent replay attacks.
///
/// Spec v0.12.1
Expand Down
3 changes: 2 additions & 1 deletion crates/ethportal-api/src/types/consensus/fork.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ use crate::utils::bytes::hex_encode;
#[error("Unknown fork for digest: {0}")]
pub struct ParseForkNameError(String);

pub type ForkDigest = [u8; 4];
pub const FORK_DIGEST_LEN: usize = 4;
pub type ForkDigest = [u8; FORK_DIGEST_LEN];

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ForkName {
Expand Down
19 changes: 15 additions & 4 deletions crates/ethportal-api/src/types/consensus/historical_summaries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ use ssz_derive::{Decode, Encode};
use ssz_types::{typenum, FixedVector, VariableList};
use tree_hash_derive::TreeHash;

/// The Generalized Index of the `historical_summaries` field of the
/// [BeaconState](super::beacon_state::BeaconState), for Electra fork.
pub const HISTORICAL_SUMMARIES_GINDEX: usize = 91;

/// `HistoricalSummary` matches the components of the phase0 `HistoricalBatch`
/// making the two hash_tree_root-compatible. This struct is introduced into the beacon state
/// in the Capella hard fork.
Expand All @@ -15,13 +19,20 @@ pub struct HistoricalSummary {
pub state_summary_root: B256,
}

/// The historical list of [HistoricalSummary].
///
/// This correspond to the `historical_summaries` field of the
/// [BeaconState](super::beacon_state::BeaconState).
pub type HistoricalSummaries = VariableList<HistoricalSummary, typenum::U16777216>;
pub type HistoricalSummariesStateProof = FixedVector<B256, typenum::U5>;

/// A historical summaries BeaconState field with proof.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode)]
/// The merkle proof of the `historical_summaries` field of the
/// [BeaconState](super::beacon_state::BeaconState).
pub type HistoricalSummariesProof = FixedVector<B256, typenum::U6>;

/// The `historical_summaries` field of the BeaconState, with proof.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Encode, Decode)]
pub struct HistoricalSummariesWithProof {
pub epoch: u64,
pub historical_summaries: HistoricalSummaries,
pub proof: HistoricalSummariesStateProof,
pub proof: HistoricalSummariesProof,
}
Loading