From 0c52bf15709304021191b46d41baba564b8ec818 Mon Sep 17 00:00:00 2001 From: Joseph Livesey Date: Fri, 18 Oct 2024 21:52:42 -0400 Subject: [PATCH] chore(forrestrie/examples): add example of matching eth block to beacon slot --- Cargo.lock | 2 + Cargo.toml | 3 +- crates/forrestrie/Cargo.toml | 2 + .../examples/match_ethereum_to_beacon.rs | 119 ++++++++++++++++++ 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 crates/forrestrie/examples/match_ethereum_to_beacon.rs diff --git a/Cargo.lock b/Cargo.lock index 395129b9..7253496c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2257,6 +2257,8 @@ dependencies = [ "serde_json", "sf-protos", "tokio", + "tracing", + "tracing-subscriber", "tree_hash 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "types", ] diff --git a/Cargo.toml b/Cargo.toml index 33dd0148..d38f33da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ clap = { version = "4.4.10", features = ["derive"] } criterion = { version = "0.5.1", features = ["html_reports"] } dotenv = "0.15.0" 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.35" } futures = "0.3.31" @@ -53,6 +53,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.35" } types = { git = "https://github.com/semiotic-ai/lighthouse.git", branch = "stable" } diff --git a/crates/forrestrie/Cargo.toml b/crates/forrestrie/Cargo.toml index 2821fad3..333957f2 100644 --- a/crates/forrestrie/Cargo.toml +++ b/crates/forrestrie/Cargo.toml @@ -20,6 +20,7 @@ merkle_proof.workspace = true primitive-types.workspace = true serde = { workspace = true, features = ["derive"] } sf-protos = { path = "../sf-protos" } +tracing.workspace = true tree_hash.workspace = true types.workspace = true @@ -29,3 +30,4 @@ insta.workspace = true reqwest.workspace = true serde_json.workspace = true tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +tracing-subscriber = "0.3" 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..b29a4997 --- /dev/null +++ b/crates/forrestrie/examples/match_ethereum_to_beacon.rs @@ -0,0 +1,119 @@ +//! # 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 forrestrie::beacon_state::{DENEB_START_SLOT, ETHEREUM_BEACON_DENEB_OFFSET}; +use sf_protos::beacon::r#type::v1::{block, Block as FirehoseBeaconBlock}; +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. + +#[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 = DENEB_START_SLOT as u64; + let mut high = EXECUTION_BLOCK_NUMBER - ETHEREUM_BEACON_DENEB_OFFSET as u64 + 2_000_000; + + 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; + + if block_number == EXECUTION_BLOCK_NUMBER { + info!(beacon_slot = block.slot, "Found matching Beacon block!"); + break; + } + + match block_number.cmp(&EXECUTION_BLOCK_NUMBER) { + Less => low = mid + 1, + Greater => high = mid - 1, + _ => unreachable!(), + } + + 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 +}