diff --git a/crates/forrestrie-examples/Cargo.toml b/crates/forrestrie-examples/Cargo.toml deleted file mode 100644 index faf71d80..00000000 --- a/crates/forrestrie-examples/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "forrestrie-examples" -version = "0.1.1" -edition = "2021" - -[dev-dependencies] -beacon-protos.workspace = true -bls.workspace = true -ethportal-api.workspace = true -firehose-client.workspace = true -firehose-protos = { path = "../firehose-protos" } -forrestrie = { path = "../forrestrie" } -futures.workspace = true -insta.workspace = true -merkle_proof.workspace = true -primitive-types.workspace = true -prost.workspace = true -prost-wkt.workspace = true -prost-wkt-types.workspace = true -reqwest = { workspace = true, features = ["json"] } -reth-primitives.workspace = true -reth-trie-common.workspace = true -serde = { workspace = true, features = ["derive"] } -snap = "1.1.1" -ssz_types = "0.8.0" -ethereum_ssz = "0.7.1" -ethereum_ssz_derive = "0.7.1" -serde_json.workspace = true -tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } -trin-validation = { git = "https://github.com/ethereum/trin.git", version = "0.1.0", tag = "v0.1.0-alpha.51" } -tonic.workspace = true -tracing.workspace = true -tracing-subscriber = "0.3" -tree_hash = "0.6.0" -types.workspace = true diff --git a/crates/forrestrie-examples/README.md b/crates/forrestrie-examples/README.md deleted file mode 100644 index 2063021f..00000000 --- a/crates/forrestrie-examples/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Forrestrie Examples - -Here's an example of how to run one of the examples in the `forrestrie-examples` crate: - -```terminal -cd crates/forrestrie-examples && cargo run -- --examples historical_state_roots_proof -``` - -Use environment variables to provide Firehose Ethereum and Firehose -Beacon providers of your choice. - -To do this, place a `.env` file in the root of `veemon`. See the -`.env.example` file in the root of this repository for what you'll need, -depending on your requirements. diff --git a/crates/forrestrie-examples/assets/historical_batch-573-c847a969.ssz b/crates/forrestrie-examples/assets/historical_batch-573-c847a969.ssz deleted file mode 100644 index 45631b2d..00000000 Binary files a/crates/forrestrie-examples/assets/historical_batch-573-c847a969.ssz and /dev/null differ diff --git a/crates/forrestrie-examples/examples/block_roots_only_proof.rs b/crates/forrestrie-examples/examples/block_roots_only_proof.rs deleted file mode 100644 index 48af6f3c..00000000 --- a/crates/forrestrie-examples/examples/block_roots_only_proof.rs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Inclusion Proofs for Block Roots Only -//! -//! For this test, we want to prove that a block_root is included in the `block_summary_root` -//! field of a [`HistoricalSummary`] from the [`BeaconState`] historical_summaries List. -//! A [`HistoricalSummary`] contains the roots of two Merkle trees, `block_summary_root` and -//! `state_summary_root`. -//! We are interested in the `block_summary_root` tree, whose leaves consists of the -//! [`BeaconBlockHeader`] roots for one era (8192 consecutive slots). -//! For this test, we are using the state at the first [`Slot`] of an era to build the proof. -//! We chose this [`Slot`] because it is the first [`Slot`] of an era, and all of the -//! [`BeaconBlockHeader`] roots needed to construct the [`HistoricalSummary`] for this era are -//! available in `state.block_roots`. - -use forrestrie::beacon_state::{HeadState, CAPELLA_START_ERA, HISTORY_TREE_DEPTH}; -use merkle_proof::verify_merkle_proof; -use types::{historical_summary::HistoricalSummary, MainnetEthSpec}; - -#[tokio::main] -async fn main() { - // You may need to update the slot being queried as the state data is updated. - // Multiply a recent era by 8192 to get the slot number. - const SLOT: u64 = 10182656; - let url = format!("https://www.lightclientdata.org/eth/v2/debug/beacon/states/{SLOT}"); - println!("Requesting state for slot {SLOT} ... (this can take a while!)"); - let response = reqwest::get(url).await.unwrap(); - let transition_state: HeadState = if response.status().is_success() { - let json_response: serde_json::Value = response.json().await.unwrap(); - serde_json::from_value(json_response).unwrap() - } else { - panic!("Request failed with status: {}", response.status()); - }; - - // There are 8192 slots in an era. 8790016 / 8192 = 1073. - let proof_era = transition_state.data().slot().as_usize() / 8192usize; - - // In this test we are using the `historical_summaries` (introduced in Capella) for - // verification, so we need to subtract the Capella start era to get the correct index. - let proof_era_index = proof_era - CAPELLA_START_ERA - 1; - - // We are going to prove that the block_root at index 4096 is included in the block_roots - // tree. - // This is an arbitrary choice just for test purposes. - let index = 4096usize; - - // Buffer of most recent 8192 block roots: - let block_root_at_index = *transition_state.data().block_roots().get(index).unwrap(); - - let proof = transition_state - .compute_block_roots_proof_only(index) - .unwrap(); - - // To verify the proof, we use the state from a later slot. - // The HistoricalSummary used to generate this proof is included in the historical_summaries - // list of this state. - let url = "https://www.lightclientdata.org/eth/v2/debug/beacon/states/head".to_string(); - println!("Requesting head state ... (this can take a while!)"); - let response = reqwest::get(url).await.unwrap(); - let state: HeadState = if response.status().is_success() { - let json_response: serde_json::Value = response.json().await.unwrap(); - serde_json::from_value(json_response).unwrap() - } else { - panic!("Request failed with status: {}", response.status()); - }; - - // The verifier retrieves the block_summary_root for the historical_summary and verifies the - // proof against it. - let historical_summary: &HistoricalSummary = state - .data() - .historical_summaries() - .unwrap() - .get(proof_era_index) - .unwrap(); - - let block_roots_summary_root = historical_summary.block_summary_root(); - - assert!( - verify_merkle_proof( - block_root_at_index, - &proof, - HISTORY_TREE_DEPTH, - index, - block_roots_summary_root - ), - "Merkle proof verification failed" - ); - - println!("Block roots only merkle proof verification succeeded"); -} diff --git a/crates/forrestrie-examples/examples/block_roots_proofs.rs b/crates/forrestrie-examples/examples/block_roots_proofs.rs deleted file mode 100644 index 16bde5fa..00000000 --- a/crates/forrestrie-examples/examples/block_roots_proofs.rs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Block Roots Proofs -//! -//! We want to prove that a block_root is included in a [`HistoricalSummary`] from the [`BeaconState`] historical_summaries List. -//! A [`HistoricalSummary`] contains the roots of two Merkle trees, block_summary_root and state_summary root. -//! We are interested in the block_summary tree, whose leaves consists of the [`BeaconBlockHeader`] roots for one era (8192 consecutive slots). -//! For example, we could have used the state at [`Slot`] 8790016, which is the first [`Slot`] of era 1073, to build the proof. -//! Because it is the first [`Slot`] of an era, all of the [`BeaconBlockHeader`] roots needed to construct the -//! [`HistoricalSummary`] for this era are available in state.block_roots. -//! The last block root in the `block_roots` buffer is the block root of the previous block. - -use forrestrie::beacon_state::{ - HeadState, CAPELLA_START_ERA, HISTORICAL_SUMMARY_TREE_DEPTH, SLOTS_PER_HISTORICAL_ROOT, -}; -use merkle_proof::verify_merkle_proof; -use tree_hash::TreeHash; -use types::MainnetEthSpec; -#[tokio::main] -async fn main() { - // You may need to update the slot being queried as the state data is updated. - // Multiply a recent era by 8192 to get the slot number. - const SLOT: u64 = 10182656; - let url = format!("https://www.lightclientdata.org/eth/v2/debug/beacon/states/{SLOT}"); - println!("Requesting state for slot {SLOT} ... (this can take a while!)"); - let response = reqwest::get(url).await.unwrap(); - let transition_state: HeadState = if response.status().is_success() { - let json_response: serde_json::Value = response.json().await.unwrap(); - serde_json::from_value(json_response).unwrap() - } else { - panic!("Request failed with status: {}", response.status()); - }; - - // There are 8192 slots in an era. - let proof_era = transition_state.data().slot().as_usize() / SLOTS_PER_HISTORICAL_ROOT; - - // In this test we are using the historical_summaries (introduced in Capella) for verification, - // so we need to subtract the Capella start era to get the correct index. - let proof_era_index = proof_era - CAPELLA_START_ERA - 1; - - // We are going to prove that the block_root at index 4096 is included in the block_roots tree. - // This is an arbitrary choice just for test purposes. - let index = 4096usize; - - // Buffer of most recent 8192 block roots: - let block_root_at_index = *transition_state.data().block_roots().get(index).unwrap(); - - assert!( - transition_state.block_roots_contain_entire_era().unwrap(), - "Block roots buffer does not contain an entire era" - ); - - let proof = transition_state.compute_block_roots_proof(index).unwrap(); - - // To verify the proof, we use the state from a later slot. - // The HistoricalSummary used to generate this proof is included in the historical_summaries list of this state. - let url = "https://www.lightclientdata.org/eth/v2/debug/beacon/states/head".to_string(); - println!("Requesting head state ... (this can take a while!)"); - let response = reqwest::get(url).await.unwrap(); - let state: HeadState = if response.status().is_success() { - let json_response: serde_json::Value = response.json().await.unwrap(); - serde_json::from_value(json_response).unwrap() - } else { - panic!("Request failed with status: {}", response.status()); - }; - - // The verifier retrieves the block_summary_root for the historical_summary and verifies the proof against it. - let historical_summary = state - .data() - .historical_summaries() - .unwrap() - .get(proof_era_index) - .unwrap(); - - let historical_summary_root = historical_summary.tree_hash_root(); - - assert!( - verify_merkle_proof( - block_root_at_index, - &proof, - HISTORICAL_SUMMARY_TREE_DEPTH, - index, - historical_summary_root - ), - "Merkle proof verification failed" - ); - - println!("Block roots proof verified successfully"); -} diff --git a/crates/forrestrie-examples/examples/empty_slot_hashes.rs b/crates/forrestrie-examples/examples/empty_slot_hashes.rs deleted file mode 100644 index 3a4cb2f1..00000000 --- a/crates/forrestrie-examples/examples/empty_slot_hashes.rs +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Empty Slot Block Hashes -//! -//! This example demonstrates that empty Beacon slots - slots with no execution block - -//! are represented in the [`BeaconState`] as duplicates of the previous full Beacon slot block hash. - -use std::collections::{BTreeMap, HashSet}; - -use forrestrie::beacon_state::{HeadState, CAPELLA_START_ERA, SLOTS_PER_HISTORICAL_ROOT}; -use primitive_types::H256; -use types::MainnetEthSpec; - -#[tokio::main] -async fn main() { - // This slot was chosen because it is the first slot of an era (and an epoch), - // which we demonstrate by showing that the slot number (see below) modulo 8192 is 0. - // You may need to update the slot being queried as the state data is updated. - // Multiply a recent era by 8192 to get the slot number. - const SLOT: u64 = 10182656; - let url = format!("https://www.lightclientdata.org/eth/v2/debug/beacon/states/{SLOT}"); - println!("Requesting state for slot {SLOT} ... (this can take a while!)"); - let response = reqwest::get(url).await.unwrap(); - let state: HeadState = if response.status().is_success() { - let json_response: serde_json::Value = response.json().await.unwrap(); - serde_json::from_value(json_response).unwrap() - } else { - panic!("Request failed with status: {}", response.status()); - }; - - let slot = state.data().slot().as_usize(); - - // Every 8192 slots, the era increments by 1, and (after Capella) the historical summaries buffer is updated. - let current_era = slot / SLOTS_PER_HISTORICAL_ROOT; - assert_eq!(slot % SLOTS_PER_HISTORICAL_ROOT, 0); - - // The historical summaries buffer is updated every 8192 slots, from the start of the Capella era. - let num_historical_summaries = state.data().historical_summaries().unwrap().len(); - assert_eq!((current_era - num_historical_summaries), CAPELLA_START_ERA); - - let block_roots = state.data().block_roots().to_vec(); - - // Block roots buffer contains duplicates. - let block_roots_set: HashSet<&H256, std::hash::RandomState> = - HashSet::from_iter(block_roots.iter()); - assert_ne!(block_roots_set.len(), block_roots.len()); - - let duplicate_block_roots_lookup_table = state - .data() - .block_roots() - .to_vec() - .iter() - .enumerate() - // Using BTreeMaps for deterministic order. - .fold(BTreeMap::>::new(), |mut acc, (i, root)| { - acc.entry(*root).or_default().push(i); - acc - }) - // Remove non-duplicate block roots. - .into_iter() - .filter(|(_, indices)| indices.len() > 1) - .collect::>>(); - - // The block roots buffer contains duplicates that are consecutive. - insta::assert_debug_snapshot!(duplicate_block_roots_lookup_table, @r###" - { - 0x0b181ac43241327561b0c9cb4e070f72989581fb49ed26f5435bef997c42ebf5: [ - 991, - 992, - ], - 0x0e0639696be97e597e0c7ee1acfff59f165ba9f5945e729633cff21c0c635848: [ - 6467, - 6468, - ], - 0x0e26c5f8321a19c33e0e190ad7f72d0c135ab8ba7104cd8a0473242c7064db18: [ - 6688, - 6689, - ], - 0x0f40bc4698ba17aa95faade9e069e95456bcffd3e966c1fb0510df803038df48: [ - 3396, - 3397, - ], - 0x0f57d09bbf7b6a20a76ce3683e555ca86e7a1c38a3d1414bc6afb76894c460c1: [ - 5265, - 5266, - ], - 0x1473d4d768a680e9e61e7c6904df1a798545e14295b664bd7be06951140e4650: [ - 7162, - 7163, - ], - 0x1e30ab8ebd808669cca453279c2d89ed1965c3920f33e2715aca2d3e2756722a: [ - 1162, - 1163, - ], - 0x287c4b53d1b7be5c6c1e42ee596cb6b2803dcdaf17821798f21175b1a7ded5a8: [ - 7543, - 7544, - ], - 0x2cf6c52b3a63f73d8393734ce77540b0ae4914f403128ef2e0b9dcabb36dd443: [ - 5087, - 5088, - ], - 0x2d8144f651ad2c0864d586f43c446570177ae0dc219f15ff9469dfd05fc8de6e: [ - 6465, - 6466, - ], - 0x3514b0a08790ff1047150893234575c86705b9b98ca0a0a109a39da2216a3a4f: [ - 2432, - 2433, - ], - 0x3e12555313ed8ad02e60bdf78688892a54e6e02498fffd5a2ed0dbfc38d97db5: [ - 2532, - 2533, - ], - 0x41eb012e02e62f6e31bf742c709a3e9ec654b9258ff86b2061d124f0839a0188: [ - 1799, - 1800, - ], - 0x4294e233c2ca1055127d1373ffaf96f91386a187f888c9de4742ea79ff2e67f0: [ - 3958, - 3959, - ], - 0x498bb1ca0923c4a56e094e2f6fe2243dff4a9766f317309da7c57be5940f5a56: [ - 124, - 125, - ], - 0x4ca5d89aaa6de795d3432fda64bbecf4aed5fa0966193e77aa1c98599fb08ebe: [ - 7807, - 7808, - ], - 0x4f497aaff8a60ec338bc3fd19e0089d3cfa922bd093f767e6ba34ce0ec0e69e9: [ - 6175, - 6176, - ], - 0x515519a00556388934dd24fd9c528b26af2dce885c4cd3d5b0120b3939808ddc: [ - 4410, - 4411, - ], - 0x56cf582ed2d994dc15c4e4e49cea4e013e5ccb825997958255ebff9a9c70a126: [ - 4127, - 4128, - ], - 0x59ef61abc9d0dee4a8c19d3f636876bc38aa56559bf29315b26ccfd66da73aa9: [ - 1510, - 1511, - ], - 0x5db5cee0a5a63e6f20744bd2da116f0b7ff1346b6b6014cf847977bd6036b17e: [ - 5297, - 5298, - ], - 0x5fe37ef18fdaee618fb869760b20be5f7d04334e93f356b00e313f3f2d4b5eb6: [ - 3743, - 3744, - ], - 0x6808158ef68b250694ebc6cfbd90418a0182ac3d6e0560ad19212dc902a31c2f: [ - 1937, - 1938, - ], - 0x6820e4ea1e925a0198c67e40d4a066778898cd8b2e6fea4c32d7dccec7c548d6: [ - 7294, - 7295, - ], - 0x69dfd5cbd798a693d3deb17ae9bb6c0b075e50d0e414b710f58438dc2a54b51d: [ - 3540, - 3541, - ], - 0x6f0b738c363cc6739178e2c813dc47d7ff9aaef5cda7b838a964ff67aa626ab3: [ - 1667, - 1668, - ], - 0x6fec0abed7cbf72b3c0c0fb00db7fa7e78fdf55c7bc52804971e9997e8c37ef6: [ - 5439, - 5440, - ], - 0x71afc6470dd6ea94a1cfa95d91975d2b2d0efcf261bcf92a37eeb722a10907e5: [ - 1518, - 1519, - ], - 0x99254b3ae83a064a9dd91f68c60630e88727bd2989110a041fed6aacb0780239: [ - 3555, - 3556, - ], - 0x9c91fed096d21a383bf4cba7ee5213c68f5fb662225af74864450d45bdd93e01: [ - 6028, - 6029, - ], - 0xa89ca327b5d989c3066ea390053651a5f8b951829bda21257f7b33503e1c7abc: [ - 6240, - 6241, - ], - 0xaba1fa146c1665c2a3083987e55a9ae15dc04800d527ca98f2baf8692b96d5fd: [ - 7167, - 7168, - ], - 0xb077d4b158fa43c1ac54ee3d608d9430970d10cbc64a219b819fc279f7d3d3e0: [ - 3380, - 3381, - ], - 0xc4153799a620d470ced2bf02f0275f6353ec57be64f34bb06a0bc3a13423a9e3: [ - 5453, - 5454, - ], - 0xcebd2b3111fce7d8f9f1ddcf556d7ba54aa0999342184e7c58fa262131e94283: [ - 2894, - 2895, - ], - 0xd436e0dbe68b089f4dca99cac9ab4dc044b448c3569ff029c230d1539c643b93: [ - 1036, - 1037, - ], - 0xd7e5a02180a5116042684af1b180739609e2424bbb4deb0d030b023b23490438: [ - 2050, - 2051, - ], - 0xda13e985195e44855e08d5bd2d54ca6ac8f4cfaa5668526760d521aeaa9c4178: [ - 7478, - 7479, - ], - 0xde5d5a3b2f2da2b6482adcd4f61c6addbf45dfee24ff938931ac90e56c9e73a9: [ - 6430, - 6431, - ], - 0xdef43bbd5c642857fdbb5fdcf8e566c1e1dffbb543c3a29e8d606c25e60d2bf3: [ - 5491, - 5492, - ], - 0xf406504fad9ec2165294c51a47bf6d258c07f7db212b897ebe5611153fbfcb88: [ - 3839, - 3840, - ], - 0xfe5b350eb4ae790d3c14db582269d3edea28f803a76983ababbf31926a7c9ff3: [ - 6784, - 6785, - ], - } - "###); - - println!("Empty slot block hashes example completed successfully"); -} diff --git a/crates/forrestrie-examples/examples/fetch_and_verify_block.rs b/crates/forrestrie-examples/examples/fetch_and_verify_block.rs deleted file mode 100644 index 59067700..00000000 --- a/crates/forrestrie-examples/examples/fetch_and_verify_block.rs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Beacon Block and Header Root Consistency -//! -//! In Ethereum's Beacon chain, the beacon block root and the block header root should match -//! for any given block, ensuring data integrity. This can be verified by computing the root -//! of a block and comparing it to the root of its header. -//! -//! For example, for slot 8786333, the block's root can be computed using the `canonical_root` method -//! from [`TreeHash`]: -//! -//! ```rust -//! let block_root = block.canonical_root(); -//! ``` -//! -//! Similarly, the block header root is derived as follows: -//! -//! ```rust -//! let block_header_root = block.block_header().tree_hash_root(); -//! ``` -//! -//! Both of these root hashes should be identical, indicating that the block's root hash -//! correctly represents the block header: -//! -//! ```rust -//! assert_eq!(block_root, block_header_root); -//! ``` -//! -use beacon_protos::Block as FirehoseBeaconBlock; -use firehose_client::{Chain, FirehoseClient}; -use tree_hash::TreeHash; -use types::{BeaconBlock, MainnetEthSpec}; - -#[tokio::main] -async fn main() { - let mut beacon_client = FirehoseClient::new(Chain::Beacon); - let response = beacon_client.fetch_block(8786333).await.unwrap().unwrap(); - let beacon_block = FirehoseBeaconBlock::try_from(response.into_inner()).unwrap(); - - let lighthouse_beacon_block = BeaconBlock::::try_from(beacon_block).unwrap(); - - insta::assert_debug_snapshot!(lighthouse_beacon_block.slot(), @ - "Slot(8786333)"); - - let block_root = lighthouse_beacon_block.canonical_root(); - - // See, for example, https://beaconcha.in/slot/8786333 and https://beaconscan.com/slot/8786333 - insta::assert_debug_snapshot!(block_root, @"0x063d4cf1a4f85d228d9eae17a9ab7df8b13de51e7a1988342a901575cce79613"); - - let block_header = lighthouse_beacon_block.block_header(); - let block_header_root = block_header.tree_hash_root(); - - assert_eq!(block_root, block_header_root); - - // This is to show that block hash and block body hash are different. - let body = lighthouse_beacon_block.body_deneb().unwrap(); - let body_hash = body.tree_hash_root(); - insta::assert_debug_snapshot!(body_hash, @"0xc15e821344ce5b201e2938248921743da8a07782168456929c8cef9f25a4cb02"); - - println!("fetch_and_verify_block.rs done"); -} diff --git a/crates/forrestrie-examples/examples/historical_state_roots_proof.rs b/crates/forrestrie-examples/examples/historical_state_roots_proof.rs deleted file mode 100644 index b151f727..00000000 --- a/crates/forrestrie-examples/examples/historical_state_roots_proof.rs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Historical and state roots proof -//! -//! This example demonstrates how to prove the inclusion of historical state roots in the beacon state. -use forrestrie::beacon_state::{HeadState, HISTORICAL_ROOTS_FIELD_INDEX, HISTORICAL_ROOTS_INDEX}; -use merkle_proof::verify_merkle_proof; -use types::{light_client_update::CURRENT_SYNC_COMMITTEE_PROOF_LEN, MainnetEthSpec}; - -#[tokio::main] -async fn main() { - let url = "https://www.lightclientdata.org/eth/v2/debug/beacon/states/head"; - println!("Requesting head state ... (this can take a while!)"); - let response = reqwest::get(url).await.unwrap(); - let mut state: HeadState = if response.status().is_success() { - let json_response: serde_json::Value = response.json().await.unwrap(); - serde_json::from_value(json_response).unwrap() - } else { - panic!("Request failed with status: {}", response.status()); - }; - - let proof = state - .compute_merkle_proof_for_historical_data(HISTORICAL_ROOTS_INDEX) - .unwrap(); - - let historical_roots_tree_hash_root = state.historical_roots_tree_hash_root(); - - let depth = CURRENT_SYNC_COMMITTEE_PROOF_LEN; - - let state_root = state.state_root().unwrap(); - - assert!( - verify_merkle_proof( - historical_roots_tree_hash_root, - &proof, - depth, - HISTORICAL_ROOTS_FIELD_INDEX, - state_root - ), - "Merkle proof verification failed" - ); - - println!("historical state roots proof verified successfully"); -} diff --git a/crates/forrestrie-examples/examples/historical_summary_proof.rs b/crates/forrestrie-examples/examples/historical_summary_proof.rs deleted file mode 100644 index 3314b998..00000000 --- a/crates/forrestrie-examples/examples/historical_summary_proof.rs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Historical Summary Proof Given Historical Summaries Root -//! -//! This example demonstrates how to prove the inclusion of historical summaries in the beacon state. - -use forrestrie::beacon_state::{ - HeadState, HISTORICAL_SUMMARIES_FIELD_INDEX, HISTORICAL_SUMMARIES_INDEX, -}; -use merkle_proof::verify_merkle_proof; -use types::{light_client_update::CURRENT_SYNC_COMMITTEE_PROOF_LEN, MainnetEthSpec}; - -#[tokio::main] -async fn main() { - let url = "https://www.lightclientdata.org/eth/v2/debug/beacon/states/head".to_string(); - println!("Requesting head state ... (this can take a while!)"); - let response = reqwest::get(url).await.unwrap(); - let mut state: HeadState = if response.status().is_success() { - let json_response: serde_json::Value = response.json().await.unwrap(); - serde_json::from_value(json_response).unwrap() - } else { - panic!("Request failed with status: {}", response.status()); - }; - - let proof = state - .compute_merkle_proof_for_historical_data(HISTORICAL_SUMMARIES_INDEX) - .unwrap(); - - let historical_summaries_tree_hash_root = state.historical_summaries_tree_hash_root().unwrap(); - - let state_root = state.state_root().unwrap(); - - let depth = CURRENT_SYNC_COMMITTEE_PROOF_LEN; - - assert!( - verify_merkle_proof( - historical_summaries_tree_hash_root, - &proof, - depth, - HISTORICAL_SUMMARIES_FIELD_INDEX, - state_root - ), - "Merkle proof verification failed" - ); - - println!("historical summaries proof verified successfully"); -} diff --git a/crates/forrestrie-examples/examples/match_ethereum_to_beacon.rs b/crates/forrestrie-examples/examples/match_ethereum_to_beacon.rs deleted file mode 100644 index 063b8d74..00000000 --- a/crates/forrestrie-examples/examples/match_ethereum_to_beacon.rs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Ethereum Block to Beacon Slot Lookup Example -//! -//! This example performs a binary search to find the corresponding Beacon chain -//! slot for a given Ethereum execution block number. The problem being addressed -//! is that, due to missed slots in the Beacon chain, Ethereum execution blocks -//! and Beacon chain slots are not always aligned. Therefore, finding the correct -//! Beacon slot that contains the execution block requires searching through -//! Beacon blocks until the execution block is located. -//! -//! ## Key Concepts -//! -//! - **Execution Block Number**: This refers to the Ethereum block number that -//! we're trying to locate within the Beacon chain. -//! - **Beacon Slot Number**: The slot number in the Beacon chain that contains -//! the corresponding Ethereum execution block. -//! - **Deneb Fork**: This is the Ethereum fork that the blocks in the example -//! are from. We can imagine using `const` values to represent the start slot -//! of the Deneb fork and other upgrades, as well as the offsets between Ethereum -//! and Beacon block numbers at different known points along the chain. -//! -//! ## Approach -//! -//! The example uses a binary search algorithm to locate the Beacon slot that -//! contains the execution block. It starts with a search range defined by -//! `DENEB_START_SLOT` and an upper bound based on an estimated offset. -//! During each iteration of the search, the Beacon block is fetched, and its -//! execution payload is examined to check if it contains the target Ethereum -//! block number. The search range is adjusted based on the result of this -//! comparison until the correct Beacon slot is found. -//! - -use beacon_protos::{Block as FirehoseBeaconBlock, Body}; -use firehose_client::{Chain, FirehoseClient}; -use forrestrie::beacon_state::ETHEREUM_BEACON_DENEB_OFFSET; -use std::cmp::Ordering::*; -use tracing::info; -use tracing_subscriber::FmtSubscriber; - -/// This block relates to the slot represented by [`BEACON_SLOT_NUMBER`]. -/// The execution block is in the execution payload of the Beacon block in slot [`BEACON_SLOT_NUMBER`]. -const EXECUTION_BLOCK_NUMBER: u64 = 20759937; -/// This slot is the slot of the Beacon block that contains the execution block with [`EXECUTION_BLOCK_NUMBER`]. -#[allow(unused)] -const BEACON_SLOT_NUMBER: u64 = 9968872; // Beacon slot 9968872 pairs with Ethereum block 20759937. - -const IMAGINARY_CURRENT_SLOT: u64 = 10_000_000; - -#[tokio::main] -async fn main() { - let subscriber = FmtSubscriber::builder() - .with_max_level(tracing::Level::INFO) - .finish(); - tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); - - let mut beacon_client = FirehoseClient::new(Chain::Beacon); - - let mut low = EXECUTION_BLOCK_NUMBER - ETHEREUM_BEACON_DENEB_OFFSET as u64; - let mut high = IMAGINARY_CURRENT_SLOT; - - let mut guesses = 0; - - while low <= high { - guesses += 1; - - let mid = low + (high - low) / 2; - - info!(guess = mid, "Current guess for Beacon slot"); - - let response = beacon_client.fetch_block(mid).await.unwrap().unwrap(); - let block = FirehoseBeaconBlock::try_from(response.into_inner()).unwrap(); - - let Some(Body::Deneb(body)) = &block.body else { - panic!("Unsupported block version!"); - }; - - let execution_payload = body.execution_payload.as_ref().unwrap(); - let block_number = execution_payload.block_number; - - match block_number.cmp(&EXECUTION_BLOCK_NUMBER) { - Less => low = mid + 1, - Greater => high = mid - 1, - Equal => { - info!( - beacon_slot = block.slot, - "Found matching Beacon block: {}!", block.slot - ); - break; - } - } - - if high == low || high == low + 1 { - if let Some(final_result) = try_final_fetches(low, high, &mut beacon_client).await { - println!( - "Found final result: matching execution block at Beacon slot: {}", - final_result - ); - break; - } - } - } - info!(guesses, "Guesses"); -} - -/// Helper function to fetch both `low` and `high` Beacon slots when binary search is down to two options -async fn try_final_fetches(low: u64, high: u64, client: &mut FirehoseClient) -> Option { - for slot in &[low, high] { - let response = client.fetch_block(*slot).await.unwrap().unwrap(); - - let block = FirehoseBeaconBlock::try_from(response.into_inner()).unwrap(); - - let Some(Body::Deneb(body)) = &block.body else { - return None; - }; - - let execution_payload = body.execution_payload.as_ref().unwrap(); - - if execution_payload.block_number == EXECUTION_BLOCK_NUMBER { - return Some(block.slot); - } - } - None -} diff --git a/crates/forrestrie-examples/examples/receipts_proof.rs b/crates/forrestrie-examples/examples/receipts_proof.rs deleted file mode 100644 index e8c7181e..00000000 --- a/crates/forrestrie-examples/examples/receipts_proof.rs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Receipts proof given an EL block's `receipt_root` -//! -//! This example shows how to generate an inclusion proof for a set of receipts of an EL block. -//! - -use firehose_client::{Chain, FirehoseClient}; -use firehose_protos::{EthBlock as Block, FullReceipt}; -use forrestrie::execution_layer::{build_trie_with_proofs, TargetLeaves}; -use reth_primitives::ReceiptWithBloom; -use reth_trie_common::proof::verify_proof; - -const EXECUTION_BLOCK_NUMBER: u64 = 20759937; - -#[tokio::main] -async fn main() { - let mut eth1_client = FirehoseClient::new(Chain::Ethereum); - let response = eth1_client - .fetch_block(EXECUTION_BLOCK_NUMBER) - .await - .unwrap() - .unwrap(); - let eth1_block: Block = Block::try_from(response.into_inner()).unwrap(); - - let receipts: Vec = eth1_block.full_receipts().unwrap(); - - let receipts_with_bloom: Vec = receipts - .iter() - .map(|full_receipt| full_receipt.get_receipt_wb().clone()) - .collect(); - - // These are the indexes of receipts for which proofs need to be generated - let target_idxs = &[1, 2, 3]; - let targets = TargetLeaves::from_indices(target_idxs, &receipts_with_bloom).unwrap(); - let mut hb = build_trie_with_proofs(&receipts_with_bloom, target_idxs); - - // produces the root, which matches the root of the blocks. - // hb.root() also calculates the proofs and store them in the HashBuilder. - let root = hb.root(); - - let calc_root = eth1_block.calculate_receipt_root(); - println!("roots: {:?}, {:?}", root, calc_root); - - // proofs can be taken and sorted, so each proof matches one of the target. - // each proof of a specific target receipt is provided in `take_proof_nodes()` - // and can be stored or verified singularly - let proof = hb.take_proof_nodes(); - for target in targets { - let _verification = verify_proof( - hb.root(), - target.nibbles.clone(), - Some(target.value.to_vec()), - proof - .clone() - .matching_nodes_sorted(&target.nibbles) - .iter() - .map(|(_, node)| node), - ); - } -} diff --git a/crates/forrestrie-examples/examples/single_block_post_merge_pre_capella_proof.rs b/crates/forrestrie-examples/examples/single_block_post_merge_pre_capella_proof.rs deleted file mode 100644 index 0a87c88b..00000000 --- a/crates/forrestrie-examples/examples/single_block_post_merge_pre_capella_proof.rs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! Proof for single block to be part of an era of beacon blocks using the [`HistoricalBatch`]. -//! -//! Notice that A [`HistoricalBatch`]` isn't an accumulator, it is a list of block_roots and state_roots -//! So each root in the [`HistoricalRootsAccumulator`] corresponds to hash_tree_root(historical_batch). -//! The batch is used to verify era against the accumulator. A block can be verified against an -//! [`HistoricalBatch`], hence chaining the proofs -use std::fs; - -use ethportal_api::consensus::beacon_state::HistoricalBatch; - -use ssz::Decode; -use trin_validation::{ - historical_roots_acc::HistoricalRootsAccumulator, merkle::proof::verify_merkle_proof, -}; - -#[tokio::main] -async fn main() { - // Load a historical batch. - // A historical batch has to be generated from beacon blocks or retrieved - // from some source that already calculated these - let bytes = - fs::read("./crates/forrestrie-examples/assets/historical_batch-573-c847a969.ssz").unwrap(); - let hist_batch = HistoricalBatch::from_ssz_bytes(&bytes).unwrap(); - - // check if my block_root is inside the HistoricalBatch - - // construct proof from historical batch - // In this example a slot that is inside the `HistoricalBatch` - // was picked: https://beaconcha.in/slot/4685828 - // NOTICE: we can also use the block roots themselves inside the the HistoricalBatch - // to figure out the slot by using the beacon chain explorer, for example: - // https://beaconcha.in/slot/58bbce808c399069fdd3e02e7906cd382ba8ffac8c1625a9d801ffa6a4120c98 - const EPOCH_SIZE: i32 = 8192; - let slot = 4685828; - let historical_root_index: i32 = slot % EPOCH_SIZE; - let historical_roots_proof = - hist_batch.build_block_root_proof((historical_root_index as u32).into()); - - // just checking if the rot macthes - let block_root = hist_batch.block_roots[historical_root_index as usize]; - - // The historical root we are getting: - println!("root: {:?}, index, {:?}", block_root, historical_root_index); - - // // verify the proof - let hist_acc = HistoricalRootsAccumulator::default(); - let block_root_index = slot % EPOCH_SIZE; - let gen_index = 2 * EPOCH_SIZE + block_root_index; - let historical_root_index = slot / EPOCH_SIZE; - let historical_root = hist_acc.historical_roots[historical_root_index as usize]; - - let result = verify_merkle_proof( - block_root, - &historical_roots_proof, - 14, - gen_index as usize, - historical_root, - ); - - println!("result of verifying proof: {:?}", result); -} diff --git a/crates/forrestrie-examples/examples/single_execution_block.rs b/crates/forrestrie-examples/examples/single_execution_block.rs deleted file mode 100644 index cbebc77d..00000000 --- a/crates/forrestrie-examples/examples/single_execution_block.rs +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Prove Inclusion of a Single Execution Layer Block in the Canonical History of the Blockchain -//! -//! This example demonstrates how to prove the inclusion of a single execution layer block in the canonical -//! history of the blockchain. -//! -//! This method includes the following proofs: -//! 1. The block hash of the Ethereum block matches the hash in the block header. -//! 2. The block is the block it says it is, calculating its tree hash root matches the hash in the `root` -//! field of the block. -//! 3. The Beacon block's Execution Payload matches the Ethereum block. -//! 4. Reproducing the block root for the "era", or 8192 slots, of a Beacon block's slot by streaming 8192 -//! Beacon blocks from the Beacon chain. -//! 5. Calculating the merkle proof of the block's inclusion in the block roots of the historical summary for -//! the given era. -//! -//! We use a fork of [`lighthouse`](https://github.com/sigp/lighthouse)'s [`types`] that allows us to access -//! the `block_summary_root` of the [`HistoricalSummary`]. -//! -//! While this example demonstrates verifying block 20759937 on the execution layer, you could also use the -//! same method to prove the inclusion of an entire era of 8192 blocks, since the method for verifying a single -//! block already includes streaming 8192 blocks for the era. And its the same 8192 blocks required to compute -//! the block roots tree hash root, which can then be compared to the tree hash root in the historical summary -//! for the era. -//! -use beacon_protos::{Block, BlockRoot, Body}; -use ethportal_api::Header; -use firehose_client::{Chain, FirehoseClient}; -use firehose_protos::EthBlock; -use forrestrie::{ - beacon_block::{ - HistoricalDataProofs, BEACON_BLOCK_BODY_PROOF_DEPTH, EXECUTION_PAYLOAD_FIELD_INDEX, - }, - beacon_state::{ - compute_block_roots_proof_only, HeadState, CAPELLA_START_ERA, HISTORY_TREE_DEPTH, - SLOTS_PER_HISTORICAL_ROOT, - }, -}; -use futures::StreamExt; -use merkle_proof::verify_merkle_proof; -use tree_hash::TreeHash; -use types::{ - historical_summary::HistoricalSummary, light_client_update::EXECUTION_PAYLOAD_INDEX, - BeaconBlock, BeaconBlockBody, BeaconBlockBodyDeneb, ExecPayload, Hash256, MainnetEthSpec, -}; - -/// This block relates to the slot represented by [`BEACON_SLOT_NUMBER`]. -/// The execution block is in the execution payload of the Beacon block in slot [`BEACON_SLOT_NUMBER`]. -const EXECUTION_BLOCK_NUMBER: u64 = 20759937; -/// This slot is the slot of the Beacon block that contains the execution block with [`EXECUTION_BLOCK_NUMBER`]. -const BEACON_SLOT_NUMBER: u64 = 9968872; // <- 9968872 pairs with 20759937 -/// The URL to fetch the head state of the Beacon chain. -const LIGHT_CLIENT_DATA_URL: &str = - "https://www.lightclientdata.org/eth/v2/debug/beacon/states/head"; - -#[tokio::main] -async fn main() { - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Get the head state of the Beacon chain from a Beacon API provider. - let state_handle = tokio::spawn(async move { - let url = LIGHT_CLIENT_DATA_URL.to_string(); - println!("Requesting head state ... (this can take a while!)"); - let response = reqwest::get(url).await.unwrap(); - let head_state: HeadState = response.json().await.unwrap(); - head_state - }); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Get the Ethereum block. - let mut eth1_client = FirehoseClient::new(Chain::Ethereum); - let response = eth1_client - .fetch_block(EXECUTION_BLOCK_NUMBER) - .await - .unwrap() - .unwrap(); - let eth1_block = EthBlock::try_from(response.into_inner()).unwrap(); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // And get the Beacon block. - let mut beacon_client = FirehoseClient::new(Chain::Beacon); - let response = beacon_client - .fetch_block(BEACON_SLOT_NUMBER) - .await - .unwrap() - .unwrap(); - let beacon_block = Block::try_from(response.into_inner()).unwrap(); - assert_eq!(beacon_block.slot, BEACON_SLOT_NUMBER); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Confirm that the block hash of the Ethereum block matches the hash in the block header. - let block_header = Header::try_from(ð1_block).unwrap(); - let eth1_block_hash = block_header.hash(); - assert_eq!(eth1_block_hash.as_slice(), ð1_block.hash); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Convert the Beacon block to a Lighthouse `BeaconBlock`. This allows us to use Lighthouse's - // implementation of the `TreeHash` trait to calculate the root of the Beacon block, which we - // use to verify that the block is the block it says it is, i.e., the hash value in the `root` - // field of the block matches the calculated root, which we calculate using the method implemented - // on the `BeaconBlock` struct in Lighthouse. - let lighthouse_beacon_block = BeaconBlock::::try_from(beacon_block.clone()) - .expect("Failed to convert Beacon block to Lighthouse BeaconBlock"); - - // Check the root of the Beacon block. This check shows that the calculation of the block root - // of the Beacon block matches the hash in the `root` field of the block we fetched over gRPC; - // the block is the block that it says it is. - let lighthouse_beacon_block_root = lighthouse_beacon_block.canonical_root(); - assert_eq!( - lighthouse_beacon_block_root.as_bytes(), - beacon_block.root.as_slice() - ); - let Some(Body::Deneb(body)) = beacon_block.body else { - panic!("Unsupported block version!"); - }; - let block_body: BeaconBlockBodyDeneb = body.try_into().unwrap(); - - // Confirm that the Beacon block's Execution Payload matches the Ethereum block we fetched. - assert_eq!( - block_body.execution_payload.block_number(), - EXECUTION_BLOCK_NUMBER - ); - - // Confirm that the Ethereum block matches the Beacon block's Execution Payload. - assert_eq!( - block_body - .execution_payload - .block_hash() - .into_root() - .as_bytes(), - eth1_block_hash.as_slice() - ); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Confirm that the Execution Payload is included in the Beacon block. - let block_body_hash = block_body.tree_hash_root(); - let execution_payload = &block_body.execution_payload; - let execution_payload_root = execution_payload.tree_hash_root(); - let body = BeaconBlockBody::from(block_body.clone()); - let proof = body.compute_merkle_proof(EXECUTION_PAYLOAD_INDEX).unwrap(); - let depth = BEACON_BLOCK_BODY_PROOF_DEPTH; - assert!(verify_merkle_proof( - execution_payload_root, - &proof, - depth, - EXECUTION_PAYLOAD_FIELD_INDEX, - block_body_hash - )); - - // The era of the block's slot. - // This is also the index of the historical summary containing the block roots for this era. - let era = lighthouse_beacon_block.slot().as_usize() / SLOTS_PER_HISTORICAL_ROOT; - - println!("Requesting 8192 blocks for the era... (this takes a while)"); - let num_blocks = SLOTS_PER_HISTORICAL_ROOT as u64; - let mut stream = beacon_client - .stream_blocks::((era * SLOTS_PER_HISTORICAL_ROOT) as u64, num_blocks) - .await - .unwrap(); - let mut block_roots: Vec = Vec::with_capacity(SLOTS_PER_HISTORICAL_ROOT); - while let Some(block) = stream.next().await { - let root = BlockRoot::try_from(block).unwrap(); - block_roots.push(root.0); - } - assert_eq!(block_roots.len(), SLOTS_PER_HISTORICAL_ROOT); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // The index of the block in the complete era of block roots. - // Beacon chain slot numbers are zero-based; genesis slot is 0. - // We need this to calculate the merkle inclusion proof later. - // If this is the first/last block of the era, the index is 0/8191. - let index = lighthouse_beacon_block.slot().as_usize() % SLOTS_PER_HISTORICAL_ROOT; - // Compute the proof of the block's inclusion in the block roots. - let proof = compute_block_roots_proof_only::(&block_roots, index).unwrap(); - // To get the correct index, we need to subtract the Capella start era. - // `HistoricalSummary` was introduced in Capella and the block we're proving inclusion for is in - // the post-Capella era. - // For pre-Capella states, we would use the same method, only using the historical_roots field. - let proof_era = era - CAPELLA_START_ERA; - - let head_state = state_handle.await.unwrap(); - let historical_summary: &HistoricalSummary = head_state - .data() - .historical_summaries() - .unwrap() - .get(proof_era) - .unwrap(); - let block_roots_tree_hash_root = historical_summary.block_summary_root(); - assert_eq!(proof.len(), HISTORY_TREE_DEPTH); - // Verify the proof. - assert!( - verify_merkle_proof( - lighthouse_beacon_block_root, // the root of the block - &proof, // the proof of the block's inclusion in the block roots - HISTORY_TREE_DEPTH, // the depth of the block roots tree - index, // the index of the block in the era - block_roots_tree_hash_root // The root of the block roots - ), - "Merkle proof verification failed" - ); - println!("All checks passed!"); -} diff --git a/crates/forrestrie-examples/examples/verify-era.rs b/crates/forrestrie-examples/examples/verify-era.rs deleted file mode 100644 index 92f29020..00000000 --- a/crates/forrestrie-examples/examples/verify-era.rs +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! In this example, we verify a complete era of both beacon blocks and execution blocks. -//! We first fetch a complete era of beacon blocks (8192 beacon blocks), compute the associated historical summary and -//! compare it against the historical summary from a current consensus stated. We also extract the -//! execution block headers and block numbers from the beacon blocks. We then fetch the execution -//! blocks using the extracted block numbers and verify the execution block data against the -//! extracted block headers. - -use beacon_protos::Block; -use ethportal_api::Header; -use firehose_client::{Chain, FirehoseClient}; -use firehose_protos::EthBlock; -use forrestrie::beacon_state::{ - HeadState, CAPELLA_START_ERA, HISTORY_TREE_DEPTH, SLOTS_PER_HISTORICAL_ROOT, -}; -use futures::StreamExt; -use tree_hash::TreeHash; -use types::{ - historical_summary::HistoricalSummary, BeaconBlock, BeaconBlockBodyDeneb, ExecPayload, - MainnetEthSpec, Slot, -}; - -use merkle_proof::MerkleTree; - -/// This slot is the starting slot of the Beacon block era. -const BEACON_SLOT_NUMBER: u64 = 10436608; -/// The URL to fetch the head state of the Beacon chain. -const LIGHT_CLIENT_DATA_URL: &str = - "https://www.lightclientdata.org/eth/v2/debug/beacon/states/head"; - -#[tokio::main] -async fn main() { - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Get the head state of the Beacon chain from a Beacon API provider. - let state_handle = tokio::spawn(async move { - let url = LIGHT_CLIENT_DATA_URL.to_string(); - println!("Requesting head state ... (this can take a while!)"); - let response = reqwest::get(url).await.unwrap(); - let head_state: HeadState = response.json().await.unwrap(); - head_state - }); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Here we are going to fetch all of the beacon blocks for an era. - // We will verify that the blocks are correct by computing a block_summary_root from the beacon blocks and comparing it to the block_summary_root in the historical summary from the consensus state. - let mut beacon_client = FirehoseClient::new(Chain::Beacon); - - // The era of the block's slot. - // This is also the index of the historical summary containing the block roots for this era. - let era = BEACON_SLOT_NUMBER as usize / SLOTS_PER_HISTORICAL_ROOT; - - // Stream the blocks - println!("Requesting 8192 blocks for the era... (this takes a while)"); - let num_blocks = SLOTS_PER_HISTORICAL_ROOT as u64; - let mut stream = beacon_client - .stream_blocks::((era * SLOTS_PER_HISTORICAL_ROOT) as u64, num_blocks) - .await - .unwrap(); - - // We are going to store off the execution block numbers and hashes for later verification. - let mut execution_block_number_and_hash = Vec::with_capacity(SLOTS_PER_HISTORICAL_ROOT); - - // We are going to store off the beacon block roots and calculate the block_summary_root from them. - let mut beacon_block_roots = Vec::with_capacity(SLOTS_PER_HISTORICAL_ROOT); - - let mut idx = 0; - let mut prev_slot = Slot::new(0); - let mut push_parent_root = false; - while let Some(block) = stream.next().await { - // Get the exeuction block number and blockhash. - let lighthouse_beacon_block = BeaconBlock::::try_from(block.clone()) - .expect("Failed to convert Beacon block to Lighthouse BeaconBlock"); - let Some(beacon_protos::Body::Deneb(body)) = block.body else { - panic!("Unsupported block version!"); - }; - let block_body: BeaconBlockBodyDeneb = body.try_into().unwrap(); - let execution_block_number = block_body.execution_payload.block_number(); - let execution_block_hash = block_body.execution_payload.block_hash(); - execution_block_number_and_hash.push((execution_block_number, execution_block_hash)); - - // There are a few things going on here: - // 1. there is currently a bug in the Firehose API where if a slot does not have an execution payload (the slot was skipped), then Firehose simply repeats the previous beacon block. - // This is a problem because this means that we can't calculate the beacon block root for the skipped slot. - // As a workaround, whenever we see a repeated block (implying a skipped slot), we will skip processing that block and on the next block we will push the parent root to the beacon block roots. - // Assuming that the parent root is correct, then the block_summary_root will be correct. - // - // 2. We are going to check the consistency of the beacon chain by comparing the claimed parent root of the current block against the previous block's root, they should match. - // This helps us catch errors within the era. - - if idx > 0 { - // If there was a skipped slot, then we will skip processing the current block and push the parent root to the beacon block roots. - let curr_slot = lighthouse_beacon_block.as_deneb().unwrap().slot; - if curr_slot == prev_slot { - push_parent_root = true; - idx += 1; - println!("Slot skipped!"); - continue; - } - if push_parent_root { - let parent_root = lighthouse_beacon_block.as_deneb().unwrap().parent_root; - beacon_block_roots.push(parent_root); - push_parent_root = false; - } - - // Check the parent root of the current block against the previous block's root. - let prev_block_root = beacon_block_roots[idx - 1]; - let prev_block_root_from_block = - lighthouse_beacon_block.as_deneb().unwrap().parent_root; - if prev_block_root != prev_block_root_from_block { - println!("Slot {}", lighthouse_beacon_block.as_deneb().unwrap().slot); - panic!("Block root mismatch!"); - } - println!( - "Slot {} verified!", - lighthouse_beacon_block.as_deneb().unwrap().slot - ); - } - - // Store the beacon block root. - let beacon_block_root = lighthouse_beacon_block.tree_hash_root(); - beacon_block_roots.push(beacon_block_root); - idx += 1; - prev_slot = lighthouse_beacon_block.as_deneb().unwrap().slot; - } - - // Check that we have the correct number of blocks. - assert_eq!( - execution_block_number_and_hash.len(), - SLOTS_PER_HISTORICAL_ROOT - ); - assert_eq!(beacon_block_roots.len(), SLOTS_PER_HISTORICAL_ROOT); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Here is where we check that the historical summary from the consensus state matches the historical summary computed from the beacon blocks. - - // Caculate the block_summary_root from the beacon blocks. Note that the block_summary_root is a field in the HistoricalSummary. - let beacon_block_roots_tree_hash_root = - MerkleTree::create(&beacon_block_roots, HISTORY_TREE_DEPTH).hash(); - - // To get the correct index for the era's HistoricalSummary in the consensus state, we need to subtract the Capella start era. - // `HistoricalSummary` was introduced in Capella and the block we're proving inclusion for is in - // the post-Capella era. - // For pre-Capella states, we would use the same method, only using the historical_roots field. - let era_index = era - CAPELLA_START_ERA; - - // Get the historical summary for the era from the consensus state. - let head_state = state_handle.await.unwrap(); - let historical_summary: &HistoricalSummary = head_state - .data() - .historical_summaries() - .unwrap() - .get(era_index) - .unwrap(); - - let block_summary_root = historical_summary.block_summary_root(); - assert_eq!(beacon_block_roots_tree_hash_root, block_summary_root); - println!("Historical summary verified!"); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Now that we have a verified set of execution block headers (and block numbers) from the beacon blocks, we can fetch the execution blocks and verify them. - - for (number, blockhash) in execution_block_number_and_hash { - // Fetch execution blocks from the Firehose API. - let mut eth1_client = FirehoseClient::new(Chain::Ethereum); - let response = eth1_client.fetch_block(number).await.unwrap().unwrap(); - let eth1_block = EthBlock::try_from(response.into_inner()).unwrap(); - - // Confirm that the block hash of the Ethereum block matches the hash in the block header. - let block_header = Header::try_from(ð1_block).unwrap(); - let eth1_block_hash = block_header.hash(); - assert_eq!(eth1_block_hash.as_slice(), ð1_block.hash); - - // Confirm that the Ethereum block matches the Beacon block's Execution Payload. - // This is our first major check linking the exuction layer to the consensus layer. - assert_eq!(blockhash.into_root().as_bytes(), eth1_block_hash.as_slice()); - println!("Block number {} verified!", number); - } - - // At this point, we have checked that the complete era's beacon blocks are correct by comparing against a historical summary from the consensus state, - // and that the corresponding execution blocks are correct by comparing against the block headers from the verified beacon blocks. - // Assuming that all checks passed, then the extracted data has been verified. - println!("All checks passed!"); -} diff --git a/crates/forrestrie-examples/examples/verify_block_inclusion_proof.rs b/crates/forrestrie-examples/examples/verify_block_inclusion_proof.rs deleted file mode 100644 index a2ae87fd..00000000 --- a/crates/forrestrie-examples/examples/verify_block_inclusion_proof.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Verify Block Inclusion Proof -//! -//! In Ethereum's Beacon Chain, execution layer payloads are included in the block body. -//! -//! This example demonstrates how to verify the inclusion proof of an execution payload -//! in a block body. -//! -//! For example, for block `20672593`, the execution payload root can be computed using the `tree_hash_root` method -//! from [`TreeHash`]: -//! -//! ```rust -//! let execution_payload_root = execution_payload.tree_hash_root(); -//! ``` -//! -//! Similarly, the block body root is derived as follows: -//! -//! ```rust -//! let block_body_hash = block_body.tree_hash_root(); -//! ``` -//! -//! The inclusion proof can be computed using the `compute_merkle_proof` method from [`BeaconBlockBody`]: -//! -//! ```rust -//! let proof = body.compute_merkle_proof(EXECUTION_PAYLOAD_INDEX).unwrap(); -//! ``` -//! -use beacon_protos::Block as FirehoseBeaconBlock; -use firehose_client::{Chain, FirehoseClient}; -use forrestrie::beacon_block::{ - HistoricalDataProofs, BEACON_BLOCK_BODY_PROOF_DEPTH, EXECUTION_PAYLOAD_FIELD_INDEX, -}; -use merkle_proof::verify_merkle_proof; -use tree_hash::TreeHash; -use types::{ - light_client_update::EXECUTION_PAYLOAD_INDEX, BeaconBlock, BeaconBlockBody, MainnetEthSpec, -}; - -#[tokio::main] -async fn main() { - // test_inclusion_proof_for_block_body_given_execution_payload - let mut beacon_client = FirehoseClient::new(Chain::Beacon); - - let response = beacon_client.fetch_block(20672593).await.unwrap().unwrap(); - - let block = FirehoseBeaconBlock::try_from(response.into_inner()).unwrap(); - - let beacon_block = BeaconBlock::::try_from(block).unwrap(); - - let execution_payload = beacon_block.body().execution_payload().unwrap(); - let execution_payload_root = execution_payload.tree_hash_root(); - - let block_body = beacon_block.body_deneb().unwrap(); - let block_body_hash = block_body.tree_hash_root(); - - let body = BeaconBlockBody::from(block_body.clone()); - let proof = body.compute_merkle_proof(EXECUTION_PAYLOAD_INDEX).unwrap(); - - let depth = BEACON_BLOCK_BODY_PROOF_DEPTH; - - assert_eq!(proof.len(), depth, "proof length should equal depth"); - - assert!(verify_merkle_proof( - execution_payload_root, - &proof, - depth, - EXECUTION_PAYLOAD_FIELD_INDEX, - block_body_hash - )); - - println!("verify_block_inclusion_proof.rs done"); -} diff --git a/crates/forrestrie-examples/src/lib.rs b/crates/forrestrie-examples/src/lib.rs deleted file mode 100644 index 3f733a7e..00000000 --- a/crates/forrestrie-examples/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 diff --git a/crates/forrestrie/CHANGELOG.md b/crates/forrestrie/CHANGELOG.md deleted file mode 100644 index 5c0cfa0d..00000000 --- a/crates/forrestrie/CHANGELOG.md +++ /dev/null @@ -1,8 +0,0 @@ -# Changelog - -## [0.1.1](https://github.com/semiotic-ai/veemon/compare/forrestrie-v0.1.0...forrestrie-v0.1.1) (2024-10-22) - - -### Bug Fixes - -* **forrestrie/examples:** substract capella start from index ([829c372](https://github.com/semiotic-ai/veemon/commit/829c3720eb99b5b0991c4e83e05876616cdfc168)) diff --git a/crates/forrestrie/Cargo.toml b/crates/forrestrie/Cargo.toml deleted file mode 100644 index 578dde6f..00000000 --- a/crates/forrestrie/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "forrestrie" -version = "0.1.1" -edition = "2021" - -[lib] -name = "forrestrie" -path = "src/lib.rs" - -[dependencies] -alloy-rlp.workspace = true -alloy-primitives.workspace = true -bls.workspace = true -merkle_proof.workspace = true -primitive-types.workspace = true -reth-trie-common.workspace = true -reth-primitives.workspace = true -serde = { workspace = true, features = ["derive"] } -ssz_types.workspace = true -tree_hash = "0.6.0" -types.workspace = true - -[dev-dependencies] -rand.workspace = true -fake.workspace = true diff --git a/crates/forrestrie/README.md b/crates/forrestrie/README.md deleted file mode 100644 index f7d18240..00000000 --- a/crates/forrestrie/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Forrestrie - -Library of types and methods for verifying post-merge Ethereum data. - -## Documentation - -- Notion doc on -[Post-merge Header Record Data Structure](https://www.notion.so/semiotic/Post-merge-header_record-data-structure-7290d03d356946188bdb9ac29366f510?pvs=4). -- [Beacon Chain `BeaconState` spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#beaconstate) -- [Beacon Chain `BeaconBlockBody` spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/deneb/beacon-chain.md#beaconblockbody) -- The [fork of `sigp/lighthouse`](https://github.com/semiotic-ai/lighthouse) we've been spiking. -- [Google Drive shared resources](https://drive.google.com/drive/folders/19QBMHZFAV7uo_Cu4RwLPTwGpBcQMd-hy), -including `head-state.json` used in `beacon_state.rs` tests. - -## Examples - -See [forrestrie-examples](./../forrestrie-examples/README.md)! - -## Protobuffers - -### [protos/type.proto](https://github.com/pinax-network/firehose-beacon/blob/main/proto/sf/beacon/type/v1/type.proto) - -Pinax's Firehose Beacon `Block` implementation. diff --git a/crates/forrestrie/src/beacon_block.rs b/crates/forrestrie/src/beacon_block.rs deleted file mode 100644 index b4ef04e3..00000000 --- a/crates/forrestrie/src/beacon_block.rs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use serde::{Deserialize, Serialize}; -use tree_hash::TreeHash; -use types::{ - beacon_block_body::NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES, - light_client_update::{self, EXECUTION_PAYLOAD_INDEX}, - BeaconBlock, BeaconBlockBody, Error, EthSpec, ForkName, Hash256, MainnetEthSpec, -}; - -#[derive(Debug, Deserialize, Serialize)] -pub struct BlockWrapper { - pub version: String, - pub execution_optimistic: bool, - pub finalized: bool, - pub data: Data, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct Data { - pub message: BeaconBlock, -} - -/// Merkle proof depth for a `BeaconBlockBody` struct with 12 fields. -/// -/// The proof depth is determined by finding the smallest power of 2 that is -/// greater than or equal to the number of fields. In this case, the number of -/// fields is 12, which is between 8 (2^3) and 16 (2^4). -pub const BEACON_BLOCK_BODY_PROOF_DEPTH: usize = 4; - -/// The field corresponds to the index of the `execution_payload` field in the [`BeaconBlockBody`] struct: -/// . -pub const EXECUTION_PAYLOAD_FIELD_INDEX: usize = 9; - -pub trait HistoricalDataProofs { - fn compute_merkle_proof(&self, index: usize) -> Result, Error>; -} - -impl HistoricalDataProofs for BeaconBlockBody { - fn compute_merkle_proof(&self, index: usize) -> Result, Error> { - let field_index = match index { - index if index == EXECUTION_PAYLOAD_INDEX => index - .checked_sub(NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES) - .ok_or(Error::IndexNotSupported(index))?, - _ => return Err(Error::IndexNotSupported(index)), - }; - - let attestations_root = if self.fork_name() > ForkName::Electra { - self.attestations_electra()?.tree_hash_root() - } else { - self.attestations_base()?.tree_hash_root() - }; - - let attester_slashings_root = if self.fork_name() > ForkName::Electra { - self.attester_slashings_electra()?.tree_hash_root() - } else { - self.attester_slashings_base()?.tree_hash_root() - }; - - let mut leaves = vec![ - self.randao_reveal().tree_hash_root(), - self.eth1_data().tree_hash_root(), - self.graffiti().tree_hash_root(), - self.proposer_slashings().tree_hash_root(), - attester_slashings_root, - attestations_root, - self.deposits().tree_hash_root(), - self.voluntary_exits().tree_hash_root(), - ]; - - if let Ok(sync_aggregate) = self.sync_aggregate() { - leaves.push(sync_aggregate.tree_hash_root()) - } - - if let Ok(execution_payload) = self.execution_payload() { - leaves.push(execution_payload.tree_hash_root()) - } - - if let Ok(bls_to_execution_changes) = self.bls_to_execution_changes() { - leaves.push(bls_to_execution_changes.tree_hash_root()) - } - - if let Ok(blob_kzg_commitments) = self.blob_kzg_commitments() { - leaves.push(blob_kzg_commitments.tree_hash_root()) - } - - let depth = light_client_update::EXECUTION_PAYLOAD_PROOF_LEN; - let tree = merkle_proof::MerkleTree::create(&leaves, depth); - let (_, proof) = tree.generate_proof(field_index, depth)?; - - Ok(proof) - } -} diff --git a/crates/forrestrie/src/beacon_state.rs b/crates/forrestrie/src/beacon_state.rs deleted file mode 100644 index 49342c1b..00000000 --- a/crates/forrestrie/src/beacon_state.rs +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use merkle_proof::MerkleTree; -use primitive_types::H256; -use serde::{Deserialize, Serialize}; -use std::sync::Arc; -use tree_hash::TreeHash; -use types::{ - historical_summary::HistoricalSummary, light_client_update, map_beacon_state_altair_fields, - map_beacon_state_base_fields, map_beacon_state_bellatrix_fields, - map_beacon_state_capella_fields, map_beacon_state_deneb_fields, - map_beacon_state_electra_fields, BeaconBlockHeader, BeaconState, BeaconStateAltair, - BeaconStateBase, BeaconStateBellatrix, BeaconStateCapella, BeaconStateDeneb, - BeaconStateElectra, BeaconStateError as Error, BitVector, Checkpoint, Epoch, Eth1Data, EthSpec, - ExecutionPayloadHeaderBellatrix, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, - ExecutionPayloadHeaderElectra, Fork, Hash256, List, ParticipationFlags, PendingAttestation, - PendingBalanceDeposit, PendingConsolidation, PendingPartialWithdrawal, Slot, SyncCommittee, - Validator, Vector, -}; - -/// The number of slots in an epoch. -pub const SLOTS_PER_EPOCH: usize = 32; -/// The number of slots in an era. -pub const SLOTS_PER_ERA: usize = SLOTS_PER_HISTORICAL_ROOT; -/// Slots are 0-indexed. -/// See, for example, `https://beaconcha.in/slot/0`. -pub const BEACON_GENESIS_SLOT: usize = 0; -/// See [Upgrading Ethereum](https://eth2book.info/capella/part4/history/) for more information. -pub const PHASE_0_START_EPOCH: usize = 0; -/// See [Upgrading Ethereum](https://eth2book.info/capella/part4/history/) for more information. -pub const ALTAIR_START_EPOCH: usize = 74240; -/// See [Upgrading Ethereum](https://eth2book.info/capella/part4/history/) for more information. -pub const BELLATRIX_START_EPOCH: usize = 144896; -/// See [Upgrading Ethereum](https://eth2book.info/capella/part4/history/) for more information. -pub const CAPELLA_START_EPOCH: usize = 194048; -/// See [Upgrading Ethereum](https://eth2book.info/capella/part4/history/) for more information. -/// The first slot number of the Deneb fork. -pub const CAPELLA_START_SLOT: usize = CAPELLA_START_EPOCH * SLOTS_PER_EPOCH; -/// The first era of the Deneb fork. -pub const CAPELLA_START_ERA: usize = - (CAPELLA_START_EPOCH * SLOTS_PER_EPOCH) / SLOTS_PER_HISTORICAL_ROOT; -/// -pub const DENEB_START_SLOT: usize = 8626176; -/// -pub const FIRST_EXECUTION_BLOCK_DENEB: usize = 19426587; -/// The offset between the Ethereum block number and the Beacon block number at the start of the Deneb fork, -/// i.e. the difference between the first execution block number in the Deneb fork and the start slot number of the Deneb fork. -pub const ETHEREUM_BEACON_DENEB_OFFSET: usize = FIRST_EXECUTION_BLOCK_DENEB - DENEB_START_SLOT; - -/// [`BeaconState`] `block_roots` vector has length [`SLOTS_PER_HISTORICAL_ROOT`] (See ), -/// the value of which is calculated uint64(2**13) (= 8,192) (See ) -pub const HISTORY_TREE_DEPTH: usize = 13; - -/// The historical roots tree (pre-Capella) and the historical summaries tree (post-Capella) have the same depth. -/// Both tree's root has the block_roots tree root and the state_roots tree root as children and so has one more layer than each of these trees. -pub const HISTORICAL_SUMMARY_TREE_DEPTH: usize = 14; - -/// Historical roots is a top-level field on [`BeaconState`], subtract off the generalized indices -// for the internal nodes. Result should be 7, the field offset of the committee in the [`BeaconState`]: -// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#beaconstate -pub const HISTORICAL_ROOTS_INDEX: usize = 39; - -/// Historical summaries is a top-level field on [`BeaconState`], subtract off the generalized indices -// for the internal nodes. Result should be 27, the field offset of the committee in the [`BeaconState`]: -// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#beaconstate -pub const HISTORICAL_SUMMARIES_INDEX: usize = 59; - -/// Index of `historical_roots` field in the [`BeaconState`] [struct](https://github.com/ethereum/annotated-spec/blob/master/phase0/beacon-chain.md#beaconstate). -pub const HISTORICAL_ROOTS_FIELD_INDEX: usize = 7; - -/// Index of `historical_summaries` field in the (post-Capella) [`BeaconState`] [struct](https://github.com/ethereum/annotated-spec/blob/master/capella/beacon-chain.md#beaconstate). -pub const HISTORICAL_SUMMARIES_FIELD_INDEX: usize = 27; - -/// The maximum number of block roots that can be stored in a [`BeaconState`]'s `block_roots` list. -pub const SLOTS_PER_HISTORICAL_ROOT: usize = 8192; - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct HeadState { - version: String, - execution_optimistic: bool, - data: BeaconState, -} - -impl HeadState { - pub fn compute_merkle_proof_for_historical_data( - &self, - index: usize, - ) -> Result, Error> { - // 1. Convert generalized index to field index. - let field_index = match index { - HISTORICAL_ROOTS_INDEX | HISTORICAL_SUMMARIES_INDEX => index - .checked_sub(self.data.num_fields_pow2()) - .ok_or(Error::IndexNotSupported(index))?, - _ => return Err(Error::IndexNotSupported(index)), - }; - - // 2. Get all `BeaconState` leaves. - let mut leaves = vec![]; - #[allow(clippy::arithmetic_side_effects)] - match &self.data { - BeaconState::Base(state) => { - map_beacon_state_base_fields!(state, |_, field| { - leaves.push(field.tree_hash_root()); - }); - } - BeaconState::Altair(state) => { - map_beacon_state_altair_fields!(state, |_, field| { - leaves.push(field.tree_hash_root()); - }); - } - BeaconState::Bellatrix(state) => { - map_beacon_state_bellatrix_fields!(state, |_, field| { - leaves.push(field.tree_hash_root()); - }); - } - BeaconState::Capella(state) => { - map_beacon_state_capella_fields!(state, |_, field| { - leaves.push(field.tree_hash_root()); - }); - } - BeaconState::Deneb(state) => { - map_beacon_state_deneb_fields!(state, |_, field| { - leaves.push(field.tree_hash_root()); - }); - } - BeaconState::Electra(state) => { - map_beacon_state_electra_fields!(state, |_, field| { - leaves.push(field.tree_hash_root()); - }); - } - }; - - // 3. Make deposit tree. - // Use the depth of the `BeaconState` fields (i.e. `log2(32) = 5`). - let depth = light_client_update::CURRENT_SYNC_COMMITTEE_PROOF_LEN; - let tree = MerkleTree::create(&leaves, depth); - let (_, proof) = tree.generate_proof(field_index, depth)?; - - Ok(proof) - } - - pub fn data(&self) -> &BeaconState { - &self.data - } - - pub fn execution_optimistic(&self) -> bool { - self.execution_optimistic - } - - pub fn historical_roots_tree_hash_root(&self) -> H256 { - self.data.historical_roots().tree_hash_root() - } - - pub fn historical_summaries_tree_hash_root(&self) -> Result { - Ok(self.data.historical_summaries()?.tree_hash_root()) - } - - pub fn state_root(&mut self) -> Result { - self.data.canonical_root() - } - - pub fn version(&self) -> &str { - &self.version - } - - /// This computation only makes sense if we have all of the leaves (BeaconBlock roots) to construct - /// the [`HistoricalSummary`] Merkle tree. - /// We construct a new [`HistoricalSummary`] from the state and check that the tree root is in historical_summaries. - /// This will be true if the state is in the first slot of an era. - pub fn block_roots_contain_entire_era(&self) -> Result { - // Check if the block_roots buffer can have accumulated an entire era, i.e. 8192 blocks. - if self.data.block_roots().len() % SLOTS_PER_HISTORICAL_ROOT == 0 { - let historical_summary = HistoricalSummary::new(&self.data); - Ok(self - .data - .historical_summaries()? - .iter() - .last() - .map(|summary| summary == &historical_summary) - .unwrap_or(false)) - } else { - Ok(false) - } - } - - /// Computes a Merkle inclusion proof of a `BeaconBlock` root using Merkle trees from either - /// the [`historical_roots`](https://github.com/ethereum/annotated-spec/blob/master/phase0/beacon-chain.md#beaconstate) - /// or [`historical_summaries`](https://github.com/ethereum/annotated-spec/blob/master/capella/beacon-chain.md#beaconstate) list. - /// See the discussion [here](https://github.com/ethereum/annotated-spec/blob/master/phase0/beacon-chain.md#slots_per_historical_root) - /// for more details about the `historical_roots` and [here](https://github.com/ethereum/annotated-spec/blob/master/capella/beacon-chain.md#historicalsummary) - /// about `historical_summaries`. - pub fn compute_block_roots_proof(&self, index: usize) -> Result, Error> { - // Construct the block_roots Merkle tree and generate the proof. - let leaves = self.data.block_roots().to_vec(); - let tree = MerkleTree::create(&leaves, HISTORY_TREE_DEPTH); - let (_, mut proof) = tree.generate_proof(index, HISTORY_TREE_DEPTH)?; - - // We are going to verify this proof using the HistoricalSummary root, the two children nodes are the block_roots tree root and that state_roots tree root. - // So we append the state_roots tree root to the proof. - let state_roots_root = self.data.state_roots().tree_hash_root(); - proof.push(state_roots_root); - - Ok(proof) - } - - pub fn compute_block_roots_proof_only(&self, index: usize) -> Result, Error> { - let leaves = self.data.block_roots().to_vec(); - let tree = MerkleTree::create(&leaves, HISTORY_TREE_DEPTH); - let (_, proof) = tree.generate_proof(index, HISTORY_TREE_DEPTH)?; - - Ok(proof) - } -} - -// Construct the block_roots Merkle tree and generate the proof. -pub fn compute_block_roots_proof_only( - block_roots: &[H256], - index: usize, -) -> Result, Error> { - let leaves = block_roots; - let tree = MerkleTree::create(leaves, HISTORY_TREE_DEPTH); - let (_, proof) = tree.generate_proof(index, HISTORY_TREE_DEPTH).unwrap(); - - Ok(proof) -} diff --git a/crates/forrestrie/src/execution_layer.rs b/crates/forrestrie/src/execution_layer.rs deleted file mode 100644 index 245c8a27..00000000 --- a/crates/forrestrie/src/execution_layer.rs +++ /dev/null @@ -1,326 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! Execution Layer functionality to build a Merkle Patricia Trie (MPT) from Ethereum receipts -//! and generate inclusion proofs for specified receipts within the trie. It includes data structures -//! for parsing and handling receipt data, as well as utilities for encoding and decoding as required -//! by the Ethereum specification. - -use alloy_primitives::{Bloom, U256}; -use alloy_rlp::Encodable; -use reth_primitives::{Log, Receipt, ReceiptWithBloom, TxType}; -use reth_trie_common::{proof::ProofRetainer, root::adjust_index_for_rlp, HashBuilder, Nibbles}; -use serde::{Deserialize, Deserializer, Serialize}; -use std::vec::IntoIter; - -#[derive(Debug, Deserialize, Serialize)] -pub struct ReceiptJson { - #[serde(rename = "type")] - #[serde(deserialize_with = "str_to_type")] - pub tx_type: TxType, - #[serde(rename = "blockHash")] - pub block_hash: String, - #[serde(rename = "blockNumber")] - pub block_number: String, - pub logs: Vec, - #[serde(rename = "cumulativeGasUsed")] - pub cumulative_gas_used: U256, - #[serde(deserialize_with = "status_to_bool")] - pub status: bool, - // TODO: should we trust logsBloom provided or calculate it from the logs? - #[serde(rename = "logsBloom")] - pub logs_bloom: Bloom, -} - -impl ReceiptJson { - #[cfg(test)] - fn fake() -> Self { - use alloy_primitives::{bytes, fixed_bytes, Address}; - use rand::{self, rngs::OsRng, RngCore}; - - fn fake_log() -> Log { - // generate random slice of bytes - let mut random_bytes = [0u8; 20]; - OsRng.fill_bytes(&mut random_bytes); - // Create a 32-byte array initialized with zeros - let mut bytes = [0u8; 32]; - - // Insert the random bytes into the last 20 bytes of the array - bytes[12..].copy_from_slice(&random_bytes); - - // Generate a static Log based on an actual log receipt - Log::new_unchecked( - Address::random(), - vec![ - fixed_bytes!( - "e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c" - ), - fixed_bytes!( - "0000000000000000000000003328f7f4a1d1c57c35df56bbf0c9dcafca309c49" - ), - ], - bytes!("0000000000000000000000000000000000000000000000000dcf09da3e1eb9f3"), - ) - } - - let logs: Vec = (0..5).map(|_| fake_log()).collect(); - - ReceiptJson { - tx_type: TxType::Eip1559, // Replace with any desired variant - block_hash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" - .to_string(), - block_number: "0x1a".to_string(), - logs, - cumulative_gas_used: U256::from(0x5208), - status: true, - logs_bloom: Bloom::default(), - } - } -} - -/// Represents a leaf in the trie for which a proof is to be generated, i.e., the target of the proof. -/// The `nibbles` represent the path to the leaf in the trie, and the `value` is the data stored at the leaf. -#[derive(Debug)] -pub struct TargetLeaf { - pub nibbles: Nibbles, - pub value: Vec, -} - -impl TargetLeaf { - // Constructor to create a new TargetLeaf - pub fn new(nibbles: Nibbles, value: Vec) -> Self { - TargetLeaf { nibbles, value } - } -} - -pub struct TargetLeaves(Vec); - -impl TargetLeaves { - fn new() -> Self { - TargetLeaves(Vec::new()) - } - - pub fn from_indices( - target_idxs: &[usize], - receipts: &[ReceiptWithBloom], - ) -> Result { - let mut index_buffer = Vec::new(); - let mut value_buffer = Vec::new(); - let mut targets = TargetLeaves::new(); - let receipts_len = receipts.len(); - - for &target_idx in target_idxs { - if target_idx >= receipts_len { - return Err("Index out of bounds"); - } - - index_buffer.clear(); - value_buffer.clear(); - - // Adjust the index and encode it - let index = adjust_index_for_rlp(target_idx, receipts_len); - index.encode(&mut index_buffer); - - // Generate nibble path from the index buffer - let nibble = Nibbles::unpack(&index_buffer); - - // Encode the receipt and create TargetLeaf - receipts[index].encode_inner(&mut value_buffer, false); - targets - .0 - .push(TargetLeaf::new(nibble, value_buffer.clone())); - } - - Ok(targets) - } -} - -impl IntoIterator for TargetLeaves { - type Item = TargetLeaf; - type IntoIter = IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl TryFrom<&ReceiptJson> for ReceiptWithBloom { - type Error = String; - - fn try_from(receipt_json: &ReceiptJson) -> Result { - let cumulative_gas_used = receipt_json - .cumulative_gas_used - .try_into() - .map_err(|_| "Failed to convert U256 to u64".to_string())?; - - let receipt = Receipt { - tx_type: receipt_json.tx_type, - success: receipt_json.status, - cumulative_gas_used, - logs: receipt_json.logs.clone(), - // NOTICE: receipts will have more fields depending of the EVM chain. - // this is how to handle them in the futuro - // #[cfg(feature = "optimism")] - // deposit_nonce: None, // Handle Optimism-specific fields as necessary - // #[cfg(feature = "optimism")] - // deposit_receipt_version: None, - }; - - Ok(ReceiptWithBloom { - bloom: receipt_json.logs_bloom, - receipt, - }) - } -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct ReceiptsFromBlock { - pub result: Vec, -} - -impl FromIterator for ReceiptsFromBlock { - fn from_iter>(iter: I) -> Self { - ReceiptsFromBlock { - result: iter.into_iter().collect(), - } - } -} - -fn status_to_bool<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let status_str: &str = Deserialize::deserialize(deserializer)?; - match status_str { - "0x1" => Ok(true), - "0x0" => Ok(false), - _ => Err(serde::de::Error::custom("Invalid status value")), - } -} - -fn str_to_type<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let tx_type_str: &str = Deserialize::deserialize(deserializer)?; - // Convert the hex string (without the "0x" prefix) to u8 - let tx_type_value = u8::from_str_radix(tx_type_str.trim_start_matches("0x"), 16) - .map_err(|_| serde::de::Error::custom("Invalid tx_type value"))?; - TxType::try_from(tx_type_value).map_err(|_| serde::de::Error::custom("Invalid tx_type value")) -} - -/// builds the trie to generate proofs from the Receipts -/// generate a different root. Make sure that the source of receipts sorts them by `logIndex` -/// # Example -/// -/// ```no_run -/// # use reth_primitives::ReceiptWithBloom; -/// # use forrestrie::execution_layer::build_trie_with_proofs; -/// # let receipts_json = vec![]; -/// // Assume `receipts_json` is a vector of deserialized receipt objects from an execution block given by an Ethereum node. -/// let receipts_with_bloom: Vec = receipts_json -/// .iter() -/// .map(ReceiptWithBloom::try_from) -/// .collect::>().unwrap(); -/// -/// // Specify the indices of receipts for which proofs are needed. -/// let target_indices = &[0, 2, 5]; -/// -/// // Build the trie and obtain proofs. -/// let mut hash_builder = build_trie_with_proofs(&receipts_with_bloom, target_indices); -/// -/// // Retrieve the root hash of the trie, and retain the proofs so they can be verified. -/// let trie_root = hash_builder.root(); -/// ``` -pub fn build_trie_with_proofs(receipts: &[ReceiptWithBloom], target_idxs: &[usize]) -> HashBuilder { - // Initialize ProofRetainer with the target nibbles (the keys for which we want proofs) - let receipts_len = receipts.len(); - let targets: Vec = target_idxs - .iter() - .map(|&i| { - let index = adjust_index_for_rlp(i, receipts_len); - let mut index_buffer = Vec::new(); - index.encode(&mut index_buffer); - Nibbles::unpack(&index_buffer) - }) - .collect(); - - let proof_retainer = ProofRetainer::new(targets); - let mut hb = HashBuilder::default().with_proof_retainer(proof_retainer); - - for i in 0..receipts_len { - // Adjust the index for RLP - let index = adjust_index_for_rlp(i, receipts_len); - - // Encode the index into nibbles - let mut index_buffer = Vec::new(); - index.encode(&mut index_buffer); - let index_nibbles = Nibbles::unpack(&index_buffer); - - // Encode the receipt value - let mut value_buffer = Vec::new(); - receipts[index].encode_inner(&mut value_buffer, false); - - hb.add_leaf(index_nibbles, &value_buffer); - } - - hb -} - -#[cfg(test)] -mod tests { - use super::*; - use reth_trie_common::proof::verify_proof; - - #[test] - fn test_compute_receipts_trie_root_and_proof() { - let block_receipts: ReceiptsFromBlock = (0_i32..10).map(|_| ReceiptJson::fake()).collect(); - - let receipts_with_bloom: Result, String> = block_receipts - .result - .iter() - .map(ReceiptWithBloom::try_from) - .collect::, _>>(); - - // computes the root and verify against existing data - let mut hb: HashBuilder; - //target_idxs are the logIndexes for receipts to get proofs from. - // these values are arbitrary - let target_idxs = &[4]; - let targets: TargetLeaves; - - match receipts_with_bloom { - Ok(receipts) => { - hb = build_trie_with_proofs(&receipts, target_idxs); - - // build some of the targets to get proofs for them - targets = TargetLeaves::from_indices(target_idxs, &receipts).unwrap(); - } - Err(e) => { - // Handle the error (e.g., by logging or panicking) - panic!("Failed to convert receipts: {}", e); - } - } - - // necessary to call this method to retain proofs - hb.root(); - - // verifies proof for retained targets - let proof = hb.take_proof_nodes(); - for target in targets { - assert_eq!( - verify_proof( - hb.root(), - target.nibbles.clone(), - Some(target.value.to_vec()), - proof - .clone() - .matching_nodes_sorted(&target.nibbles) - .iter() - .map(|(_, node)| node) - ), - Ok(()) - ); - } - } -} diff --git a/crates/forrestrie/src/lib.rs b/crates/forrestrie/src/lib.rs deleted file mode 100644 index 19785755..00000000 --- a/crates/forrestrie/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -pub mod beacon_block; -pub mod beacon_state; -pub mod execution_layer; diff --git a/forrestrie-examples/Cargo.toml b/forrestrie-examples/Cargo.toml deleted file mode 100644 index faf71d80..00000000 --- a/forrestrie-examples/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "forrestrie-examples" -version = "0.1.1" -edition = "2021" - -[dev-dependencies] -beacon-protos.workspace = true -bls.workspace = true -ethportal-api.workspace = true -firehose-client.workspace = true -firehose-protos = { path = "../firehose-protos" } -forrestrie = { path = "../forrestrie" } -futures.workspace = true -insta.workspace = true -merkle_proof.workspace = true -primitive-types.workspace = true -prost.workspace = true -prost-wkt.workspace = true -prost-wkt-types.workspace = true -reqwest = { workspace = true, features = ["json"] } -reth-primitives.workspace = true -reth-trie-common.workspace = true -serde = { workspace = true, features = ["derive"] } -snap = "1.1.1" -ssz_types = "0.8.0" -ethereum_ssz = "0.7.1" -ethereum_ssz_derive = "0.7.1" -serde_json.workspace = true -tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } -trin-validation = { git = "https://github.com/ethereum/trin.git", version = "0.1.0", tag = "v0.1.0-alpha.51" } -tonic.workspace = true -tracing.workspace = true -tracing-subscriber = "0.3" -tree_hash = "0.6.0" -types.workspace = true diff --git a/forrestrie-examples/README.md b/forrestrie-examples/README.md deleted file mode 100644 index 2063021f..00000000 --- a/forrestrie-examples/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Forrestrie Examples - -Here's an example of how to run one of the examples in the `forrestrie-examples` crate: - -```terminal -cd crates/forrestrie-examples && cargo run -- --examples historical_state_roots_proof -``` - -Use environment variables to provide Firehose Ethereum and Firehose -Beacon providers of your choice. - -To do this, place a `.env` file in the root of `veemon`. See the -`.env.example` file in the root of this repository for what you'll need, -depending on your requirements. diff --git a/forrestrie-examples/assets/historical_batch-573-c847a969.ssz b/forrestrie-examples/assets/historical_batch-573-c847a969.ssz deleted file mode 100644 index 45631b2d..00000000 Binary files a/forrestrie-examples/assets/historical_batch-573-c847a969.ssz and /dev/null differ diff --git a/forrestrie-examples/examples/block_roots_only_proof.rs b/forrestrie-examples/examples/block_roots_only_proof.rs deleted file mode 100644 index 48af6f3c..00000000 --- a/forrestrie-examples/examples/block_roots_only_proof.rs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Inclusion Proofs for Block Roots Only -//! -//! For this test, we want to prove that a block_root is included in the `block_summary_root` -//! field of a [`HistoricalSummary`] from the [`BeaconState`] historical_summaries List. -//! A [`HistoricalSummary`] contains the roots of two Merkle trees, `block_summary_root` and -//! `state_summary_root`. -//! We are interested in the `block_summary_root` tree, whose leaves consists of the -//! [`BeaconBlockHeader`] roots for one era (8192 consecutive slots). -//! For this test, we are using the state at the first [`Slot`] of an era to build the proof. -//! We chose this [`Slot`] because it is the first [`Slot`] of an era, and all of the -//! [`BeaconBlockHeader`] roots needed to construct the [`HistoricalSummary`] for this era are -//! available in `state.block_roots`. - -use forrestrie::beacon_state::{HeadState, CAPELLA_START_ERA, HISTORY_TREE_DEPTH}; -use merkle_proof::verify_merkle_proof; -use types::{historical_summary::HistoricalSummary, MainnetEthSpec}; - -#[tokio::main] -async fn main() { - // You may need to update the slot being queried as the state data is updated. - // Multiply a recent era by 8192 to get the slot number. - const SLOT: u64 = 10182656; - let url = format!("https://www.lightclientdata.org/eth/v2/debug/beacon/states/{SLOT}"); - println!("Requesting state for slot {SLOT} ... (this can take a while!)"); - let response = reqwest::get(url).await.unwrap(); - let transition_state: HeadState = if response.status().is_success() { - let json_response: serde_json::Value = response.json().await.unwrap(); - serde_json::from_value(json_response).unwrap() - } else { - panic!("Request failed with status: {}", response.status()); - }; - - // There are 8192 slots in an era. 8790016 / 8192 = 1073. - let proof_era = transition_state.data().slot().as_usize() / 8192usize; - - // In this test we are using the `historical_summaries` (introduced in Capella) for - // verification, so we need to subtract the Capella start era to get the correct index. - let proof_era_index = proof_era - CAPELLA_START_ERA - 1; - - // We are going to prove that the block_root at index 4096 is included in the block_roots - // tree. - // This is an arbitrary choice just for test purposes. - let index = 4096usize; - - // Buffer of most recent 8192 block roots: - let block_root_at_index = *transition_state.data().block_roots().get(index).unwrap(); - - let proof = transition_state - .compute_block_roots_proof_only(index) - .unwrap(); - - // To verify the proof, we use the state from a later slot. - // The HistoricalSummary used to generate this proof is included in the historical_summaries - // list of this state. - let url = "https://www.lightclientdata.org/eth/v2/debug/beacon/states/head".to_string(); - println!("Requesting head state ... (this can take a while!)"); - let response = reqwest::get(url).await.unwrap(); - let state: HeadState = if response.status().is_success() { - let json_response: serde_json::Value = response.json().await.unwrap(); - serde_json::from_value(json_response).unwrap() - } else { - panic!("Request failed with status: {}", response.status()); - }; - - // The verifier retrieves the block_summary_root for the historical_summary and verifies the - // proof against it. - let historical_summary: &HistoricalSummary = state - .data() - .historical_summaries() - .unwrap() - .get(proof_era_index) - .unwrap(); - - let block_roots_summary_root = historical_summary.block_summary_root(); - - assert!( - verify_merkle_proof( - block_root_at_index, - &proof, - HISTORY_TREE_DEPTH, - index, - block_roots_summary_root - ), - "Merkle proof verification failed" - ); - - println!("Block roots only merkle proof verification succeeded"); -} diff --git a/forrestrie-examples/examples/block_roots_proofs.rs b/forrestrie-examples/examples/block_roots_proofs.rs deleted file mode 100644 index 16bde5fa..00000000 --- a/forrestrie-examples/examples/block_roots_proofs.rs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Block Roots Proofs -//! -//! We want to prove that a block_root is included in a [`HistoricalSummary`] from the [`BeaconState`] historical_summaries List. -//! A [`HistoricalSummary`] contains the roots of two Merkle trees, block_summary_root and state_summary root. -//! We are interested in the block_summary tree, whose leaves consists of the [`BeaconBlockHeader`] roots for one era (8192 consecutive slots). -//! For example, we could have used the state at [`Slot`] 8790016, which is the first [`Slot`] of era 1073, to build the proof. -//! Because it is the first [`Slot`] of an era, all of the [`BeaconBlockHeader`] roots needed to construct the -//! [`HistoricalSummary`] for this era are available in state.block_roots. -//! The last block root in the `block_roots` buffer is the block root of the previous block. - -use forrestrie::beacon_state::{ - HeadState, CAPELLA_START_ERA, HISTORICAL_SUMMARY_TREE_DEPTH, SLOTS_PER_HISTORICAL_ROOT, -}; -use merkle_proof::verify_merkle_proof; -use tree_hash::TreeHash; -use types::MainnetEthSpec; -#[tokio::main] -async fn main() { - // You may need to update the slot being queried as the state data is updated. - // Multiply a recent era by 8192 to get the slot number. - const SLOT: u64 = 10182656; - let url = format!("https://www.lightclientdata.org/eth/v2/debug/beacon/states/{SLOT}"); - println!("Requesting state for slot {SLOT} ... (this can take a while!)"); - let response = reqwest::get(url).await.unwrap(); - let transition_state: HeadState = if response.status().is_success() { - let json_response: serde_json::Value = response.json().await.unwrap(); - serde_json::from_value(json_response).unwrap() - } else { - panic!("Request failed with status: {}", response.status()); - }; - - // There are 8192 slots in an era. - let proof_era = transition_state.data().slot().as_usize() / SLOTS_PER_HISTORICAL_ROOT; - - // In this test we are using the historical_summaries (introduced in Capella) for verification, - // so we need to subtract the Capella start era to get the correct index. - let proof_era_index = proof_era - CAPELLA_START_ERA - 1; - - // We are going to prove that the block_root at index 4096 is included in the block_roots tree. - // This is an arbitrary choice just for test purposes. - let index = 4096usize; - - // Buffer of most recent 8192 block roots: - let block_root_at_index = *transition_state.data().block_roots().get(index).unwrap(); - - assert!( - transition_state.block_roots_contain_entire_era().unwrap(), - "Block roots buffer does not contain an entire era" - ); - - let proof = transition_state.compute_block_roots_proof(index).unwrap(); - - // To verify the proof, we use the state from a later slot. - // The HistoricalSummary used to generate this proof is included in the historical_summaries list of this state. - let url = "https://www.lightclientdata.org/eth/v2/debug/beacon/states/head".to_string(); - println!("Requesting head state ... (this can take a while!)"); - let response = reqwest::get(url).await.unwrap(); - let state: HeadState = if response.status().is_success() { - let json_response: serde_json::Value = response.json().await.unwrap(); - serde_json::from_value(json_response).unwrap() - } else { - panic!("Request failed with status: {}", response.status()); - }; - - // The verifier retrieves the block_summary_root for the historical_summary and verifies the proof against it. - let historical_summary = state - .data() - .historical_summaries() - .unwrap() - .get(proof_era_index) - .unwrap(); - - let historical_summary_root = historical_summary.tree_hash_root(); - - assert!( - verify_merkle_proof( - block_root_at_index, - &proof, - HISTORICAL_SUMMARY_TREE_DEPTH, - index, - historical_summary_root - ), - "Merkle proof verification failed" - ); - - println!("Block roots proof verified successfully"); -} diff --git a/forrestrie-examples/examples/empty_slot_hashes.rs b/forrestrie-examples/examples/empty_slot_hashes.rs deleted file mode 100644 index 3a4cb2f1..00000000 --- a/forrestrie-examples/examples/empty_slot_hashes.rs +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Empty Slot Block Hashes -//! -//! This example demonstrates that empty Beacon slots - slots with no execution block - -//! are represented in the [`BeaconState`] as duplicates of the previous full Beacon slot block hash. - -use std::collections::{BTreeMap, HashSet}; - -use forrestrie::beacon_state::{HeadState, CAPELLA_START_ERA, SLOTS_PER_HISTORICAL_ROOT}; -use primitive_types::H256; -use types::MainnetEthSpec; - -#[tokio::main] -async fn main() { - // This slot was chosen because it is the first slot of an era (and an epoch), - // which we demonstrate by showing that the slot number (see below) modulo 8192 is 0. - // You may need to update the slot being queried as the state data is updated. - // Multiply a recent era by 8192 to get the slot number. - const SLOT: u64 = 10182656; - let url = format!("https://www.lightclientdata.org/eth/v2/debug/beacon/states/{SLOT}"); - println!("Requesting state for slot {SLOT} ... (this can take a while!)"); - let response = reqwest::get(url).await.unwrap(); - let state: HeadState = if response.status().is_success() { - let json_response: serde_json::Value = response.json().await.unwrap(); - serde_json::from_value(json_response).unwrap() - } else { - panic!("Request failed with status: {}", response.status()); - }; - - let slot = state.data().slot().as_usize(); - - // Every 8192 slots, the era increments by 1, and (after Capella) the historical summaries buffer is updated. - let current_era = slot / SLOTS_PER_HISTORICAL_ROOT; - assert_eq!(slot % SLOTS_PER_HISTORICAL_ROOT, 0); - - // The historical summaries buffer is updated every 8192 slots, from the start of the Capella era. - let num_historical_summaries = state.data().historical_summaries().unwrap().len(); - assert_eq!((current_era - num_historical_summaries), CAPELLA_START_ERA); - - let block_roots = state.data().block_roots().to_vec(); - - // Block roots buffer contains duplicates. - let block_roots_set: HashSet<&H256, std::hash::RandomState> = - HashSet::from_iter(block_roots.iter()); - assert_ne!(block_roots_set.len(), block_roots.len()); - - let duplicate_block_roots_lookup_table = state - .data() - .block_roots() - .to_vec() - .iter() - .enumerate() - // Using BTreeMaps for deterministic order. - .fold(BTreeMap::>::new(), |mut acc, (i, root)| { - acc.entry(*root).or_default().push(i); - acc - }) - // Remove non-duplicate block roots. - .into_iter() - .filter(|(_, indices)| indices.len() > 1) - .collect::>>(); - - // The block roots buffer contains duplicates that are consecutive. - insta::assert_debug_snapshot!(duplicate_block_roots_lookup_table, @r###" - { - 0x0b181ac43241327561b0c9cb4e070f72989581fb49ed26f5435bef997c42ebf5: [ - 991, - 992, - ], - 0x0e0639696be97e597e0c7ee1acfff59f165ba9f5945e729633cff21c0c635848: [ - 6467, - 6468, - ], - 0x0e26c5f8321a19c33e0e190ad7f72d0c135ab8ba7104cd8a0473242c7064db18: [ - 6688, - 6689, - ], - 0x0f40bc4698ba17aa95faade9e069e95456bcffd3e966c1fb0510df803038df48: [ - 3396, - 3397, - ], - 0x0f57d09bbf7b6a20a76ce3683e555ca86e7a1c38a3d1414bc6afb76894c460c1: [ - 5265, - 5266, - ], - 0x1473d4d768a680e9e61e7c6904df1a798545e14295b664bd7be06951140e4650: [ - 7162, - 7163, - ], - 0x1e30ab8ebd808669cca453279c2d89ed1965c3920f33e2715aca2d3e2756722a: [ - 1162, - 1163, - ], - 0x287c4b53d1b7be5c6c1e42ee596cb6b2803dcdaf17821798f21175b1a7ded5a8: [ - 7543, - 7544, - ], - 0x2cf6c52b3a63f73d8393734ce77540b0ae4914f403128ef2e0b9dcabb36dd443: [ - 5087, - 5088, - ], - 0x2d8144f651ad2c0864d586f43c446570177ae0dc219f15ff9469dfd05fc8de6e: [ - 6465, - 6466, - ], - 0x3514b0a08790ff1047150893234575c86705b9b98ca0a0a109a39da2216a3a4f: [ - 2432, - 2433, - ], - 0x3e12555313ed8ad02e60bdf78688892a54e6e02498fffd5a2ed0dbfc38d97db5: [ - 2532, - 2533, - ], - 0x41eb012e02e62f6e31bf742c709a3e9ec654b9258ff86b2061d124f0839a0188: [ - 1799, - 1800, - ], - 0x4294e233c2ca1055127d1373ffaf96f91386a187f888c9de4742ea79ff2e67f0: [ - 3958, - 3959, - ], - 0x498bb1ca0923c4a56e094e2f6fe2243dff4a9766f317309da7c57be5940f5a56: [ - 124, - 125, - ], - 0x4ca5d89aaa6de795d3432fda64bbecf4aed5fa0966193e77aa1c98599fb08ebe: [ - 7807, - 7808, - ], - 0x4f497aaff8a60ec338bc3fd19e0089d3cfa922bd093f767e6ba34ce0ec0e69e9: [ - 6175, - 6176, - ], - 0x515519a00556388934dd24fd9c528b26af2dce885c4cd3d5b0120b3939808ddc: [ - 4410, - 4411, - ], - 0x56cf582ed2d994dc15c4e4e49cea4e013e5ccb825997958255ebff9a9c70a126: [ - 4127, - 4128, - ], - 0x59ef61abc9d0dee4a8c19d3f636876bc38aa56559bf29315b26ccfd66da73aa9: [ - 1510, - 1511, - ], - 0x5db5cee0a5a63e6f20744bd2da116f0b7ff1346b6b6014cf847977bd6036b17e: [ - 5297, - 5298, - ], - 0x5fe37ef18fdaee618fb869760b20be5f7d04334e93f356b00e313f3f2d4b5eb6: [ - 3743, - 3744, - ], - 0x6808158ef68b250694ebc6cfbd90418a0182ac3d6e0560ad19212dc902a31c2f: [ - 1937, - 1938, - ], - 0x6820e4ea1e925a0198c67e40d4a066778898cd8b2e6fea4c32d7dccec7c548d6: [ - 7294, - 7295, - ], - 0x69dfd5cbd798a693d3deb17ae9bb6c0b075e50d0e414b710f58438dc2a54b51d: [ - 3540, - 3541, - ], - 0x6f0b738c363cc6739178e2c813dc47d7ff9aaef5cda7b838a964ff67aa626ab3: [ - 1667, - 1668, - ], - 0x6fec0abed7cbf72b3c0c0fb00db7fa7e78fdf55c7bc52804971e9997e8c37ef6: [ - 5439, - 5440, - ], - 0x71afc6470dd6ea94a1cfa95d91975d2b2d0efcf261bcf92a37eeb722a10907e5: [ - 1518, - 1519, - ], - 0x99254b3ae83a064a9dd91f68c60630e88727bd2989110a041fed6aacb0780239: [ - 3555, - 3556, - ], - 0x9c91fed096d21a383bf4cba7ee5213c68f5fb662225af74864450d45bdd93e01: [ - 6028, - 6029, - ], - 0xa89ca327b5d989c3066ea390053651a5f8b951829bda21257f7b33503e1c7abc: [ - 6240, - 6241, - ], - 0xaba1fa146c1665c2a3083987e55a9ae15dc04800d527ca98f2baf8692b96d5fd: [ - 7167, - 7168, - ], - 0xb077d4b158fa43c1ac54ee3d608d9430970d10cbc64a219b819fc279f7d3d3e0: [ - 3380, - 3381, - ], - 0xc4153799a620d470ced2bf02f0275f6353ec57be64f34bb06a0bc3a13423a9e3: [ - 5453, - 5454, - ], - 0xcebd2b3111fce7d8f9f1ddcf556d7ba54aa0999342184e7c58fa262131e94283: [ - 2894, - 2895, - ], - 0xd436e0dbe68b089f4dca99cac9ab4dc044b448c3569ff029c230d1539c643b93: [ - 1036, - 1037, - ], - 0xd7e5a02180a5116042684af1b180739609e2424bbb4deb0d030b023b23490438: [ - 2050, - 2051, - ], - 0xda13e985195e44855e08d5bd2d54ca6ac8f4cfaa5668526760d521aeaa9c4178: [ - 7478, - 7479, - ], - 0xde5d5a3b2f2da2b6482adcd4f61c6addbf45dfee24ff938931ac90e56c9e73a9: [ - 6430, - 6431, - ], - 0xdef43bbd5c642857fdbb5fdcf8e566c1e1dffbb543c3a29e8d606c25e60d2bf3: [ - 5491, - 5492, - ], - 0xf406504fad9ec2165294c51a47bf6d258c07f7db212b897ebe5611153fbfcb88: [ - 3839, - 3840, - ], - 0xfe5b350eb4ae790d3c14db582269d3edea28f803a76983ababbf31926a7c9ff3: [ - 6784, - 6785, - ], - } - "###); - - println!("Empty slot block hashes example completed successfully"); -} diff --git a/forrestrie-examples/examples/fetch_and_verify_block.rs b/forrestrie-examples/examples/fetch_and_verify_block.rs deleted file mode 100644 index 59067700..00000000 --- a/forrestrie-examples/examples/fetch_and_verify_block.rs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Beacon Block and Header Root Consistency -//! -//! In Ethereum's Beacon chain, the beacon block root and the block header root should match -//! for any given block, ensuring data integrity. This can be verified by computing the root -//! of a block and comparing it to the root of its header. -//! -//! For example, for slot 8786333, the block's root can be computed using the `canonical_root` method -//! from [`TreeHash`]: -//! -//! ```rust -//! let block_root = block.canonical_root(); -//! ``` -//! -//! Similarly, the block header root is derived as follows: -//! -//! ```rust -//! let block_header_root = block.block_header().tree_hash_root(); -//! ``` -//! -//! Both of these root hashes should be identical, indicating that the block's root hash -//! correctly represents the block header: -//! -//! ```rust -//! assert_eq!(block_root, block_header_root); -//! ``` -//! -use beacon_protos::Block as FirehoseBeaconBlock; -use firehose_client::{Chain, FirehoseClient}; -use tree_hash::TreeHash; -use types::{BeaconBlock, MainnetEthSpec}; - -#[tokio::main] -async fn main() { - let mut beacon_client = FirehoseClient::new(Chain::Beacon); - let response = beacon_client.fetch_block(8786333).await.unwrap().unwrap(); - let beacon_block = FirehoseBeaconBlock::try_from(response.into_inner()).unwrap(); - - let lighthouse_beacon_block = BeaconBlock::::try_from(beacon_block).unwrap(); - - insta::assert_debug_snapshot!(lighthouse_beacon_block.slot(), @ - "Slot(8786333)"); - - let block_root = lighthouse_beacon_block.canonical_root(); - - // See, for example, https://beaconcha.in/slot/8786333 and https://beaconscan.com/slot/8786333 - insta::assert_debug_snapshot!(block_root, @"0x063d4cf1a4f85d228d9eae17a9ab7df8b13de51e7a1988342a901575cce79613"); - - let block_header = lighthouse_beacon_block.block_header(); - let block_header_root = block_header.tree_hash_root(); - - assert_eq!(block_root, block_header_root); - - // This is to show that block hash and block body hash are different. - let body = lighthouse_beacon_block.body_deneb().unwrap(); - let body_hash = body.tree_hash_root(); - insta::assert_debug_snapshot!(body_hash, @"0xc15e821344ce5b201e2938248921743da8a07782168456929c8cef9f25a4cb02"); - - println!("fetch_and_verify_block.rs done"); -} diff --git a/forrestrie-examples/examples/historical_state_roots_proof.rs b/forrestrie-examples/examples/historical_state_roots_proof.rs deleted file mode 100644 index b151f727..00000000 --- a/forrestrie-examples/examples/historical_state_roots_proof.rs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Historical and state roots proof -//! -//! This example demonstrates how to prove the inclusion of historical state roots in the beacon state. -use forrestrie::beacon_state::{HeadState, HISTORICAL_ROOTS_FIELD_INDEX, HISTORICAL_ROOTS_INDEX}; -use merkle_proof::verify_merkle_proof; -use types::{light_client_update::CURRENT_SYNC_COMMITTEE_PROOF_LEN, MainnetEthSpec}; - -#[tokio::main] -async fn main() { - let url = "https://www.lightclientdata.org/eth/v2/debug/beacon/states/head"; - println!("Requesting head state ... (this can take a while!)"); - let response = reqwest::get(url).await.unwrap(); - let mut state: HeadState = if response.status().is_success() { - let json_response: serde_json::Value = response.json().await.unwrap(); - serde_json::from_value(json_response).unwrap() - } else { - panic!("Request failed with status: {}", response.status()); - }; - - let proof = state - .compute_merkle_proof_for_historical_data(HISTORICAL_ROOTS_INDEX) - .unwrap(); - - let historical_roots_tree_hash_root = state.historical_roots_tree_hash_root(); - - let depth = CURRENT_SYNC_COMMITTEE_PROOF_LEN; - - let state_root = state.state_root().unwrap(); - - assert!( - verify_merkle_proof( - historical_roots_tree_hash_root, - &proof, - depth, - HISTORICAL_ROOTS_FIELD_INDEX, - state_root - ), - "Merkle proof verification failed" - ); - - println!("historical state roots proof verified successfully"); -} diff --git a/forrestrie-examples/examples/historical_summary_proof.rs b/forrestrie-examples/examples/historical_summary_proof.rs deleted file mode 100644 index 3314b998..00000000 --- a/forrestrie-examples/examples/historical_summary_proof.rs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Historical Summary Proof Given Historical Summaries Root -//! -//! This example demonstrates how to prove the inclusion of historical summaries in the beacon state. - -use forrestrie::beacon_state::{ - HeadState, HISTORICAL_SUMMARIES_FIELD_INDEX, HISTORICAL_SUMMARIES_INDEX, -}; -use merkle_proof::verify_merkle_proof; -use types::{light_client_update::CURRENT_SYNC_COMMITTEE_PROOF_LEN, MainnetEthSpec}; - -#[tokio::main] -async fn main() { - let url = "https://www.lightclientdata.org/eth/v2/debug/beacon/states/head".to_string(); - println!("Requesting head state ... (this can take a while!)"); - let response = reqwest::get(url).await.unwrap(); - let mut state: HeadState = if response.status().is_success() { - let json_response: serde_json::Value = response.json().await.unwrap(); - serde_json::from_value(json_response).unwrap() - } else { - panic!("Request failed with status: {}", response.status()); - }; - - let proof = state - .compute_merkle_proof_for_historical_data(HISTORICAL_SUMMARIES_INDEX) - .unwrap(); - - let historical_summaries_tree_hash_root = state.historical_summaries_tree_hash_root().unwrap(); - - let state_root = state.state_root().unwrap(); - - let depth = CURRENT_SYNC_COMMITTEE_PROOF_LEN; - - assert!( - verify_merkle_proof( - historical_summaries_tree_hash_root, - &proof, - depth, - HISTORICAL_SUMMARIES_FIELD_INDEX, - state_root - ), - "Merkle proof verification failed" - ); - - println!("historical summaries proof verified successfully"); -} diff --git a/forrestrie-examples/examples/match_ethereum_to_beacon.rs b/forrestrie-examples/examples/match_ethereum_to_beacon.rs deleted file mode 100644 index 063b8d74..00000000 --- a/forrestrie-examples/examples/match_ethereum_to_beacon.rs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Ethereum Block to Beacon Slot Lookup Example -//! -//! This example performs a binary search to find the corresponding Beacon chain -//! slot for a given Ethereum execution block number. The problem being addressed -//! is that, due to missed slots in the Beacon chain, Ethereum execution blocks -//! and Beacon chain slots are not always aligned. Therefore, finding the correct -//! Beacon slot that contains the execution block requires searching through -//! Beacon blocks until the execution block is located. -//! -//! ## Key Concepts -//! -//! - **Execution Block Number**: This refers to the Ethereum block number that -//! we're trying to locate within the Beacon chain. -//! - **Beacon Slot Number**: The slot number in the Beacon chain that contains -//! the corresponding Ethereum execution block. -//! - **Deneb Fork**: This is the Ethereum fork that the blocks in the example -//! are from. We can imagine using `const` values to represent the start slot -//! of the Deneb fork and other upgrades, as well as the offsets between Ethereum -//! and Beacon block numbers at different known points along the chain. -//! -//! ## Approach -//! -//! The example uses a binary search algorithm to locate the Beacon slot that -//! contains the execution block. It starts with a search range defined by -//! `DENEB_START_SLOT` and an upper bound based on an estimated offset. -//! During each iteration of the search, the Beacon block is fetched, and its -//! execution payload is examined to check if it contains the target Ethereum -//! block number. The search range is adjusted based on the result of this -//! comparison until the correct Beacon slot is found. -//! - -use beacon_protos::{Block as FirehoseBeaconBlock, Body}; -use firehose_client::{Chain, FirehoseClient}; -use forrestrie::beacon_state::ETHEREUM_BEACON_DENEB_OFFSET; -use std::cmp::Ordering::*; -use tracing::info; -use tracing_subscriber::FmtSubscriber; - -/// This block relates to the slot represented by [`BEACON_SLOT_NUMBER`]. -/// The execution block is in the execution payload of the Beacon block in slot [`BEACON_SLOT_NUMBER`]. -const EXECUTION_BLOCK_NUMBER: u64 = 20759937; -/// This slot is the slot of the Beacon block that contains the execution block with [`EXECUTION_BLOCK_NUMBER`]. -#[allow(unused)] -const BEACON_SLOT_NUMBER: u64 = 9968872; // Beacon slot 9968872 pairs with Ethereum block 20759937. - -const IMAGINARY_CURRENT_SLOT: u64 = 10_000_000; - -#[tokio::main] -async fn main() { - let subscriber = FmtSubscriber::builder() - .with_max_level(tracing::Level::INFO) - .finish(); - tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); - - let mut beacon_client = FirehoseClient::new(Chain::Beacon); - - let mut low = EXECUTION_BLOCK_NUMBER - ETHEREUM_BEACON_DENEB_OFFSET as u64; - let mut high = IMAGINARY_CURRENT_SLOT; - - let mut guesses = 0; - - while low <= high { - guesses += 1; - - let mid = low + (high - low) / 2; - - info!(guess = mid, "Current guess for Beacon slot"); - - let response = beacon_client.fetch_block(mid).await.unwrap().unwrap(); - let block = FirehoseBeaconBlock::try_from(response.into_inner()).unwrap(); - - let Some(Body::Deneb(body)) = &block.body else { - panic!("Unsupported block version!"); - }; - - let execution_payload = body.execution_payload.as_ref().unwrap(); - let block_number = execution_payload.block_number; - - match block_number.cmp(&EXECUTION_BLOCK_NUMBER) { - Less => low = mid + 1, - Greater => high = mid - 1, - Equal => { - info!( - beacon_slot = block.slot, - "Found matching Beacon block: {}!", block.slot - ); - break; - } - } - - if high == low || high == low + 1 { - if let Some(final_result) = try_final_fetches(low, high, &mut beacon_client).await { - println!( - "Found final result: matching execution block at Beacon slot: {}", - final_result - ); - break; - } - } - } - info!(guesses, "Guesses"); -} - -/// Helper function to fetch both `low` and `high` Beacon slots when binary search is down to two options -async fn try_final_fetches(low: u64, high: u64, client: &mut FirehoseClient) -> Option { - for slot in &[low, high] { - let response = client.fetch_block(*slot).await.unwrap().unwrap(); - - let block = FirehoseBeaconBlock::try_from(response.into_inner()).unwrap(); - - let Some(Body::Deneb(body)) = &block.body else { - return None; - }; - - let execution_payload = body.execution_payload.as_ref().unwrap(); - - if execution_payload.block_number == EXECUTION_BLOCK_NUMBER { - return Some(block.slot); - } - } - None -} diff --git a/forrestrie-examples/examples/receipts_proof.rs b/forrestrie-examples/examples/receipts_proof.rs deleted file mode 100644 index e8c7181e..00000000 --- a/forrestrie-examples/examples/receipts_proof.rs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Receipts proof given an EL block's `receipt_root` -//! -//! This example shows how to generate an inclusion proof for a set of receipts of an EL block. -//! - -use firehose_client::{Chain, FirehoseClient}; -use firehose_protos::{EthBlock as Block, FullReceipt}; -use forrestrie::execution_layer::{build_trie_with_proofs, TargetLeaves}; -use reth_primitives::ReceiptWithBloom; -use reth_trie_common::proof::verify_proof; - -const EXECUTION_BLOCK_NUMBER: u64 = 20759937; - -#[tokio::main] -async fn main() { - let mut eth1_client = FirehoseClient::new(Chain::Ethereum); - let response = eth1_client - .fetch_block(EXECUTION_BLOCK_NUMBER) - .await - .unwrap() - .unwrap(); - let eth1_block: Block = Block::try_from(response.into_inner()).unwrap(); - - let receipts: Vec = eth1_block.full_receipts().unwrap(); - - let receipts_with_bloom: Vec = receipts - .iter() - .map(|full_receipt| full_receipt.get_receipt_wb().clone()) - .collect(); - - // These are the indexes of receipts for which proofs need to be generated - let target_idxs = &[1, 2, 3]; - let targets = TargetLeaves::from_indices(target_idxs, &receipts_with_bloom).unwrap(); - let mut hb = build_trie_with_proofs(&receipts_with_bloom, target_idxs); - - // produces the root, which matches the root of the blocks. - // hb.root() also calculates the proofs and store them in the HashBuilder. - let root = hb.root(); - - let calc_root = eth1_block.calculate_receipt_root(); - println!("roots: {:?}, {:?}", root, calc_root); - - // proofs can be taken and sorted, so each proof matches one of the target. - // each proof of a specific target receipt is provided in `take_proof_nodes()` - // and can be stored or verified singularly - let proof = hb.take_proof_nodes(); - for target in targets { - let _verification = verify_proof( - hb.root(), - target.nibbles.clone(), - Some(target.value.to_vec()), - proof - .clone() - .matching_nodes_sorted(&target.nibbles) - .iter() - .map(|(_, node)| node), - ); - } -} diff --git a/forrestrie-examples/examples/single_block_post_merge_pre_capella_proof.rs b/forrestrie-examples/examples/single_block_post_merge_pre_capella_proof.rs deleted file mode 100644 index 0a87c88b..00000000 --- a/forrestrie-examples/examples/single_block_post_merge_pre_capella_proof.rs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! Proof for single block to be part of an era of beacon blocks using the [`HistoricalBatch`]. -//! -//! Notice that A [`HistoricalBatch`]` isn't an accumulator, it is a list of block_roots and state_roots -//! So each root in the [`HistoricalRootsAccumulator`] corresponds to hash_tree_root(historical_batch). -//! The batch is used to verify era against the accumulator. A block can be verified against an -//! [`HistoricalBatch`], hence chaining the proofs -use std::fs; - -use ethportal_api::consensus::beacon_state::HistoricalBatch; - -use ssz::Decode; -use trin_validation::{ - historical_roots_acc::HistoricalRootsAccumulator, merkle::proof::verify_merkle_proof, -}; - -#[tokio::main] -async fn main() { - // Load a historical batch. - // A historical batch has to be generated from beacon blocks or retrieved - // from some source that already calculated these - let bytes = - fs::read("./crates/forrestrie-examples/assets/historical_batch-573-c847a969.ssz").unwrap(); - let hist_batch = HistoricalBatch::from_ssz_bytes(&bytes).unwrap(); - - // check if my block_root is inside the HistoricalBatch - - // construct proof from historical batch - // In this example a slot that is inside the `HistoricalBatch` - // was picked: https://beaconcha.in/slot/4685828 - // NOTICE: we can also use the block roots themselves inside the the HistoricalBatch - // to figure out the slot by using the beacon chain explorer, for example: - // https://beaconcha.in/slot/58bbce808c399069fdd3e02e7906cd382ba8ffac8c1625a9d801ffa6a4120c98 - const EPOCH_SIZE: i32 = 8192; - let slot = 4685828; - let historical_root_index: i32 = slot % EPOCH_SIZE; - let historical_roots_proof = - hist_batch.build_block_root_proof((historical_root_index as u32).into()); - - // just checking if the rot macthes - let block_root = hist_batch.block_roots[historical_root_index as usize]; - - // The historical root we are getting: - println!("root: {:?}, index, {:?}", block_root, historical_root_index); - - // // verify the proof - let hist_acc = HistoricalRootsAccumulator::default(); - let block_root_index = slot % EPOCH_SIZE; - let gen_index = 2 * EPOCH_SIZE + block_root_index; - let historical_root_index = slot / EPOCH_SIZE; - let historical_root = hist_acc.historical_roots[historical_root_index as usize]; - - let result = verify_merkle_proof( - block_root, - &historical_roots_proof, - 14, - gen_index as usize, - historical_root, - ); - - println!("result of verifying proof: {:?}", result); -} diff --git a/forrestrie-examples/examples/single_execution_block.rs b/forrestrie-examples/examples/single_execution_block.rs deleted file mode 100644 index cbebc77d..00000000 --- a/forrestrie-examples/examples/single_execution_block.rs +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Prove Inclusion of a Single Execution Layer Block in the Canonical History of the Blockchain -//! -//! This example demonstrates how to prove the inclusion of a single execution layer block in the canonical -//! history of the blockchain. -//! -//! This method includes the following proofs: -//! 1. The block hash of the Ethereum block matches the hash in the block header. -//! 2. The block is the block it says it is, calculating its tree hash root matches the hash in the `root` -//! field of the block. -//! 3. The Beacon block's Execution Payload matches the Ethereum block. -//! 4. Reproducing the block root for the "era", or 8192 slots, of a Beacon block's slot by streaming 8192 -//! Beacon blocks from the Beacon chain. -//! 5. Calculating the merkle proof of the block's inclusion in the block roots of the historical summary for -//! the given era. -//! -//! We use a fork of [`lighthouse`](https://github.com/sigp/lighthouse)'s [`types`] that allows us to access -//! the `block_summary_root` of the [`HistoricalSummary`]. -//! -//! While this example demonstrates verifying block 20759937 on the execution layer, you could also use the -//! same method to prove the inclusion of an entire era of 8192 blocks, since the method for verifying a single -//! block already includes streaming 8192 blocks for the era. And its the same 8192 blocks required to compute -//! the block roots tree hash root, which can then be compared to the tree hash root in the historical summary -//! for the era. -//! -use beacon_protos::{Block, BlockRoot, Body}; -use ethportal_api::Header; -use firehose_client::{Chain, FirehoseClient}; -use firehose_protos::EthBlock; -use forrestrie::{ - beacon_block::{ - HistoricalDataProofs, BEACON_BLOCK_BODY_PROOF_DEPTH, EXECUTION_PAYLOAD_FIELD_INDEX, - }, - beacon_state::{ - compute_block_roots_proof_only, HeadState, CAPELLA_START_ERA, HISTORY_TREE_DEPTH, - SLOTS_PER_HISTORICAL_ROOT, - }, -}; -use futures::StreamExt; -use merkle_proof::verify_merkle_proof; -use tree_hash::TreeHash; -use types::{ - historical_summary::HistoricalSummary, light_client_update::EXECUTION_PAYLOAD_INDEX, - BeaconBlock, BeaconBlockBody, BeaconBlockBodyDeneb, ExecPayload, Hash256, MainnetEthSpec, -}; - -/// This block relates to the slot represented by [`BEACON_SLOT_NUMBER`]. -/// The execution block is in the execution payload of the Beacon block in slot [`BEACON_SLOT_NUMBER`]. -const EXECUTION_BLOCK_NUMBER: u64 = 20759937; -/// This slot is the slot of the Beacon block that contains the execution block with [`EXECUTION_BLOCK_NUMBER`]. -const BEACON_SLOT_NUMBER: u64 = 9968872; // <- 9968872 pairs with 20759937 -/// The URL to fetch the head state of the Beacon chain. -const LIGHT_CLIENT_DATA_URL: &str = - "https://www.lightclientdata.org/eth/v2/debug/beacon/states/head"; - -#[tokio::main] -async fn main() { - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Get the head state of the Beacon chain from a Beacon API provider. - let state_handle = tokio::spawn(async move { - let url = LIGHT_CLIENT_DATA_URL.to_string(); - println!("Requesting head state ... (this can take a while!)"); - let response = reqwest::get(url).await.unwrap(); - let head_state: HeadState = response.json().await.unwrap(); - head_state - }); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Get the Ethereum block. - let mut eth1_client = FirehoseClient::new(Chain::Ethereum); - let response = eth1_client - .fetch_block(EXECUTION_BLOCK_NUMBER) - .await - .unwrap() - .unwrap(); - let eth1_block = EthBlock::try_from(response.into_inner()).unwrap(); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // And get the Beacon block. - let mut beacon_client = FirehoseClient::new(Chain::Beacon); - let response = beacon_client - .fetch_block(BEACON_SLOT_NUMBER) - .await - .unwrap() - .unwrap(); - let beacon_block = Block::try_from(response.into_inner()).unwrap(); - assert_eq!(beacon_block.slot, BEACON_SLOT_NUMBER); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Confirm that the block hash of the Ethereum block matches the hash in the block header. - let block_header = Header::try_from(ð1_block).unwrap(); - let eth1_block_hash = block_header.hash(); - assert_eq!(eth1_block_hash.as_slice(), ð1_block.hash); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Convert the Beacon block to a Lighthouse `BeaconBlock`. This allows us to use Lighthouse's - // implementation of the `TreeHash` trait to calculate the root of the Beacon block, which we - // use to verify that the block is the block it says it is, i.e., the hash value in the `root` - // field of the block matches the calculated root, which we calculate using the method implemented - // on the `BeaconBlock` struct in Lighthouse. - let lighthouse_beacon_block = BeaconBlock::::try_from(beacon_block.clone()) - .expect("Failed to convert Beacon block to Lighthouse BeaconBlock"); - - // Check the root of the Beacon block. This check shows that the calculation of the block root - // of the Beacon block matches the hash in the `root` field of the block we fetched over gRPC; - // the block is the block that it says it is. - let lighthouse_beacon_block_root = lighthouse_beacon_block.canonical_root(); - assert_eq!( - lighthouse_beacon_block_root.as_bytes(), - beacon_block.root.as_slice() - ); - let Some(Body::Deneb(body)) = beacon_block.body else { - panic!("Unsupported block version!"); - }; - let block_body: BeaconBlockBodyDeneb = body.try_into().unwrap(); - - // Confirm that the Beacon block's Execution Payload matches the Ethereum block we fetched. - assert_eq!( - block_body.execution_payload.block_number(), - EXECUTION_BLOCK_NUMBER - ); - - // Confirm that the Ethereum block matches the Beacon block's Execution Payload. - assert_eq!( - block_body - .execution_payload - .block_hash() - .into_root() - .as_bytes(), - eth1_block_hash.as_slice() - ); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Confirm that the Execution Payload is included in the Beacon block. - let block_body_hash = block_body.tree_hash_root(); - let execution_payload = &block_body.execution_payload; - let execution_payload_root = execution_payload.tree_hash_root(); - let body = BeaconBlockBody::from(block_body.clone()); - let proof = body.compute_merkle_proof(EXECUTION_PAYLOAD_INDEX).unwrap(); - let depth = BEACON_BLOCK_BODY_PROOF_DEPTH; - assert!(verify_merkle_proof( - execution_payload_root, - &proof, - depth, - EXECUTION_PAYLOAD_FIELD_INDEX, - block_body_hash - )); - - // The era of the block's slot. - // This is also the index of the historical summary containing the block roots for this era. - let era = lighthouse_beacon_block.slot().as_usize() / SLOTS_PER_HISTORICAL_ROOT; - - println!("Requesting 8192 blocks for the era... (this takes a while)"); - let num_blocks = SLOTS_PER_HISTORICAL_ROOT as u64; - let mut stream = beacon_client - .stream_blocks::((era * SLOTS_PER_HISTORICAL_ROOT) as u64, num_blocks) - .await - .unwrap(); - let mut block_roots: Vec = Vec::with_capacity(SLOTS_PER_HISTORICAL_ROOT); - while let Some(block) = stream.next().await { - let root = BlockRoot::try_from(block).unwrap(); - block_roots.push(root.0); - } - assert_eq!(block_roots.len(), SLOTS_PER_HISTORICAL_ROOT); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // The index of the block in the complete era of block roots. - // Beacon chain slot numbers are zero-based; genesis slot is 0. - // We need this to calculate the merkle inclusion proof later. - // If this is the first/last block of the era, the index is 0/8191. - let index = lighthouse_beacon_block.slot().as_usize() % SLOTS_PER_HISTORICAL_ROOT; - // Compute the proof of the block's inclusion in the block roots. - let proof = compute_block_roots_proof_only::(&block_roots, index).unwrap(); - // To get the correct index, we need to subtract the Capella start era. - // `HistoricalSummary` was introduced in Capella and the block we're proving inclusion for is in - // the post-Capella era. - // For pre-Capella states, we would use the same method, only using the historical_roots field. - let proof_era = era - CAPELLA_START_ERA; - - let head_state = state_handle.await.unwrap(); - let historical_summary: &HistoricalSummary = head_state - .data() - .historical_summaries() - .unwrap() - .get(proof_era) - .unwrap(); - let block_roots_tree_hash_root = historical_summary.block_summary_root(); - assert_eq!(proof.len(), HISTORY_TREE_DEPTH); - // Verify the proof. - assert!( - verify_merkle_proof( - lighthouse_beacon_block_root, // the root of the block - &proof, // the proof of the block's inclusion in the block roots - HISTORY_TREE_DEPTH, // the depth of the block roots tree - index, // the index of the block in the era - block_roots_tree_hash_root // The root of the block roots - ), - "Merkle proof verification failed" - ); - println!("All checks passed!"); -} diff --git a/forrestrie-examples/examples/verify-era.rs b/forrestrie-examples/examples/verify-era.rs deleted file mode 100644 index 92f29020..00000000 --- a/forrestrie-examples/examples/verify-era.rs +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! In this example, we verify a complete era of both beacon blocks and execution blocks. -//! We first fetch a complete era of beacon blocks (8192 beacon blocks), compute the associated historical summary and -//! compare it against the historical summary from a current consensus stated. We also extract the -//! execution block headers and block numbers from the beacon blocks. We then fetch the execution -//! blocks using the extracted block numbers and verify the execution block data against the -//! extracted block headers. - -use beacon_protos::Block; -use ethportal_api::Header; -use firehose_client::{Chain, FirehoseClient}; -use firehose_protos::EthBlock; -use forrestrie::beacon_state::{ - HeadState, CAPELLA_START_ERA, HISTORY_TREE_DEPTH, SLOTS_PER_HISTORICAL_ROOT, -}; -use futures::StreamExt; -use tree_hash::TreeHash; -use types::{ - historical_summary::HistoricalSummary, BeaconBlock, BeaconBlockBodyDeneb, ExecPayload, - MainnetEthSpec, Slot, -}; - -use merkle_proof::MerkleTree; - -/// This slot is the starting slot of the Beacon block era. -const BEACON_SLOT_NUMBER: u64 = 10436608; -/// The URL to fetch the head state of the Beacon chain. -const LIGHT_CLIENT_DATA_URL: &str = - "https://www.lightclientdata.org/eth/v2/debug/beacon/states/head"; - -#[tokio::main] -async fn main() { - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Get the head state of the Beacon chain from a Beacon API provider. - let state_handle = tokio::spawn(async move { - let url = LIGHT_CLIENT_DATA_URL.to_string(); - println!("Requesting head state ... (this can take a while!)"); - let response = reqwest::get(url).await.unwrap(); - let head_state: HeadState = response.json().await.unwrap(); - head_state - }); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Here we are going to fetch all of the beacon blocks for an era. - // We will verify that the blocks are correct by computing a block_summary_root from the beacon blocks and comparing it to the block_summary_root in the historical summary from the consensus state. - let mut beacon_client = FirehoseClient::new(Chain::Beacon); - - // The era of the block's slot. - // This is also the index of the historical summary containing the block roots for this era. - let era = BEACON_SLOT_NUMBER as usize / SLOTS_PER_HISTORICAL_ROOT; - - // Stream the blocks - println!("Requesting 8192 blocks for the era... (this takes a while)"); - let num_blocks = SLOTS_PER_HISTORICAL_ROOT as u64; - let mut stream = beacon_client - .stream_blocks::((era * SLOTS_PER_HISTORICAL_ROOT) as u64, num_blocks) - .await - .unwrap(); - - // We are going to store off the execution block numbers and hashes for later verification. - let mut execution_block_number_and_hash = Vec::with_capacity(SLOTS_PER_HISTORICAL_ROOT); - - // We are going to store off the beacon block roots and calculate the block_summary_root from them. - let mut beacon_block_roots = Vec::with_capacity(SLOTS_PER_HISTORICAL_ROOT); - - let mut idx = 0; - let mut prev_slot = Slot::new(0); - let mut push_parent_root = false; - while let Some(block) = stream.next().await { - // Get the exeuction block number and blockhash. - let lighthouse_beacon_block = BeaconBlock::::try_from(block.clone()) - .expect("Failed to convert Beacon block to Lighthouse BeaconBlock"); - let Some(beacon_protos::Body::Deneb(body)) = block.body else { - panic!("Unsupported block version!"); - }; - let block_body: BeaconBlockBodyDeneb = body.try_into().unwrap(); - let execution_block_number = block_body.execution_payload.block_number(); - let execution_block_hash = block_body.execution_payload.block_hash(); - execution_block_number_and_hash.push((execution_block_number, execution_block_hash)); - - // There are a few things going on here: - // 1. there is currently a bug in the Firehose API where if a slot does not have an execution payload (the slot was skipped), then Firehose simply repeats the previous beacon block. - // This is a problem because this means that we can't calculate the beacon block root for the skipped slot. - // As a workaround, whenever we see a repeated block (implying a skipped slot), we will skip processing that block and on the next block we will push the parent root to the beacon block roots. - // Assuming that the parent root is correct, then the block_summary_root will be correct. - // - // 2. We are going to check the consistency of the beacon chain by comparing the claimed parent root of the current block against the previous block's root, they should match. - // This helps us catch errors within the era. - - if idx > 0 { - // If there was a skipped slot, then we will skip processing the current block and push the parent root to the beacon block roots. - let curr_slot = lighthouse_beacon_block.as_deneb().unwrap().slot; - if curr_slot == prev_slot { - push_parent_root = true; - idx += 1; - println!("Slot skipped!"); - continue; - } - if push_parent_root { - let parent_root = lighthouse_beacon_block.as_deneb().unwrap().parent_root; - beacon_block_roots.push(parent_root); - push_parent_root = false; - } - - // Check the parent root of the current block against the previous block's root. - let prev_block_root = beacon_block_roots[idx - 1]; - let prev_block_root_from_block = - lighthouse_beacon_block.as_deneb().unwrap().parent_root; - if prev_block_root != prev_block_root_from_block { - println!("Slot {}", lighthouse_beacon_block.as_deneb().unwrap().slot); - panic!("Block root mismatch!"); - } - println!( - "Slot {} verified!", - lighthouse_beacon_block.as_deneb().unwrap().slot - ); - } - - // Store the beacon block root. - let beacon_block_root = lighthouse_beacon_block.tree_hash_root(); - beacon_block_roots.push(beacon_block_root); - idx += 1; - prev_slot = lighthouse_beacon_block.as_deneb().unwrap().slot; - } - - // Check that we have the correct number of blocks. - assert_eq!( - execution_block_number_and_hash.len(), - SLOTS_PER_HISTORICAL_ROOT - ); - assert_eq!(beacon_block_roots.len(), SLOTS_PER_HISTORICAL_ROOT); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Here is where we check that the historical summary from the consensus state matches the historical summary computed from the beacon blocks. - - // Caculate the block_summary_root from the beacon blocks. Note that the block_summary_root is a field in the HistoricalSummary. - let beacon_block_roots_tree_hash_root = - MerkleTree::create(&beacon_block_roots, HISTORY_TREE_DEPTH).hash(); - - // To get the correct index for the era's HistoricalSummary in the consensus state, we need to subtract the Capella start era. - // `HistoricalSummary` was introduced in Capella and the block we're proving inclusion for is in - // the post-Capella era. - // For pre-Capella states, we would use the same method, only using the historical_roots field. - let era_index = era - CAPELLA_START_ERA; - - // Get the historical summary for the era from the consensus state. - let head_state = state_handle.await.unwrap(); - let historical_summary: &HistoricalSummary = head_state - .data() - .historical_summaries() - .unwrap() - .get(era_index) - .unwrap(); - - let block_summary_root = historical_summary.block_summary_root(); - assert_eq!(beacon_block_roots_tree_hash_root, block_summary_root); - println!("Historical summary verified!"); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Now that we have a verified set of execution block headers (and block numbers) from the beacon blocks, we can fetch the execution blocks and verify them. - - for (number, blockhash) in execution_block_number_and_hash { - // Fetch execution blocks from the Firehose API. - let mut eth1_client = FirehoseClient::new(Chain::Ethereum); - let response = eth1_client.fetch_block(number).await.unwrap().unwrap(); - let eth1_block = EthBlock::try_from(response.into_inner()).unwrap(); - - // Confirm that the block hash of the Ethereum block matches the hash in the block header. - let block_header = Header::try_from(ð1_block).unwrap(); - let eth1_block_hash = block_header.hash(); - assert_eq!(eth1_block_hash.as_slice(), ð1_block.hash); - - // Confirm that the Ethereum block matches the Beacon block's Execution Payload. - // This is our first major check linking the exuction layer to the consensus layer. - assert_eq!(blockhash.into_root().as_bytes(), eth1_block_hash.as_slice()); - println!("Block number {} verified!", number); - } - - // At this point, we have checked that the complete era's beacon blocks are correct by comparing against a historical summary from the consensus state, - // and that the corresponding execution blocks are correct by comparing against the block headers from the verified beacon blocks. - // Assuming that all checks passed, then the extracted data has been verified. - println!("All checks passed!"); -} diff --git a/forrestrie-examples/examples/verify_block_inclusion_proof.rs b/forrestrie-examples/examples/verify_block_inclusion_proof.rs deleted file mode 100644 index a2ae87fd..00000000 --- a/forrestrie-examples/examples/verify_block_inclusion_proof.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! # Verify Block Inclusion Proof -//! -//! In Ethereum's Beacon Chain, execution layer payloads are included in the block body. -//! -//! This example demonstrates how to verify the inclusion proof of an execution payload -//! in a block body. -//! -//! For example, for block `20672593`, the execution payload root can be computed using the `tree_hash_root` method -//! from [`TreeHash`]: -//! -//! ```rust -//! let execution_payload_root = execution_payload.tree_hash_root(); -//! ``` -//! -//! Similarly, the block body root is derived as follows: -//! -//! ```rust -//! let block_body_hash = block_body.tree_hash_root(); -//! ``` -//! -//! The inclusion proof can be computed using the `compute_merkle_proof` method from [`BeaconBlockBody`]: -//! -//! ```rust -//! let proof = body.compute_merkle_proof(EXECUTION_PAYLOAD_INDEX).unwrap(); -//! ``` -//! -use beacon_protos::Block as FirehoseBeaconBlock; -use firehose_client::{Chain, FirehoseClient}; -use forrestrie::beacon_block::{ - HistoricalDataProofs, BEACON_BLOCK_BODY_PROOF_DEPTH, EXECUTION_PAYLOAD_FIELD_INDEX, -}; -use merkle_proof::verify_merkle_proof; -use tree_hash::TreeHash; -use types::{ - light_client_update::EXECUTION_PAYLOAD_INDEX, BeaconBlock, BeaconBlockBody, MainnetEthSpec, -}; - -#[tokio::main] -async fn main() { - // test_inclusion_proof_for_block_body_given_execution_payload - let mut beacon_client = FirehoseClient::new(Chain::Beacon); - - let response = beacon_client.fetch_block(20672593).await.unwrap().unwrap(); - - let block = FirehoseBeaconBlock::try_from(response.into_inner()).unwrap(); - - let beacon_block = BeaconBlock::::try_from(block).unwrap(); - - let execution_payload = beacon_block.body().execution_payload().unwrap(); - let execution_payload_root = execution_payload.tree_hash_root(); - - let block_body = beacon_block.body_deneb().unwrap(); - let block_body_hash = block_body.tree_hash_root(); - - let body = BeaconBlockBody::from(block_body.clone()); - let proof = body.compute_merkle_proof(EXECUTION_PAYLOAD_INDEX).unwrap(); - - let depth = BEACON_BLOCK_BODY_PROOF_DEPTH; - - assert_eq!(proof.len(), depth, "proof length should equal depth"); - - assert!(verify_merkle_proof( - execution_payload_root, - &proof, - depth, - EXECUTION_PAYLOAD_FIELD_INDEX, - block_body_hash - )); - - println!("verify_block_inclusion_proof.rs done"); -} diff --git a/forrestrie-examples/src/lib.rs b/forrestrie-examples/src/lib.rs deleted file mode 100644 index 3f733a7e..00000000 --- a/forrestrie-examples/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 diff --git a/forrestrie/CHANGELOG.md b/forrestrie/CHANGELOG.md deleted file mode 100644 index 5c0cfa0d..00000000 --- a/forrestrie/CHANGELOG.md +++ /dev/null @@ -1,8 +0,0 @@ -# Changelog - -## [0.1.1](https://github.com/semiotic-ai/veemon/compare/forrestrie-v0.1.0...forrestrie-v0.1.1) (2024-10-22) - - -### Bug Fixes - -* **forrestrie/examples:** substract capella start from index ([829c372](https://github.com/semiotic-ai/veemon/commit/829c3720eb99b5b0991c4e83e05876616cdfc168)) diff --git a/forrestrie/Cargo.toml b/forrestrie/Cargo.toml deleted file mode 100644 index 578dde6f..00000000 --- a/forrestrie/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "forrestrie" -version = "0.1.1" -edition = "2021" - -[lib] -name = "forrestrie" -path = "src/lib.rs" - -[dependencies] -alloy-rlp.workspace = true -alloy-primitives.workspace = true -bls.workspace = true -merkle_proof.workspace = true -primitive-types.workspace = true -reth-trie-common.workspace = true -reth-primitives.workspace = true -serde = { workspace = true, features = ["derive"] } -ssz_types.workspace = true -tree_hash = "0.6.0" -types.workspace = true - -[dev-dependencies] -rand.workspace = true -fake.workspace = true diff --git a/forrestrie/README.md b/forrestrie/README.md deleted file mode 100644 index f7d18240..00000000 --- a/forrestrie/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Forrestrie - -Library of types and methods for verifying post-merge Ethereum data. - -## Documentation - -- Notion doc on -[Post-merge Header Record Data Structure](https://www.notion.so/semiotic/Post-merge-header_record-data-structure-7290d03d356946188bdb9ac29366f510?pvs=4). -- [Beacon Chain `BeaconState` spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#beaconstate) -- [Beacon Chain `BeaconBlockBody` spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/deneb/beacon-chain.md#beaconblockbody) -- The [fork of `sigp/lighthouse`](https://github.com/semiotic-ai/lighthouse) we've been spiking. -- [Google Drive shared resources](https://drive.google.com/drive/folders/19QBMHZFAV7uo_Cu4RwLPTwGpBcQMd-hy), -including `head-state.json` used in `beacon_state.rs` tests. - -## Examples - -See [forrestrie-examples](./../forrestrie-examples/README.md)! - -## Protobuffers - -### [protos/type.proto](https://github.com/pinax-network/firehose-beacon/blob/main/proto/sf/beacon/type/v1/type.proto) - -Pinax's Firehose Beacon `Block` implementation. diff --git a/forrestrie/src/beacon_block.rs b/forrestrie/src/beacon_block.rs deleted file mode 100644 index b4ef04e3..00000000 --- a/forrestrie/src/beacon_block.rs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use serde::{Deserialize, Serialize}; -use tree_hash::TreeHash; -use types::{ - beacon_block_body::NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES, - light_client_update::{self, EXECUTION_PAYLOAD_INDEX}, - BeaconBlock, BeaconBlockBody, Error, EthSpec, ForkName, Hash256, MainnetEthSpec, -}; - -#[derive(Debug, Deserialize, Serialize)] -pub struct BlockWrapper { - pub version: String, - pub execution_optimistic: bool, - pub finalized: bool, - pub data: Data, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct Data { - pub message: BeaconBlock, -} - -/// Merkle proof depth for a `BeaconBlockBody` struct with 12 fields. -/// -/// The proof depth is determined by finding the smallest power of 2 that is -/// greater than or equal to the number of fields. In this case, the number of -/// fields is 12, which is between 8 (2^3) and 16 (2^4). -pub const BEACON_BLOCK_BODY_PROOF_DEPTH: usize = 4; - -/// The field corresponds to the index of the `execution_payload` field in the [`BeaconBlockBody`] struct: -/// . -pub const EXECUTION_PAYLOAD_FIELD_INDEX: usize = 9; - -pub trait HistoricalDataProofs { - fn compute_merkle_proof(&self, index: usize) -> Result, Error>; -} - -impl HistoricalDataProofs for BeaconBlockBody { - fn compute_merkle_proof(&self, index: usize) -> Result, Error> { - let field_index = match index { - index if index == EXECUTION_PAYLOAD_INDEX => index - .checked_sub(NUM_BEACON_BLOCK_BODY_HASH_TREE_ROOT_LEAVES) - .ok_or(Error::IndexNotSupported(index))?, - _ => return Err(Error::IndexNotSupported(index)), - }; - - let attestations_root = if self.fork_name() > ForkName::Electra { - self.attestations_electra()?.tree_hash_root() - } else { - self.attestations_base()?.tree_hash_root() - }; - - let attester_slashings_root = if self.fork_name() > ForkName::Electra { - self.attester_slashings_electra()?.tree_hash_root() - } else { - self.attester_slashings_base()?.tree_hash_root() - }; - - let mut leaves = vec![ - self.randao_reveal().tree_hash_root(), - self.eth1_data().tree_hash_root(), - self.graffiti().tree_hash_root(), - self.proposer_slashings().tree_hash_root(), - attester_slashings_root, - attestations_root, - self.deposits().tree_hash_root(), - self.voluntary_exits().tree_hash_root(), - ]; - - if let Ok(sync_aggregate) = self.sync_aggregate() { - leaves.push(sync_aggregate.tree_hash_root()) - } - - if let Ok(execution_payload) = self.execution_payload() { - leaves.push(execution_payload.tree_hash_root()) - } - - if let Ok(bls_to_execution_changes) = self.bls_to_execution_changes() { - leaves.push(bls_to_execution_changes.tree_hash_root()) - } - - if let Ok(blob_kzg_commitments) = self.blob_kzg_commitments() { - leaves.push(blob_kzg_commitments.tree_hash_root()) - } - - let depth = light_client_update::EXECUTION_PAYLOAD_PROOF_LEN; - let tree = merkle_proof::MerkleTree::create(&leaves, depth); - let (_, proof) = tree.generate_proof(field_index, depth)?; - - Ok(proof) - } -} diff --git a/forrestrie/src/beacon_state.rs b/forrestrie/src/beacon_state.rs deleted file mode 100644 index 49342c1b..00000000 --- a/forrestrie/src/beacon_state.rs +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use merkle_proof::MerkleTree; -use primitive_types::H256; -use serde::{Deserialize, Serialize}; -use std::sync::Arc; -use tree_hash::TreeHash; -use types::{ - historical_summary::HistoricalSummary, light_client_update, map_beacon_state_altair_fields, - map_beacon_state_base_fields, map_beacon_state_bellatrix_fields, - map_beacon_state_capella_fields, map_beacon_state_deneb_fields, - map_beacon_state_electra_fields, BeaconBlockHeader, BeaconState, BeaconStateAltair, - BeaconStateBase, BeaconStateBellatrix, BeaconStateCapella, BeaconStateDeneb, - BeaconStateElectra, BeaconStateError as Error, BitVector, Checkpoint, Epoch, Eth1Data, EthSpec, - ExecutionPayloadHeaderBellatrix, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, - ExecutionPayloadHeaderElectra, Fork, Hash256, List, ParticipationFlags, PendingAttestation, - PendingBalanceDeposit, PendingConsolidation, PendingPartialWithdrawal, Slot, SyncCommittee, - Validator, Vector, -}; - -/// The number of slots in an epoch. -pub const SLOTS_PER_EPOCH: usize = 32; -/// The number of slots in an era. -pub const SLOTS_PER_ERA: usize = SLOTS_PER_HISTORICAL_ROOT; -/// Slots are 0-indexed. -/// See, for example, `https://beaconcha.in/slot/0`. -pub const BEACON_GENESIS_SLOT: usize = 0; -/// See [Upgrading Ethereum](https://eth2book.info/capella/part4/history/) for more information. -pub const PHASE_0_START_EPOCH: usize = 0; -/// See [Upgrading Ethereum](https://eth2book.info/capella/part4/history/) for more information. -pub const ALTAIR_START_EPOCH: usize = 74240; -/// See [Upgrading Ethereum](https://eth2book.info/capella/part4/history/) for more information. -pub const BELLATRIX_START_EPOCH: usize = 144896; -/// See [Upgrading Ethereum](https://eth2book.info/capella/part4/history/) for more information. -pub const CAPELLA_START_EPOCH: usize = 194048; -/// See [Upgrading Ethereum](https://eth2book.info/capella/part4/history/) for more information. -/// The first slot number of the Deneb fork. -pub const CAPELLA_START_SLOT: usize = CAPELLA_START_EPOCH * SLOTS_PER_EPOCH; -/// The first era of the Deneb fork. -pub const CAPELLA_START_ERA: usize = - (CAPELLA_START_EPOCH * SLOTS_PER_EPOCH) / SLOTS_PER_HISTORICAL_ROOT; -/// -pub const DENEB_START_SLOT: usize = 8626176; -/// -pub const FIRST_EXECUTION_BLOCK_DENEB: usize = 19426587; -/// The offset between the Ethereum block number and the Beacon block number at the start of the Deneb fork, -/// i.e. the difference between the first execution block number in the Deneb fork and the start slot number of the Deneb fork. -pub const ETHEREUM_BEACON_DENEB_OFFSET: usize = FIRST_EXECUTION_BLOCK_DENEB - DENEB_START_SLOT; - -/// [`BeaconState`] `block_roots` vector has length [`SLOTS_PER_HISTORICAL_ROOT`] (See ), -/// the value of which is calculated uint64(2**13) (= 8,192) (See ) -pub const HISTORY_TREE_DEPTH: usize = 13; - -/// The historical roots tree (pre-Capella) and the historical summaries tree (post-Capella) have the same depth. -/// Both tree's root has the block_roots tree root and the state_roots tree root as children and so has one more layer than each of these trees. -pub const HISTORICAL_SUMMARY_TREE_DEPTH: usize = 14; - -/// Historical roots is a top-level field on [`BeaconState`], subtract off the generalized indices -// for the internal nodes. Result should be 7, the field offset of the committee in the [`BeaconState`]: -// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#beaconstate -pub const HISTORICAL_ROOTS_INDEX: usize = 39; - -/// Historical summaries is a top-level field on [`BeaconState`], subtract off the generalized indices -// for the internal nodes. Result should be 27, the field offset of the committee in the [`BeaconState`]: -// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#beaconstate -pub const HISTORICAL_SUMMARIES_INDEX: usize = 59; - -/// Index of `historical_roots` field in the [`BeaconState`] [struct](https://github.com/ethereum/annotated-spec/blob/master/phase0/beacon-chain.md#beaconstate). -pub const HISTORICAL_ROOTS_FIELD_INDEX: usize = 7; - -/// Index of `historical_summaries` field in the (post-Capella) [`BeaconState`] [struct](https://github.com/ethereum/annotated-spec/blob/master/capella/beacon-chain.md#beaconstate). -pub const HISTORICAL_SUMMARIES_FIELD_INDEX: usize = 27; - -/// The maximum number of block roots that can be stored in a [`BeaconState`]'s `block_roots` list. -pub const SLOTS_PER_HISTORICAL_ROOT: usize = 8192; - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct HeadState { - version: String, - execution_optimistic: bool, - data: BeaconState, -} - -impl HeadState { - pub fn compute_merkle_proof_for_historical_data( - &self, - index: usize, - ) -> Result, Error> { - // 1. Convert generalized index to field index. - let field_index = match index { - HISTORICAL_ROOTS_INDEX | HISTORICAL_SUMMARIES_INDEX => index - .checked_sub(self.data.num_fields_pow2()) - .ok_or(Error::IndexNotSupported(index))?, - _ => return Err(Error::IndexNotSupported(index)), - }; - - // 2. Get all `BeaconState` leaves. - let mut leaves = vec![]; - #[allow(clippy::arithmetic_side_effects)] - match &self.data { - BeaconState::Base(state) => { - map_beacon_state_base_fields!(state, |_, field| { - leaves.push(field.tree_hash_root()); - }); - } - BeaconState::Altair(state) => { - map_beacon_state_altair_fields!(state, |_, field| { - leaves.push(field.tree_hash_root()); - }); - } - BeaconState::Bellatrix(state) => { - map_beacon_state_bellatrix_fields!(state, |_, field| { - leaves.push(field.tree_hash_root()); - }); - } - BeaconState::Capella(state) => { - map_beacon_state_capella_fields!(state, |_, field| { - leaves.push(field.tree_hash_root()); - }); - } - BeaconState::Deneb(state) => { - map_beacon_state_deneb_fields!(state, |_, field| { - leaves.push(field.tree_hash_root()); - }); - } - BeaconState::Electra(state) => { - map_beacon_state_electra_fields!(state, |_, field| { - leaves.push(field.tree_hash_root()); - }); - } - }; - - // 3. Make deposit tree. - // Use the depth of the `BeaconState` fields (i.e. `log2(32) = 5`). - let depth = light_client_update::CURRENT_SYNC_COMMITTEE_PROOF_LEN; - let tree = MerkleTree::create(&leaves, depth); - let (_, proof) = tree.generate_proof(field_index, depth)?; - - Ok(proof) - } - - pub fn data(&self) -> &BeaconState { - &self.data - } - - pub fn execution_optimistic(&self) -> bool { - self.execution_optimistic - } - - pub fn historical_roots_tree_hash_root(&self) -> H256 { - self.data.historical_roots().tree_hash_root() - } - - pub fn historical_summaries_tree_hash_root(&self) -> Result { - Ok(self.data.historical_summaries()?.tree_hash_root()) - } - - pub fn state_root(&mut self) -> Result { - self.data.canonical_root() - } - - pub fn version(&self) -> &str { - &self.version - } - - /// This computation only makes sense if we have all of the leaves (BeaconBlock roots) to construct - /// the [`HistoricalSummary`] Merkle tree. - /// We construct a new [`HistoricalSummary`] from the state and check that the tree root is in historical_summaries. - /// This will be true if the state is in the first slot of an era. - pub fn block_roots_contain_entire_era(&self) -> Result { - // Check if the block_roots buffer can have accumulated an entire era, i.e. 8192 blocks. - if self.data.block_roots().len() % SLOTS_PER_HISTORICAL_ROOT == 0 { - let historical_summary = HistoricalSummary::new(&self.data); - Ok(self - .data - .historical_summaries()? - .iter() - .last() - .map(|summary| summary == &historical_summary) - .unwrap_or(false)) - } else { - Ok(false) - } - } - - /// Computes a Merkle inclusion proof of a `BeaconBlock` root using Merkle trees from either - /// the [`historical_roots`](https://github.com/ethereum/annotated-spec/blob/master/phase0/beacon-chain.md#beaconstate) - /// or [`historical_summaries`](https://github.com/ethereum/annotated-spec/blob/master/capella/beacon-chain.md#beaconstate) list. - /// See the discussion [here](https://github.com/ethereum/annotated-spec/blob/master/phase0/beacon-chain.md#slots_per_historical_root) - /// for more details about the `historical_roots` and [here](https://github.com/ethereum/annotated-spec/blob/master/capella/beacon-chain.md#historicalsummary) - /// about `historical_summaries`. - pub fn compute_block_roots_proof(&self, index: usize) -> Result, Error> { - // Construct the block_roots Merkle tree and generate the proof. - let leaves = self.data.block_roots().to_vec(); - let tree = MerkleTree::create(&leaves, HISTORY_TREE_DEPTH); - let (_, mut proof) = tree.generate_proof(index, HISTORY_TREE_DEPTH)?; - - // We are going to verify this proof using the HistoricalSummary root, the two children nodes are the block_roots tree root and that state_roots tree root. - // So we append the state_roots tree root to the proof. - let state_roots_root = self.data.state_roots().tree_hash_root(); - proof.push(state_roots_root); - - Ok(proof) - } - - pub fn compute_block_roots_proof_only(&self, index: usize) -> Result, Error> { - let leaves = self.data.block_roots().to_vec(); - let tree = MerkleTree::create(&leaves, HISTORY_TREE_DEPTH); - let (_, proof) = tree.generate_proof(index, HISTORY_TREE_DEPTH)?; - - Ok(proof) - } -} - -// Construct the block_roots Merkle tree and generate the proof. -pub fn compute_block_roots_proof_only( - block_roots: &[H256], - index: usize, -) -> Result, Error> { - let leaves = block_roots; - let tree = MerkleTree::create(leaves, HISTORY_TREE_DEPTH); - let (_, proof) = tree.generate_proof(index, HISTORY_TREE_DEPTH).unwrap(); - - Ok(proof) -} diff --git a/forrestrie/src/execution_layer.rs b/forrestrie/src/execution_layer.rs deleted file mode 100644 index 245c8a27..00000000 --- a/forrestrie/src/execution_layer.rs +++ /dev/null @@ -1,326 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! Execution Layer functionality to build a Merkle Patricia Trie (MPT) from Ethereum receipts -//! and generate inclusion proofs for specified receipts within the trie. It includes data structures -//! for parsing and handling receipt data, as well as utilities for encoding and decoding as required -//! by the Ethereum specification. - -use alloy_primitives::{Bloom, U256}; -use alloy_rlp::Encodable; -use reth_primitives::{Log, Receipt, ReceiptWithBloom, TxType}; -use reth_trie_common::{proof::ProofRetainer, root::adjust_index_for_rlp, HashBuilder, Nibbles}; -use serde::{Deserialize, Deserializer, Serialize}; -use std::vec::IntoIter; - -#[derive(Debug, Deserialize, Serialize)] -pub struct ReceiptJson { - #[serde(rename = "type")] - #[serde(deserialize_with = "str_to_type")] - pub tx_type: TxType, - #[serde(rename = "blockHash")] - pub block_hash: String, - #[serde(rename = "blockNumber")] - pub block_number: String, - pub logs: Vec, - #[serde(rename = "cumulativeGasUsed")] - pub cumulative_gas_used: U256, - #[serde(deserialize_with = "status_to_bool")] - pub status: bool, - // TODO: should we trust logsBloom provided or calculate it from the logs? - #[serde(rename = "logsBloom")] - pub logs_bloom: Bloom, -} - -impl ReceiptJson { - #[cfg(test)] - fn fake() -> Self { - use alloy_primitives::{bytes, fixed_bytes, Address}; - use rand::{self, rngs::OsRng, RngCore}; - - fn fake_log() -> Log { - // generate random slice of bytes - let mut random_bytes = [0u8; 20]; - OsRng.fill_bytes(&mut random_bytes); - // Create a 32-byte array initialized with zeros - let mut bytes = [0u8; 32]; - - // Insert the random bytes into the last 20 bytes of the array - bytes[12..].copy_from_slice(&random_bytes); - - // Generate a static Log based on an actual log receipt - Log::new_unchecked( - Address::random(), - vec![ - fixed_bytes!( - "e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c" - ), - fixed_bytes!( - "0000000000000000000000003328f7f4a1d1c57c35df56bbf0c9dcafca309c49" - ), - ], - bytes!("0000000000000000000000000000000000000000000000000dcf09da3e1eb9f3"), - ) - } - - let logs: Vec = (0..5).map(|_| fake_log()).collect(); - - ReceiptJson { - tx_type: TxType::Eip1559, // Replace with any desired variant - block_hash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" - .to_string(), - block_number: "0x1a".to_string(), - logs, - cumulative_gas_used: U256::from(0x5208), - status: true, - logs_bloom: Bloom::default(), - } - } -} - -/// Represents a leaf in the trie for which a proof is to be generated, i.e., the target of the proof. -/// The `nibbles` represent the path to the leaf in the trie, and the `value` is the data stored at the leaf. -#[derive(Debug)] -pub struct TargetLeaf { - pub nibbles: Nibbles, - pub value: Vec, -} - -impl TargetLeaf { - // Constructor to create a new TargetLeaf - pub fn new(nibbles: Nibbles, value: Vec) -> Self { - TargetLeaf { nibbles, value } - } -} - -pub struct TargetLeaves(Vec); - -impl TargetLeaves { - fn new() -> Self { - TargetLeaves(Vec::new()) - } - - pub fn from_indices( - target_idxs: &[usize], - receipts: &[ReceiptWithBloom], - ) -> Result { - let mut index_buffer = Vec::new(); - let mut value_buffer = Vec::new(); - let mut targets = TargetLeaves::new(); - let receipts_len = receipts.len(); - - for &target_idx in target_idxs { - if target_idx >= receipts_len { - return Err("Index out of bounds"); - } - - index_buffer.clear(); - value_buffer.clear(); - - // Adjust the index and encode it - let index = adjust_index_for_rlp(target_idx, receipts_len); - index.encode(&mut index_buffer); - - // Generate nibble path from the index buffer - let nibble = Nibbles::unpack(&index_buffer); - - // Encode the receipt and create TargetLeaf - receipts[index].encode_inner(&mut value_buffer, false); - targets - .0 - .push(TargetLeaf::new(nibble, value_buffer.clone())); - } - - Ok(targets) - } -} - -impl IntoIterator for TargetLeaves { - type Item = TargetLeaf; - type IntoIter = IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl TryFrom<&ReceiptJson> for ReceiptWithBloom { - type Error = String; - - fn try_from(receipt_json: &ReceiptJson) -> Result { - let cumulative_gas_used = receipt_json - .cumulative_gas_used - .try_into() - .map_err(|_| "Failed to convert U256 to u64".to_string())?; - - let receipt = Receipt { - tx_type: receipt_json.tx_type, - success: receipt_json.status, - cumulative_gas_used, - logs: receipt_json.logs.clone(), - // NOTICE: receipts will have more fields depending of the EVM chain. - // this is how to handle them in the futuro - // #[cfg(feature = "optimism")] - // deposit_nonce: None, // Handle Optimism-specific fields as necessary - // #[cfg(feature = "optimism")] - // deposit_receipt_version: None, - }; - - Ok(ReceiptWithBloom { - bloom: receipt_json.logs_bloom, - receipt, - }) - } -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct ReceiptsFromBlock { - pub result: Vec, -} - -impl FromIterator for ReceiptsFromBlock { - fn from_iter>(iter: I) -> Self { - ReceiptsFromBlock { - result: iter.into_iter().collect(), - } - } -} - -fn status_to_bool<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let status_str: &str = Deserialize::deserialize(deserializer)?; - match status_str { - "0x1" => Ok(true), - "0x0" => Ok(false), - _ => Err(serde::de::Error::custom("Invalid status value")), - } -} - -fn str_to_type<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let tx_type_str: &str = Deserialize::deserialize(deserializer)?; - // Convert the hex string (without the "0x" prefix) to u8 - let tx_type_value = u8::from_str_radix(tx_type_str.trim_start_matches("0x"), 16) - .map_err(|_| serde::de::Error::custom("Invalid tx_type value"))?; - TxType::try_from(tx_type_value).map_err(|_| serde::de::Error::custom("Invalid tx_type value")) -} - -/// builds the trie to generate proofs from the Receipts -/// generate a different root. Make sure that the source of receipts sorts them by `logIndex` -/// # Example -/// -/// ```no_run -/// # use reth_primitives::ReceiptWithBloom; -/// # use forrestrie::execution_layer::build_trie_with_proofs; -/// # let receipts_json = vec![]; -/// // Assume `receipts_json` is a vector of deserialized receipt objects from an execution block given by an Ethereum node. -/// let receipts_with_bloom: Vec = receipts_json -/// .iter() -/// .map(ReceiptWithBloom::try_from) -/// .collect::>().unwrap(); -/// -/// // Specify the indices of receipts for which proofs are needed. -/// let target_indices = &[0, 2, 5]; -/// -/// // Build the trie and obtain proofs. -/// let mut hash_builder = build_trie_with_proofs(&receipts_with_bloom, target_indices); -/// -/// // Retrieve the root hash of the trie, and retain the proofs so they can be verified. -/// let trie_root = hash_builder.root(); -/// ``` -pub fn build_trie_with_proofs(receipts: &[ReceiptWithBloom], target_idxs: &[usize]) -> HashBuilder { - // Initialize ProofRetainer with the target nibbles (the keys for which we want proofs) - let receipts_len = receipts.len(); - let targets: Vec = target_idxs - .iter() - .map(|&i| { - let index = adjust_index_for_rlp(i, receipts_len); - let mut index_buffer = Vec::new(); - index.encode(&mut index_buffer); - Nibbles::unpack(&index_buffer) - }) - .collect(); - - let proof_retainer = ProofRetainer::new(targets); - let mut hb = HashBuilder::default().with_proof_retainer(proof_retainer); - - for i in 0..receipts_len { - // Adjust the index for RLP - let index = adjust_index_for_rlp(i, receipts_len); - - // Encode the index into nibbles - let mut index_buffer = Vec::new(); - index.encode(&mut index_buffer); - let index_nibbles = Nibbles::unpack(&index_buffer); - - // Encode the receipt value - let mut value_buffer = Vec::new(); - receipts[index].encode_inner(&mut value_buffer, false); - - hb.add_leaf(index_nibbles, &value_buffer); - } - - hb -} - -#[cfg(test)] -mod tests { - use super::*; - use reth_trie_common::proof::verify_proof; - - #[test] - fn test_compute_receipts_trie_root_and_proof() { - let block_receipts: ReceiptsFromBlock = (0_i32..10).map(|_| ReceiptJson::fake()).collect(); - - let receipts_with_bloom: Result, String> = block_receipts - .result - .iter() - .map(ReceiptWithBloom::try_from) - .collect::, _>>(); - - // computes the root and verify against existing data - let mut hb: HashBuilder; - //target_idxs are the logIndexes for receipts to get proofs from. - // these values are arbitrary - let target_idxs = &[4]; - let targets: TargetLeaves; - - match receipts_with_bloom { - Ok(receipts) => { - hb = build_trie_with_proofs(&receipts, target_idxs); - - // build some of the targets to get proofs for them - targets = TargetLeaves::from_indices(target_idxs, &receipts).unwrap(); - } - Err(e) => { - // Handle the error (e.g., by logging or panicking) - panic!("Failed to convert receipts: {}", e); - } - } - - // necessary to call this method to retain proofs - hb.root(); - - // verifies proof for retained targets - let proof = hb.take_proof_nodes(); - for target in targets { - assert_eq!( - verify_proof( - hb.root(), - target.nibbles.clone(), - Some(target.value.to_vec()), - proof - .clone() - .matching_nodes_sorted(&target.nibbles) - .iter() - .map(|(_, node)| node) - ), - Ok(()) - ); - } - } -} diff --git a/forrestrie/src/lib.rs b/forrestrie/src/lib.rs deleted file mode 100644 index 19785755..00000000 --- a/forrestrie/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright 2024-, Semiotic AI, Inc. -// SPDX-License-Identifier: Apache-2.0 - -pub mod beacon_block; -pub mod beacon_state; -pub mod execution_layer;