From ce01738b2bc4e79a7ac4684bf9aae10601ce4e4f Mon Sep 17 00:00:00 2001 From: Joseph Livesey Date: Fri, 18 Oct 2024 17:44:05 -0400 Subject: [PATCH 1/2] docs(forrestrie): add const values for useful calculations --- crates/forrestrie/src/beacon_state.rs | 29 ++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/forrestrie/src/beacon_state.rs b/crates/forrestrie/src/beacon_state.rs index db8c1c75..a410859f 100644 --- a/crates/forrestrie/src/beacon_state.rs +++ b/crates/forrestrie/src/beacon_state.rs @@ -16,7 +16,34 @@ use types::{ Validator, Vector, }; -pub const CAPELLA_START_ERA: usize = 758; +/// 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; +/// https://beaconcha.in/slot/8626176 +pub const DENEB_START_SLOT: usize = 8626176; +/// https://beaconcha.in/slot/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 ) From d8f042d1aa3b035317e5e4110c55ff15cf180585 Mon Sep 17 00:00:00 2001 From: Joseph Livesey Date: Fri, 18 Oct 2024 21:55:51 -0400 Subject: [PATCH 2/2] chore(forrestrie/examples): add example of matching eth to beacon block --- Cargo.lock | 2 + Cargo.toml | 3 +- crates/forrestrie/Cargo.toml | 2 + .../examples/match_ethereum_to_beacon.rs | 122 ++++++++++++++++++ 4 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 crates/forrestrie/examples/match_ethereum_to_beacon.rs 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 +}