diff --git a/Cargo.lock b/Cargo.lock index ed321f03..4432e8ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2334,6 +2334,8 @@ dependencies = [ "tokio", "tonic", "tonic-build", + "tracing", + "tracing-subscriber", "tree_hash 0.6.0", "types", ] diff --git a/Cargo.toml b/Cargo.toml index 460e1837..439427a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ bytes = "1.5" clap = { version = "4.4.10", features = ["derive"] } criterion = { version = "0.5.1", features = ["html_reports"] } dotenvy = "0.15.7" -env_logger = "0.11.2" +env_logger = "0.11.5" ethereum-types = "=0.14.1" ethportal-api = { git = "https://github.com/ethereum/trin.git", version = "0.2.2", tag = "v0.1.0-alpha.51" } futures = "0.3.31" @@ -54,6 +54,7 @@ tokio-test = "0.4.3" tonic = "0.12.0" tonic-build = "0.12.0" tracing = "0.1.40" +tracing-subscriber = "0.3" tree_hash = "0.6.0" trin-validation = { git = "https://github.com/ethereum/trin.git", version = "0.1.0", tag = "v0.1.0-alpha.51" } types = { git = "https://github.com/semiotic-ai/lighthouse.git", branch = "stable" } diff --git a/crates/forrestrie/Cargo.toml b/crates/forrestrie/Cargo.toml index 816f0fbd..ef1bed11 100644 --- a/crates/forrestrie/Cargo.toml +++ b/crates/forrestrie/Cargo.toml @@ -18,6 +18,7 @@ prost.workspace = true prost-wkt.workspace = true prost-wkt-types.workspace = true serde = { workspace = true, features = ["derive"] } +tracing.workspace = true ssz_types.workspace = true tonic.workspace = true tree_hash.workspace = true @@ -29,6 +30,7 @@ insta.workspace = true reqwest = { workspace = true, features = ["json"] } serde_json.workspace = true tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +tracing-subscriber = "0.3" [build-dependencies] prost-build.workspace = true diff --git a/crates/forrestrie/examples/match_ethereum_to_beacon.rs b/crates/forrestrie/examples/match_ethereum_to_beacon.rs new file mode 100644 index 00000000..15aaa96e --- /dev/null +++ b/crates/forrestrie/examples/match_ethereum_to_beacon.rs @@ -0,0 +1,122 @@ +//! # 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 firehose_client::client::{Chain, FirehoseClient}; +use firehose_protos::beacon_v1::{block, Block as FirehoseBeaconBlock}; +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(block::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(block::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 +}