Skip to content

Commit 743475f

Browse files
authored
feat: enable post-capella HistoricalSummary Header validation (#1774)
1 parent b80d992 commit 743475f

File tree

14 files changed

+286
-178
lines changed

14 files changed

+286
-178
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin/e2hs-writer/src/provider.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use reqwest::{
1414
};
1515
use tracing::info;
1616
use trin_execution::era::binary_search::EraBinarySearch;
17-
use trin_validation::constants::EPOCH_SIZE;
17+
use trin_validation::constants::SLOTS_PER_HISTORICAL_ROOT;
1818

1919
pub enum EraSource {
2020
// processed era1 file
@@ -91,8 +91,8 @@ pub struct EraProvider {
9191
impl EraProvider {
9292
pub async fn new(epoch: u64) -> anyhow::Result<Self> {
9393
info!("Fetching e2store files for epoch: {epoch}");
94-
let starting_block = epoch * EPOCH_SIZE;
95-
let ending_block = starting_block + EPOCH_SIZE;
94+
let starting_block = epoch * SLOTS_PER_HISTORICAL_ROOT;
95+
let ending_block = starting_block + SLOTS_PER_HISTORICAL_ROOT;
9696
let http_client = Client::builder()
9797
.default_headers(HeaderMap::from_iter([(
9898
CONTENT_TYPE,
@@ -108,7 +108,7 @@ impl EraProvider {
108108
while next_block < ending_block {
109109
let source = if !network_spec().is_paris_active_at_block(next_block) {
110110
let era1_paths = get_era1_files(&http_client).await?;
111-
let epoch_index = next_block / EPOCH_SIZE;
111+
let epoch_index = next_block / SLOTS_PER_HISTORICAL_ROOT;
112112
let era1_path = era1_paths.get(&epoch_index).ok_or(anyhow!(
113113
"Era1 file not found for epoch index: {epoch_index}",
114114
))?;
@@ -162,7 +162,8 @@ impl EraProvider {
162162
);
163163
Ok(match &self.sources[0] {
164164
EraSource::PreMerge(era1) => {
165-
let block_tuple = era1.block_tuples[(block_number % EPOCH_SIZE) as usize].clone();
165+
let block_tuple =
166+
era1.block_tuples[(block_number % SLOTS_PER_HISTORICAL_ROOT) as usize].clone();
166167
ensure!(
167168
block_tuple.header.header.number == block_number,
168169
"Block number mismatch",

bin/e2hs-writer/src/reader.rs

+5-7
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ use ethportal_api::{
2525
use portal_bridge::api::execution::ExecutionApi;
2626
use tokio::try_join;
2727
use trin_execution::era::beacon::decode_transactions;
28-
use trin_validation::{
29-
accumulator::PreMergeAccumulator, constants::EPOCH_SIZE, header_validator::HeaderValidator,
30-
};
28+
use trin_validation::{accumulator::PreMergeAccumulator, constants::SLOTS_PER_HISTORICAL_ROOT};
3129
use url::Url;
3230

3331
use crate::{
@@ -63,27 +61,27 @@ impl EpochReader {
6361
) -> anyhow::Result<Self> {
6462
let execution_api = ExecutionApi::new(el_provider_url.clone(), el_provider_url, 10).await?;
6563
let latest_block = execution_api.get_latest_block_number().await?;
66-
let maximum_epoch = latest_block / EPOCH_SIZE;
64+
let maximum_epoch = latest_block / SLOTS_PER_HISTORICAL_ROOT;
6765
ensure!(
6866
epoch_index <= maximum_epoch,
6967
"Epoch {epoch_index} is greater than the maximum epoch {maximum_epoch}"
7068
);
7169

72-
let starting_block = epoch_index * EPOCH_SIZE;
70+
let starting_block = epoch_index * SLOTS_PER_HISTORICAL_ROOT;
7371
let epoch_accumulator = if network_spec().is_paris_active_at_block(starting_block) {
7472
None
7573
} else {
7674
Some(Arc::new(
7775
lookup_epoch_acc(
7876
epoch_index,
79-
&HeaderValidator::new().pre_merge_acc,
77+
&PreMergeAccumulator::default(),
8078
&epoch_acc_path,
8179
)
8280
.await?,
8381
))
8482
};
8583

86-
let ending_block = starting_block + EPOCH_SIZE;
84+
let ending_block = starting_block + SLOTS_PER_HISTORICAL_ROOT;
8785
let is_paris_active = network_spec().is_paris_active_at_block(ending_block);
8886
let receipts_handle = tokio::spawn(async move {
8987
if is_paris_active {

bin/portal-bridge/src/bridge/e2hs.rs

+20-6
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@ use e2store::{
1414
utils::get_e2hs_files,
1515
};
1616
use ethportal_api::{
17+
consensus::historical_summaries::HistoricalSummaries,
1718
types::{
18-
execution::header_with_proof::HeaderWithProof, network::Subnetwork, portal_wire::OfferTrace,
19+
execution::header_with_proof::{BlockHeaderProof, HeaderWithProof},
20+
network::Subnetwork,
21+
portal_wire::OfferTrace,
1922
},
2023
BlockBody, ContentValue, HistoryContentKey, HistoryContentValue, OverlayContentKey,
2124
RawContentValue, Receipts,
@@ -95,7 +98,9 @@ impl E2HSBridge {
9598
Ok(Self {
9699
gossiper,
97100
block_semaphore,
98-
header_validator: HeaderValidator::new(),
101+
header_validator: HeaderValidator::new_with_historical_summaries(
102+
HistoricalSummaries::default(),
103+
),
99104
block_range,
100105
random_fill,
101106
e2hs_files,
@@ -171,7 +176,7 @@ impl E2HSBridge {
171176
continue;
172177
}
173178
}
174-
if let Err(err) = self.validate_block_tuple(&block_tuple) {
179+
if let Err(err) = self.validate_block_tuple(&block_tuple).await {
175180
error!("Failed to validate block tuple: {err:?}");
176181
continue;
177182
}
@@ -206,10 +211,19 @@ impl E2HSBridge {
206211
.unwrap_or_else(|err| panic!("unable to read e2hs file at path: {e2hs_path:?} : {err}"))
207212
}
208213

209-
fn validate_block_tuple(&self, block_tuple: &BlockTuple) -> anyhow::Result<()> {
214+
async fn validate_block_tuple(&self, block_tuple: &BlockTuple) -> anyhow::Result<()> {
210215
let header_with_proof = &block_tuple.header_with_proof.header_with_proof;
211-
self.header_validator
212-
.validate_header_with_proof(header_with_proof)?;
216+
// The E2HS bridge doesn't have access to a provider so it can't validate historical summary
217+
// Header with Proofs
218+
if !matches!(
219+
header_with_proof.proof,
220+
BlockHeaderProof::HistoricalSummariesCapella(_)
221+
| BlockHeaderProof::HistoricalSummariesDeneb(_)
222+
) {
223+
self.header_validator
224+
.validate_header_with_proof(header_with_proof)
225+
.await?;
226+
}
213227
let body = &block_tuple.body.body;
214228
body.validate_against_header(&header_with_proof.header)?;
215229
let receipts = &block_tuple.receipts.receipts;

bin/portal-bridge/src/types/range.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::collections::HashMap;
22

3-
use trin_validation::constants::EPOCH_SIZE;
3+
use trin_validation::constants::SLOTS_PER_HISTORICAL_ROOT;
44

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

1818
// Calculate start and end epochs
19-
let start_epoch = start_block / EPOCH_SIZE;
20-
let end_epoch = end_block / EPOCH_SIZE;
19+
let start_epoch = start_block / SLOTS_PER_HISTORICAL_ROOT;
20+
let end_epoch = end_block / SLOTS_PER_HISTORICAL_ROOT;
2121

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

2828
// Determine if the epoch is fully or partially covered
2929
if epoch_first_block >= start_block && epoch_last_block <= end_block {

bin/trin/src/run.rs

+1-8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use std::{net::SocketAddr, path::PathBuf, sync::Arc};
22

33
use ethportal_api::{
44
types::{distance::Distance, network::Subnetwork},
5-
utils::bytes::hex_encode,
65
version::get_trin_version,
76
};
87
use portalnet::{
@@ -14,7 +13,6 @@ use portalnet::{
1413
use rpc::{config::RpcConfig, launch_jsonrpc_server, RpcServerHandle};
1514
use tokio::sync::{mpsc, RwLock};
1615
use tracing::info;
17-
use tree_hash::TreeHash;
1816
use trin_beacon::initialize_beacon_network;
1917
use trin_history::initialize_history_network;
2018
use trin_state::initialize_state_network;
@@ -121,12 +119,7 @@ async fn run_trin_internal(
121119
}
122120

123121
// Initialize validation oracle
124-
let header_oracle = HeaderOracle::default();
125-
info!(
126-
hash_tree_root = %hex_encode(header_oracle.header_validator.pre_merge_acc.tree_hash_root().0),
127-
"Loaded pre-merge accumulator."
128-
);
129-
let header_oracle = Arc::new(RwLock::new(header_oracle));
122+
let header_oracle = Arc::new(RwLock::new(HeaderOracle::default()));
130123

131124
// Initialize and spawn uTP socket
132125
let (utp_talk_reqs_tx, utp_talk_reqs_rx) = mpsc::unbounded_channel();

crates/subnetworks/history/src/validation.rs

+13-11
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,23 @@ use ethportal_api::{
1212
use ssz::Decode;
1313
use tokio::sync::RwLock;
1414
use trin_validation::{
15+
header_validator::HeaderValidator,
1516
oracle::HeaderOracle,
1617
validator::{ValidationResult, Validator},
1718
};
1819

1920
pub struct ChainHistoryValidator {
2021
pub header_oracle: Arc<RwLock<HeaderOracle>>,
22+
pub header_validator: HeaderValidator,
2123
}
2224

2325
impl ChainHistoryValidator {
2426
pub fn new(header_oracle: Arc<RwLock<HeaderOracle>>) -> Self {
25-
Self { header_oracle }
27+
let header_validator = HeaderValidator::new_with_header_oracle(header_oracle.clone());
28+
Self {
29+
header_oracle,
30+
header_validator,
31+
}
2632
}
2733
}
2834

@@ -44,11 +50,9 @@ impl Validator<HistoryContentKey> for ChainHistoryValidator {
4450
"Content validation failed: Invalid header hash. Found: {header_hash:?} - Expected: {:?}",
4551
hex_encode(header_hash)
4652
);
47-
self.header_oracle
48-
.read()
49-
.await
50-
.header_validator
51-
.validate_header_with_proof(&header_with_proof)?;
53+
self.header_validator
54+
.validate_header_with_proof(&header_with_proof)
55+
.await?;
5256

5357
Ok(ValidationResult::new(true))
5458
}
@@ -63,11 +67,9 @@ impl Validator<HistoryContentKey> for ChainHistoryValidator {
6367
"Content validation failed: Invalid header number. Found: {header_number} - Expected: {}",
6468
key.block_number
6569
);
66-
self.header_oracle
67-
.read()
68-
.await
69-
.header_validator
70-
.validate_header_with_proof(&header_with_proof)?;
70+
self.header_validator
71+
.validate_header_with_proof(&header_with_proof)
72+
.await?;
7173

7274
Ok(ValidationResult::new(true))
7375
}

crates/validation/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ serde.workspace = true
2626
serde_json.workspace = true
2727
ssz_types.workspace = true
2828
tokio.workspace = true
29+
tracing.workspace = true
2930
tree_hash.workspace = true
3031
tree_hash_derive.workspace = true
3132

crates/validation/src/accumulator.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ use ssz_derive::{Decode, Encode};
1414
use ssz_types::{typenum, VariableList};
1515
use tree_hash_derive::TreeHash;
1616

17-
use crate::{constants::EPOCH_SIZE, merkle::proof::MerkleTree, TrinValidationAssets};
17+
use crate::{
18+
constants::SLOTS_PER_HISTORICAL_ROOT, merkle::proof::MerkleTree, TrinValidationAssets,
19+
};
1820

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

5153
pub(crate) fn get_epoch_index_of_header(&self, header: &Header) -> u64 {
52-
header.number / EPOCH_SIZE
54+
header.number / SLOTS_PER_HISTORICAL_ROOT
5355
}
5456

5557
pub fn construct_proof(
5658
header: &Header,
5759
epoch_acc: &EpochAccumulator,
5860
) -> anyhow::Result<BlockProofHistoricalHashesAccumulator> {
5961
// Validate header hash matches historical hash from epoch accumulator
60-
let hr_index = (header.number % EPOCH_SIZE) as usize;
62+
let hr_index = (header.number % SLOTS_PER_HISTORICAL_ROOT) as usize;
6163
let header_record = epoch_acc[hr_index];
6264
if header_record.block_hash != header.hash_slow() {
6365
return Err(anyhow!(

crates/validation/src/constants.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1+
use alloy::primitives::{b256, B256};
2+
13
// Execution Layer hard forks https://ethereum.org/en/history/
24
pub const CAPELLA_FORK_EPOCH: u64 = 194_048;
35
pub const SLOTS_PER_EPOCH: u64 = 32;
46

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

911
/// Max number of blocks / epoch = 2 ** 13
10-
pub const EPOCH_SIZE: u64 = 8192;
12+
pub const SLOTS_PER_HISTORICAL_ROOT: u64 = 8192;
1113

1214
// Max number of epochs = 2 ** 17
1315
// const MAX_HISTORICAL_EPOCHS: usize = 131072;

0 commit comments

Comments
 (0)