Skip to content

Commit 5bc7e54

Browse files
authored
feat: add Deneb header validation (#1786)
1 parent ef55259 commit 5bc7e54

File tree

9 files changed

+601
-349
lines changed

9 files changed

+601
-349
lines changed

Cargo.lock

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

crates/subnetworks/history/src/validation.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ mod tests {
168168
}
169169

170170
#[test_log::test(tokio::test)]
171-
#[should_panic(expected = "Merkle proof validation failed for pre-merge header")]
171+
#[should_panic(expected = "Execution block proof verification failed for pre-Merge header")]
172172
async fn invalidate_header_by_hash_with_invalid_number() {
173173
let header_with_proof_ssz = get_header_with_proof_ssz();
174174
let mut header =
@@ -188,7 +188,7 @@ mod tests {
188188
}
189189

190190
#[test_log::test(tokio::test)]
191-
#[should_panic(expected = "Merkle proof validation failed for pre-merge header")]
191+
#[should_panic(expected = "Execution block proof verification failed for pre-Merge header")]
192192
async fn invalidate_header_by_hash_with_invalid_gaslimit() {
193193
let header_with_proof_ssz = get_header_with_proof_ssz();
194194
let mut header =
@@ -224,7 +224,7 @@ mod tests {
224224
}
225225

226226
#[test_log::test(tokio::test)]
227-
#[should_panic(expected = "Merkle proof validation failed for pre-merge header")]
227+
#[should_panic(expected = "Execution block proof verification failed for pre-Merge header")]
228228
async fn invalidate_header_by_number_with_invalid_number() {
229229
let header_with_proof_ssz = get_header_with_proof_ssz();
230230
let mut header =
@@ -244,7 +244,7 @@ mod tests {
244244
}
245245

246246
#[test_log::test(tokio::test)]
247-
#[should_panic(expected = "Merkle proof validation failed for pre-merge header")]
247+
#[should_panic(expected = "Execution block proof verification failed for pre-Merge header")]
248248
async fn invalidate_header_by_number_with_invalid_gaslimit() {
249249
let header_with_proof_ssz: Vec<u8> = get_header_with_proof_ssz();
250250
let mut header =

crates/utils/Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,13 @@ version.workspace = true
1313

1414
[dependencies]
1515
alloy.workspace = true
16+
anyhow.workspace = true
17+
ethereum_ssz.workspace = true
1618
ethportal-api.workspace = true
1719
directories.workspace = true
20+
serde.workspace = true
21+
serde_json.workspace = true
22+
serde_yaml.workspace = true
1823
tempfile.workspace = true
1924
tracing.workspace = true
2025
tracing-subscriber = { workspace = true, features = ["env-filter"] }

crates/utils/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ pub mod cli;
55
pub mod dir;
66
pub mod log;
77
pub mod submodules;
8+
pub mod testing;

crates/utils/src/submodules.rs

+36-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
use std::{
2-
fs, io,
2+
fs::{self, File},
3+
io::{self, BufReader},
34
path::{Path, PathBuf},
45
};
56

7+
use anyhow::anyhow;
8+
use serde::Deserialize;
9+
use ssz::Decode;
10+
611
pub const PORTAL_SPEC_TESTS_SUBMODULE_PATH: [&str; 2] =
712
["../../portal-spec-tests", "../../../portal-spec-tests"];
813

@@ -25,6 +30,35 @@ pub fn read_portal_spec_tests_file<P: AsRef<Path>>(path: P) -> io::Result<String
2530
}
2631

2732
/// Reads binary file from a "portal-spec-tests" submodule
28-
pub fn read_portal_spec_tests_file_as_bytes<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
33+
pub fn read_binary_portal_spec_tests_file<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
2934
fs::read(portal_spec_tests_file_path(path))
3035
}
36+
37+
/// Reads json file from a "portal-spec-tests" submodule
38+
pub fn read_json_portal_spec_tests_file<T>(path: impl AsRef<Path>) -> anyhow::Result<T>
39+
where
40+
T: for<'de> Deserialize<'de>,
41+
{
42+
let reader = BufReader::new(File::open(portal_spec_tests_file_path(path))?);
43+
Ok(serde_json::from_reader(reader)?)
44+
}
45+
46+
/// Reads yaml file from a "portal-spec-tests" submodule
47+
pub fn read_yaml_portal_spec_tests_file<T>(path: impl AsRef<Path>) -> anyhow::Result<T>
48+
where
49+
T: for<'de> Deserialize<'de>,
50+
{
51+
let reader = BufReader::new(File::open(portal_spec_tests_file_path(path))?);
52+
Ok(serde_yaml::from_reader(reader)?)
53+
}
54+
55+
/// Reads ssz file from a "portal-spec-tests" submodule
56+
pub fn read_ssz_portal_spec_tests_file<T: Decode>(path: impl AsRef<Path>) -> anyhow::Result<T> {
57+
let bytes = read_binary_portal_spec_tests_file(&path)?;
58+
T::from_ssz_bytes(&bytes).map_err(|err| {
59+
anyhow!(
60+
"Error decoding ssz file: {}. Error: {err:?}",
61+
path.as_ref().display()
62+
)
63+
})
64+
}

crates/utils/src/testing.rs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use ethportal_api::{ContentValue, ContentValueError, OverlayContentKey, RawContentValue};
2+
use serde::{Deserialize, Serialize};
3+
4+
/// The common test vectors type.
5+
#[derive(Debug, Clone, Serialize, Deserialize)]
6+
pub struct ContentItem<K: OverlayContentKey> {
7+
pub content_key: K,
8+
#[serde(rename = "content_value")]
9+
pub raw_content_value: RawContentValue,
10+
}
11+
12+
impl<K: OverlayContentKey> ContentItem<K> {
13+
pub fn content_value<V: ContentValue<TContentKey = K>>(&self) -> Result<V, ContentValueError> {
14+
V::decode(&self.content_key, &self.raw_content_value)
15+
}
16+
}

crates/validation/src/accumulator.rs

+77
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,80 @@ impl PreMergeAccumulator {
120120
)
121121
}
122122
}
123+
124+
#[cfg(test)]
125+
mod tests {
126+
use std::fs;
127+
128+
use alloy::primitives::map::HashMap;
129+
use ethportal_api::{
130+
types::execution::header_with_proof::{BlockHeaderProof, HeaderWithProof},
131+
HistoryContentKey, HistoryContentValue,
132+
};
133+
use rstest::rstest;
134+
use trin_utils::{
135+
submodules::{
136+
read_json_portal_spec_tests_file, read_ssz_portal_spec_tests_file,
137+
read_yaml_portal_spec_tests_file,
138+
},
139+
testing::ContentItem,
140+
};
141+
142+
use super::*;
143+
144+
#[rstest]
145+
fn construct_proof(
146+
#[values(
147+
1_000_001, 1_000_002, 1_000_003, 1_000_004, 1_000_005, 1_000_006, 1_000_007, 1_000_008,
148+
1_000_009, 1_000_010
149+
)]
150+
block_number: u64,
151+
) {
152+
let all_test_data: HashMap<u64, ContentItem<HistoryContentKey>> =
153+
read_json_portal_spec_tests_file(
154+
"tests/mainnet/history/headers_with_proof/1000001-1000010.json",
155+
)
156+
.unwrap();
157+
let test_data = all_test_data[&block_number].clone();
158+
159+
let epoch_accumulator = read_ssz_portal_spec_tests_file(
160+
"tests/mainnet/history/accumulator/epoch-record-00122.ssz",
161+
)
162+
.unwrap();
163+
164+
test_construct_proof(test_data, epoch_accumulator);
165+
}
166+
167+
#[rstest]
168+
fn construct_proof_from_partial_epoch(#[values(15_537_392, 15_537_393)] block_number: u64) {
169+
let test_data: ContentItem<HistoryContentKey> = read_yaml_portal_spec_tests_file(format!(
170+
"tests/mainnet/history/headers_with_proof/{block_number}.yaml"
171+
))
172+
.unwrap();
173+
174+
let epoch_accumulator_bytes = fs::read("./src/assets/epoch_accs/0xe6ebe562c89bc8ecb94dc9b2889a27a816ec05d3d6bd1625acad72227071e721.bin").unwrap();
175+
let epoch_accumulator = EpochAccumulator::from_ssz_bytes(&epoch_accumulator_bytes).unwrap();
176+
assert_eq!(epoch_accumulator.len(), 5362);
177+
178+
test_construct_proof(test_data, epoch_accumulator);
179+
}
180+
181+
fn test_construct_proof(
182+
content_item: ContentItem<HistoryContentKey>,
183+
epoch_accumulator: EpochAccumulator,
184+
) {
185+
let HistoryContentValue::BlockHeaderWithProof(HeaderWithProof { header, proof }) =
186+
content_item.content_value().unwrap()
187+
else {
188+
panic!("Expected BlockHeaderWithProof content value");
189+
};
190+
191+
let BlockHeaderProof::HistoricalHashes(expected_proof) = proof else {
192+
panic!("Expected HistoricalHashes proof")
193+
};
194+
195+
let generated_proof =
196+
PreMergeAccumulator::construct_proof(&header, &epoch_accumulator).unwrap();
197+
assert_eq!(generated_proof, expected_proof);
198+
}
199+
}

0 commit comments

Comments
 (0)