Skip to content

feat: enable post-capella HistoricalSummary Header validation #1774

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 2 commits into from
May 1, 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
1 change: 1 addition & 0 deletions Cargo.lock

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

11 changes: 6 additions & 5 deletions bin/e2hs-writer/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use reqwest::{
};
use tracing::info;
use trin_execution::era::binary_search::EraBinarySearch;
use trin_validation::constants::EPOCH_SIZE;
use trin_validation::constants::SLOTS_PER_HISTORICAL_ROOT;

pub enum EraSource {
// processed era1 file
Expand Down Expand Up @@ -91,8 +91,8 @@ pub struct EraProvider {
impl EraProvider {
pub async fn new(epoch: u64) -> anyhow::Result<Self> {
info!("Fetching e2store files for epoch: {epoch}");
let starting_block = epoch * EPOCH_SIZE;
let ending_block = starting_block + EPOCH_SIZE;
let starting_block = epoch * SLOTS_PER_HISTORICAL_ROOT;
let ending_block = starting_block + SLOTS_PER_HISTORICAL_ROOT;
let http_client = Client::builder()
.default_headers(HeaderMap::from_iter([(
CONTENT_TYPE,
Expand All @@ -108,7 +108,7 @@ impl EraProvider {
while next_block < ending_block {
let source = if !network_spec().is_paris_active_at_block(next_block) {
let era1_paths = get_era1_files(&http_client).await?;
let epoch_index = next_block / EPOCH_SIZE;
let epoch_index = next_block / SLOTS_PER_HISTORICAL_ROOT;
let era1_path = era1_paths.get(&epoch_index).ok_or(anyhow!(
"Era1 file not found for epoch index: {epoch_index}",
))?;
Expand Down Expand Up @@ -162,7 +162,8 @@ impl EraProvider {
);
Ok(match &self.sources[0] {
EraSource::PreMerge(era1) => {
let block_tuple = era1.block_tuples[(block_number % EPOCH_SIZE) as usize].clone();
let block_tuple =
era1.block_tuples[(block_number % SLOTS_PER_HISTORICAL_ROOT) as usize].clone();
ensure!(
block_tuple.header.header.number == block_number,
"Block number mismatch",
Expand Down
12 changes: 5 additions & 7 deletions bin/e2hs-writer/src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ use ethportal_api::{
use portal_bridge::api::execution::ExecutionApi;
use tokio::try_join;
use trin_execution::era::beacon::decode_transactions;
use trin_validation::{
accumulator::PreMergeAccumulator, constants::EPOCH_SIZE, header_validator::HeaderValidator,
};
use trin_validation::{accumulator::PreMergeAccumulator, constants::SLOTS_PER_HISTORICAL_ROOT};
use url::Url;

use crate::{
Expand Down Expand Up @@ -63,27 +61,27 @@ impl EpochReader {
) -> anyhow::Result<Self> {
let execution_api = ExecutionApi::new(el_provider_url.clone(), el_provider_url, 10).await?;
let latest_block = execution_api.get_latest_block_number().await?;
let maximum_epoch = latest_block / EPOCH_SIZE;
let maximum_epoch = latest_block / SLOTS_PER_HISTORICAL_ROOT;
ensure!(
epoch_index <= maximum_epoch,
"Epoch {epoch_index} is greater than the maximum epoch {maximum_epoch}"
);

let starting_block = epoch_index * EPOCH_SIZE;
let starting_block = epoch_index * SLOTS_PER_HISTORICAL_ROOT;
let epoch_accumulator = if network_spec().is_paris_active_at_block(starting_block) {
None
} else {
Some(Arc::new(
lookup_epoch_acc(
epoch_index,
&HeaderValidator::new().pre_merge_acc,
&PreMergeAccumulator::default(),
&epoch_acc_path,
)
.await?,
))
};

let ending_block = starting_block + EPOCH_SIZE;
let ending_block = starting_block + SLOTS_PER_HISTORICAL_ROOT;
let is_paris_active = network_spec().is_paris_active_at_block(ending_block);
let receipts_handle = tokio::spawn(async move {
if is_paris_active {
Expand Down
26 changes: 20 additions & 6 deletions bin/portal-bridge/src/bridge/e2hs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ use e2store::{
utils::get_e2hs_files,
};
use ethportal_api::{
consensus::historical_summaries::HistoricalSummaries,
types::{
execution::header_with_proof::HeaderWithProof, network::Subnetwork, portal_wire::OfferTrace,
execution::header_with_proof::{BlockHeaderProof, HeaderWithProof},
network::Subnetwork,
portal_wire::OfferTrace,
},
BlockBody, ContentValue, HistoryContentKey, HistoryContentValue, OverlayContentKey,
RawContentValue, Receipts,
Expand Down Expand Up @@ -95,7 +98,9 @@ impl E2HSBridge {
Ok(Self {
gossiper,
block_semaphore,
header_validator: HeaderValidator::new(),
header_validator: HeaderValidator::new_with_historical_summaries(
HistoricalSummaries::default(),
),
block_range,
random_fill,
e2hs_files,
Expand Down Expand Up @@ -171,7 +176,7 @@ impl E2HSBridge {
continue;
}
}
if let Err(err) = self.validate_block_tuple(&block_tuple) {
if let Err(err) = self.validate_block_tuple(&block_tuple).await {
error!("Failed to validate block tuple: {err:?}");
continue;
}
Expand Down Expand Up @@ -206,10 +211,19 @@ impl E2HSBridge {
.unwrap_or_else(|err| panic!("unable to read e2hs file at path: {e2hs_path:?} : {err}"))
}

fn validate_block_tuple(&self, block_tuple: &BlockTuple) -> anyhow::Result<()> {
async fn validate_block_tuple(&self, block_tuple: &BlockTuple) -> anyhow::Result<()> {
let header_with_proof = &block_tuple.header_with_proof.header_with_proof;
self.header_validator
.validate_header_with_proof(header_with_proof)?;
// The E2HS bridge doesn't have access to a provider so it can't validate historical summary
// Header with Proofs
if !matches!(
header_with_proof.proof,
BlockHeaderProof::HistoricalSummariesCapella(_)
| BlockHeaderProof::HistoricalSummariesDeneb(_)
) {
self.header_validator
.validate_header_with_proof(header_with_proof)
.await?;
}
let body = &block_tuple.body.body;
body.validate_against_header(&header_with_proof.header)?;
let receipts = &block_tuple.receipts.receipts;
Expand Down
10 changes: 5 additions & 5 deletions bin/portal-bridge/src/types/range.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::collections::HashMap;

use trin_validation::constants::EPOCH_SIZE;
use trin_validation::constants::SLOTS_PER_HISTORICAL_ROOT;

/// Converts a block range into a mapping of epoch indexes to block ranges.
///
Expand All @@ -16,14 +16,14 @@ pub fn block_range_to_epochs(start_block: u64, end_block: u64) -> HashMap<u64, O
let mut epoch_map = HashMap::new();

// Calculate start and end epochs
let start_epoch = start_block / EPOCH_SIZE;
let end_epoch = end_block / EPOCH_SIZE;
let start_epoch = start_block / SLOTS_PER_HISTORICAL_ROOT;
let end_epoch = end_block / SLOTS_PER_HISTORICAL_ROOT;

// Process each epoch in the range
for epoch in start_epoch..=end_epoch {
// Calculate the first and last block of the current epoch
let epoch_first_block = epoch * EPOCH_SIZE;
let epoch_last_block = epoch_first_block + EPOCH_SIZE - 1;
let epoch_first_block = epoch * SLOTS_PER_HISTORICAL_ROOT;
let epoch_last_block = epoch_first_block + SLOTS_PER_HISTORICAL_ROOT - 1;

// Determine if the epoch is fully or partially covered
if epoch_first_block >= start_block && epoch_last_block <= end_block {
Expand Down
9 changes: 1 addition & 8 deletions bin/trin/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use std::{net::SocketAddr, path::PathBuf, sync::Arc};

use ethportal_api::{
types::{distance::Distance, network::Subnetwork},
utils::bytes::hex_encode,
version::get_trin_version,
};
use portalnet::{
Expand All @@ -14,7 +13,6 @@ use portalnet::{
use rpc::{config::RpcConfig, launch_jsonrpc_server, RpcServerHandle};
use tokio::sync::{mpsc, RwLock};
use tracing::info;
use tree_hash::TreeHash;
use trin_beacon::initialize_beacon_network;
use trin_history::initialize_history_network;
use trin_state::initialize_state_network;
Expand Down Expand Up @@ -121,12 +119,7 @@ async fn run_trin_internal(
}

// Initialize validation oracle
let header_oracle = HeaderOracle::default();
info!(
hash_tree_root = %hex_encode(header_oracle.header_validator.pre_merge_acc.tree_hash_root().0),
"Loaded pre-merge accumulator."
);
let header_oracle = Arc::new(RwLock::new(header_oracle));
let header_oracle = Arc::new(RwLock::new(HeaderOracle::default()));

// Initialize and spawn uTP socket
let (utp_talk_reqs_tx, utp_talk_reqs_rx) = mpsc::unbounded_channel();
Expand Down
24 changes: 13 additions & 11 deletions crates/subnetworks/history/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,23 @@ use ethportal_api::{
use ssz::Decode;
use tokio::sync::RwLock;
use trin_validation::{
header_validator::HeaderValidator,
oracle::HeaderOracle,
validator::{ValidationResult, Validator},
};

pub struct ChainHistoryValidator {
pub header_oracle: Arc<RwLock<HeaderOracle>>,
pub header_validator: HeaderValidator,
}

impl ChainHistoryValidator {
pub fn new(header_oracle: Arc<RwLock<HeaderOracle>>) -> Self {
Self { header_oracle }
let header_validator = HeaderValidator::new_with_header_oracle(header_oracle.clone());
Self {
header_oracle,
header_validator,
}
}
}

Expand All @@ -44,11 +50,9 @@ impl Validator<HistoryContentKey> for ChainHistoryValidator {
"Content validation failed: Invalid header hash. Found: {header_hash:?} - Expected: {:?}",
hex_encode(header_hash)
);
self.header_oracle
.read()
.await
.header_validator
.validate_header_with_proof(&header_with_proof)?;
self.header_validator
.validate_header_with_proof(&header_with_proof)
.await?;

Ok(ValidationResult::new(true))
}
Expand All @@ -63,11 +67,9 @@ impl Validator<HistoryContentKey> for ChainHistoryValidator {
"Content validation failed: Invalid header number. Found: {header_number} - Expected: {}",
key.block_number
);
self.header_oracle
.read()
.await
.header_validator
.validate_header_with_proof(&header_with_proof)?;
self.header_validator
.validate_header_with_proof(&header_with_proof)
.await?;

Ok(ValidationResult::new(true))
}
Expand Down
1 change: 1 addition & 0 deletions crates/validation/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ serde.workspace = true
serde_json.workspace = true
ssz_types.workspace = true
tokio.workspace = true
tracing.workspace = true
tree_hash.workspace = true
tree_hash_derive.workspace = true

Expand Down
8 changes: 5 additions & 3 deletions crates/validation/src/accumulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ use ssz_derive::{Decode, Encode};
use ssz_types::{typenum, VariableList};
use tree_hash_derive::TreeHash;

use crate::{constants::EPOCH_SIZE, merkle::proof::MerkleTree, TrinValidationAssets};
use crate::{
constants::SLOTS_PER_HISTORICAL_ROOT, merkle::proof::MerkleTree, TrinValidationAssets,
};

/// SSZ List[Hash256, max_length = MAX_HISTORICAL_EPOCHS]
/// List of historical epoch accumulator merkle roots preceding current epoch.
Expand Down Expand Up @@ -49,15 +51,15 @@ impl PreMergeAccumulator {
}

pub(crate) fn get_epoch_index_of_header(&self, header: &Header) -> u64 {
header.number / EPOCH_SIZE
header.number / SLOTS_PER_HISTORICAL_ROOT
}

pub fn construct_proof(
header: &Header,
epoch_acc: &EpochAccumulator,
) -> anyhow::Result<BlockProofHistoricalHashesAccumulator> {
// Validate header hash matches historical hash from epoch accumulator
let hr_index = (header.number % EPOCH_SIZE) as usize;
let hr_index = (header.number % SLOTS_PER_HISTORICAL_ROOT) as usize;
let header_record = epoch_acc[hr_index];
if header_record.block_hash != header.hash_slow() {
return Err(anyhow!(
Expand Down
8 changes: 5 additions & 3 deletions crates/validation/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use alloy::primitives::{b256, B256};

// Execution Layer hard forks https://ethereum.org/en/history/
pub const CAPELLA_FORK_EPOCH: u64 = 194_048;
pub const SLOTS_PER_EPOCH: u64 = 32;

/// The default hash of the pre-merge accumulator at the time of the merge block.
pub const DEFAULT_PRE_MERGE_ACC_HASH: &str =
"0x8eac399e24480dce3cfe06f4bdecba51c6e5d0c46200e3e8611a0b44a3a69ff9";
pub const DEFAULT_PRE_MERGE_ACC_HASH: B256 =
b256!("0x8eac399e24480dce3cfe06f4bdecba51c6e5d0c46200e3e8611a0b44a3a69ff9");

/// Max number of blocks / epoch = 2 ** 13
pub const EPOCH_SIZE: u64 = 8192;
pub const SLOTS_PER_HISTORICAL_ROOT: u64 = 8192;

// Max number of epochs = 2 ** 17
// const MAX_HISTORICAL_EPOCHS: usize = 131072;
Expand Down
Loading