From ef6b7346c3ce44f460f5dad4c93103936b2686fa Mon Sep 17 00:00:00 2001 From: dylan Date: Fri, 7 Mar 2025 14:34:55 -0700 Subject: [PATCH 01/53] updates the tx-poller to stream transactions - shifts the tx-poller to a more actor oriented approach by streaming transactions out of the cache. - transaction deduplication and validation is intended to be carried out by the simulator. --- src/tasks/tx_poller.rs | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/tasks/tx_poller.rs b/src/tasks/tx_poller.rs index 4a83577..371cb34 100644 --- a/src/tasks/tx_poller.rs +++ b/src/tasks/tx_poller.rs @@ -1,34 +1,38 @@ +use std::sync::Arc; + use alloy::consensus::TxEnvelope; use eyre::Error; use reqwest::{Client, Url}; use serde::{Deserialize, Serialize}; use serde_json::from_slice; +use tokio::{sync::mpsc, task::JoinHandle}; pub use crate::config::BuilderConfig; -/// Response from the tx-pool endpoint. +/// Models a response from the transaction pool. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TxPoolResponse { + /// Holds the transactions property as a list on the response. transactions: Vec, } /// Implements a poller for the block builder to pull transactions from the transaction pool. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TxPoller { - /// config for the builder + /// Config values from the Builder. pub config: BuilderConfig, - /// Reqwest client for fetching transactions from the tx-pool + /// Reqwest Client for fetching transactions from the tx-pool. pub client: Client, } -/// TxPoller implements a poller that fetches unique transactions from the transaction pool. +/// TxPoller implements a poller task that fetches unique transactions from the transaction pool. impl TxPoller { - /// returns a new TxPoller with the given config. + /// Returns a new TxPoller with the given config. pub fn new(config: &BuilderConfig) -> Self { Self { config: config.clone(), client: Client::new() } } - /// polls the tx-pool for unique transactions and evicts expired transactions. + /// Polls the tx-pool for unique transactions and evicts expired transactions. /// unique transactions that haven't been seen before are sent into the builder pipeline. pub async fn check_tx_cache(&mut self) -> Result, Error> { let url: Url = Url::parse(&self.config.tx_pool_url)?.join("transactions")?; @@ -36,4 +40,23 @@ impl TxPoller { let response: TxPoolResponse = from_slice(result.text().await?.as_bytes())?; Ok(response.transactions) } + + /// Spawns a task that trawls the cache for transactions and sends along anything it finds + pub fn spawn(mut self) -> (mpsc::UnboundedReceiver>, JoinHandle<()>) { + let (outbound, inbound) = mpsc::unbounded_channel(); + let jh = tokio::spawn(async move { + loop { + if let Ok(transactions) = self.check_tx_cache().await { + tracing::debug!(count = ?transactions.len(), "found transactions"); + for tx in transactions.iter() { + if let Err(err) = outbound.send(Arc::new(tx.clone())) { + tracing::error!(err = ?err, "failed to send transaction outbound"); + } + } + } + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + } + }); + (inbound, jh) + } } From 1ad6e14e914fa478a5f6a583a0f26f66a89baf9c Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 17 Mar 2025 12:11:32 -0600 Subject: [PATCH 02/53] refactors - improves poll interval handling - removes arcs - updates comments - adds file level comment --- src/tasks/tx_poller.rs | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/tasks/tx_poller.rs b/src/tasks/tx_poller.rs index 371cb34..203bad0 100644 --- a/src/tasks/tx_poller.rs +++ b/src/tasks/tx_poller.rs @@ -1,5 +1,5 @@ -use std::sync::Arc; - +//! Transaction service responsible for fetching and sending trasnsactions to the simulator. +use crate::config::BuilderConfig; use alloy::consensus::TxEnvelope; use eyre::Error; use reqwest::{Client, Url}; @@ -7,8 +7,6 @@ use serde::{Deserialize, Serialize}; use serde_json::from_slice; use tokio::{sync::mpsc, task::JoinHandle}; -pub use crate::config::BuilderConfig; - /// Models a response from the transaction pool. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TxPoolResponse { @@ -21,19 +19,26 @@ pub struct TxPoolResponse { pub struct TxPoller { /// Config values from the Builder. pub config: BuilderConfig, - /// Reqwest Client for fetching transactions from the tx-pool. + /// Reqwest Client for fetching transactions from the cache. pub client: Client, + /// Defines the interval at which the service should poll the cache. + pub poll_interval_ms: u64, } -/// TxPoller implements a poller task that fetches unique transactions from the transaction pool. +/// [`TxPoller`] implements a poller task that fetches unique transactions from the transaction pool. impl TxPoller { - /// Returns a new TxPoller with the given config. + /// Returns a new [`TxPoller`] with the given config. + /// * Defaults to 1000ms poll interval (1s). pub fn new(config: &BuilderConfig) -> Self { - Self { config: config.clone(), client: Client::new() } + Self { config: config.clone(), client: Client::new(), poll_interval_ms: 1000 } + } + + /// Returns a new [`TxPoller`] with the given config and cache polling interval in milliseconds. + pub fn new_with_poll_interval_ms(config: &BuilderConfig, poll_interval_ms: u64) -> Self { + Self { config: config.clone(), client: Client::new(), poll_interval_ms } } - /// Polls the tx-pool for unique transactions and evicts expired transactions. - /// unique transactions that haven't been seen before are sent into the builder pipeline. + /// Polls the transaction cache for transactions. pub async fn check_tx_cache(&mut self) -> Result, Error> { let url: Url = Url::parse(&self.config.tx_pool_url)?.join("transactions")?; let result = self.client.get(url).send().await?; @@ -41,20 +46,20 @@ impl TxPoller { Ok(response.transactions) } - /// Spawns a task that trawls the cache for transactions and sends along anything it finds - pub fn spawn(mut self) -> (mpsc::UnboundedReceiver>, JoinHandle<()>) { + /// Spawns a task that continuously polls the cache for transactions and sends any it finds to its sender. + pub fn spawn(mut self) -> (mpsc::UnboundedReceiver, JoinHandle<()>) { let (outbound, inbound) = mpsc::unbounded_channel(); let jh = tokio::spawn(async move { loop { if let Ok(transactions) = self.check_tx_cache().await { tracing::debug!(count = ?transactions.len(), "found transactions"); - for tx in transactions.iter() { - if let Err(err) = outbound.send(Arc::new(tx.clone())) { - tracing::error!(err = ?err, "failed to send transaction outbound"); + for tx in transactions.into_iter() { + if let Err(err) = outbound.send(tx) { + tracing::error!(err = ?err, "failed to send transaction - channel is dropped."); } } } - tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + tokio::time::sleep(tokio::time::Duration::from_millis(self.poll_interval_ms)).await; } }); (inbound, jh) From b5a5cf4efee4b8450da69bd72d135243e2b09f0a Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Tue, 8 Apr 2025 15:56:17 -0400 Subject: [PATCH 03/53] Fix: various updates to the tx poller (#67) * fix: break loop on closure and improve tracing * refactor: break out the task future --- src/tasks/tx_poller.rs | 51 ++++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/src/tasks/tx_poller.rs b/src/tasks/tx_poller.rs index 203bad0..abc9430 100644 --- a/src/tasks/tx_poller.rs +++ b/src/tasks/tx_poller.rs @@ -5,7 +5,8 @@ use eyre::Error; use reqwest::{Client, Url}; use serde::{Deserialize, Serialize}; use serde_json::from_slice; -use tokio::{sync::mpsc, task::JoinHandle}; +use tokio::{sync::mpsc, task::JoinHandle, time}; +use tracing::{Instrument, debug, trace}; /// Models a response from the transaction pool. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -46,22 +47,48 @@ impl TxPoller { Ok(response.transactions) } - /// Spawns a task that continuously polls the cache for transactions and sends any it finds to its sender. - pub fn spawn(mut self) -> (mpsc::UnboundedReceiver, JoinHandle<()>) { - let (outbound, inbound) = mpsc::unbounded_channel(); - let jh = tokio::spawn(async move { - loop { - if let Ok(transactions) = self.check_tx_cache().await { - tracing::debug!(count = ?transactions.len(), "found transactions"); + async fn task_future(mut self, outbound: mpsc::UnboundedSender) { + loop { + let span = tracing::debug_span!("TxPoller::loop", url = %self.config.tx_pool_url); + + // Enter the span for the next check. + let _guard = span.enter(); + + // Check this here to avoid making the web request if we know + // we don't need the results. + if outbound.is_closed() { + trace!("No receivers left, shutting down"); + break; + } + // exit the span after the check. + drop(_guard); + + match self.check_tx_cache().instrument(span.clone()).await { + Ok(transactions) => { + let _guard = span.entered(); + debug!(count = ?transactions.len(), "found transactions"); for tx in transactions.into_iter() { - if let Err(err) = outbound.send(tx) { - tracing::error!(err = ?err, "failed to send transaction - channel is dropped."); + if outbound.send(tx).is_err() { + // If there are no receivers, we can shut down + trace!("No receivers left, shutting down"); + break; } } } - tokio::time::sleep(tokio::time::Duration::from_millis(self.poll_interval_ms)).await; + // If fetching was an error, we log and continue. We expect + // these to be transient network issues. + Err(e) => { + debug!(error = %e, "Error fetching transactions"); + } } - }); + time::sleep(time::Duration::from_millis(self.poll_interval_ms)).await; + } + } + + /// Spawns a task that continuously polls the cache for transactions and sends any it finds to its sender. + pub fn spawn(self) -> (mpsc::UnboundedReceiver, JoinHandle<()>) { + let (outbound, inbound) = mpsc::unbounded_channel(); + let jh = tokio::spawn(self.task_future(outbound)); (inbound, jh) } } From 3117b85a11774cd02735b2242bcb6c19e110a421 Mon Sep 17 00:00:00 2001 From: dylan Date: Sun, 16 Mar 2025 19:08:11 -0600 Subject: [PATCH 04/53] updates bundler to streaming actor pattern --- src/tasks/block.rs | 1 - src/tasks/bundler.rs | 83 +++++++++++++++++++++----------------------- 2 files changed, 39 insertions(+), 45 deletions(-) diff --git a/src/tasks/block.rs b/src/tasks/block.rs index 6ba1e8e..e750696 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -180,7 +180,6 @@ impl BlockBuilder { error!(error = %e, "error polling bundles"); } } - self.bundle_poller.evict(); } /// Simulates a Zenith bundle against the rollup state diff --git a/src/tasks/bundler.rs b/src/tasks/bundler.rs index 670ecc5..b43e80b 100644 --- a/src/tasks/bundler.rs +++ b/src/tasks/bundler.rs @@ -1,4 +1,6 @@ //! Bundler service responsible for managing bundles. +use std::sync::Arc; + use super::oauth::Authenticator; pub use crate::config::BuilderConfig; @@ -6,53 +8,56 @@ pub use crate::config::BuilderConfig; use oauth2::TokenResponse; use reqwest::Url; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::time::{Duration, Instant}; +use tokio::sync::mpsc::{UnboundedReceiver, unbounded_channel}; +use tokio::task::JoinHandle; use zenith_types::ZenithEthBundle; -/// A bundle response from the tx-pool endpoint, containing a UUID and a -/// [`ZenithEthBundle`]. +/// Holds a Signet bundle from the cache that has a unique identifier +/// and a Zenith bundle #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Bundle { - /// The bundle id (a UUID) + /// Cache identifier for the bundle pub id: String, - /// The bundle itself + /// The Zenith bundle for this bundle pub bundle: ZenithEthBundle, } +impl PartialEq for Bundle { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Eq for Bundle {} + /// Response from the tx-pool containing a list of bundles. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TxPoolBundleResponse { - /// the list of bundles + /// Bundle responses are availabel on the bundles property pub bundles: Vec, } /// The BundlePoller polls the tx-pool for bundles and manages the seen bundles. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct BundlePoller { - /// Configuration + /// The builder configuration values. pub config: BuilderConfig, - /// [`Authenticator`] for fetching OAuth tokens + /// Authentication module that periodically fetches and stores auth tokens. pub authenticator: Authenticator, - /// Already seen bundle UUIDs - pub seen_uuids: HashMap, } /// Implements a poller for the block builder to pull bundles from the tx cache. impl BundlePoller { /// Creates a new BundlePoller from the provided builder config. pub fn new(config: &BuilderConfig, authenticator: Authenticator) -> Self { - Self { config: config.clone(), authenticator, seen_uuids: HashMap::new() } + Self { config: config.clone(), authenticator } } /// Fetches bundles from the transaction cache and returns the (oldest? random?) bundle in the cache. pub async fn check_bundle_cache(&mut self) -> eyre::Result> { - let mut unique: Vec = Vec::new(); - let bundle_url: Url = Url::parse(&self.config.tx_pool_url)?.join("bundles")?; let token = self.authenticator.fetch_oauth_token().await?; - // Add the token to the request headers let result = reqwest::Client::new() .get(bundle_url) .bearer_auth(token.access_token().secret()) @@ -61,38 +66,28 @@ impl BundlePoller { .error_for_status()?; let body = result.bytes().await?; - let bundles: TxPoolBundleResponse = serde_json::from_slice(&body)?; - - bundles.bundles.iter().for_each(|bundle| { - self.check_seen_bundles(bundle.clone(), &mut unique); - }); + let resp: TxPoolBundleResponse = serde_json::from_slice(&body)?; - Ok(unique) + Ok(resp.bundles) } - /// Checks if the bundle has been seen before and if not, adds it to the unique bundles list. - fn check_seen_bundles(&mut self, bundle: Bundle, unique: &mut Vec) { - self.seen_uuids.entry(bundle.id.clone()).or_insert_with(|| { - // add to the set of unique bundles - unique.push(bundle.clone()); - Instant::now() + Duration::from_secs(self.config.tx_pool_cache_duration) + /// Spawns a task that simply sends out any bundles it ever finds + pub fn spawn(mut self) -> (UnboundedReceiver>, JoinHandle<()>) { + let (outbound, inbound) = unbounded_channel(); + let jh = tokio::spawn(async move { + loop { + if let Ok(bundles) = self.check_bundle_cache().await { + tracing::debug!(count = ?bundles.len(), "found bundles"); + for bundle in bundles.iter() { + if let Err(err) = outbound.send(Arc::new(bundle.clone())) { + tracing::error!(err = ?err, "Failed to send bundle"); + } + } + } + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + } }); - } - - /// Evicts expired bundles from the cache. - pub fn evict(&mut self) { - let expired_keys: Vec = self - .seen_uuids - .iter() - .filter_map( - |(key, expiry)| { - if expiry.elapsed().is_zero() { Some(key.clone()) } else { None } - }, - ) - .collect(); - for key in expired_keys { - self.seen_uuids.remove(&key); - } + (inbound, jh) } } From 96dce0425f1ba1a96e7090ec7aec82f571966657 Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 17 Mar 2025 12:20:44 -0600 Subject: [PATCH 05/53] refactors - improves poll interval handling - cleans up and updates comments - removes arc usage --- src/tasks/bundler.rs | 56 +++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/tasks/bundler.rs b/src/tasks/bundler.rs index b43e80b..accd943 100644 --- a/src/tasks/bundler.rs +++ b/src/tasks/bundler.rs @@ -1,10 +1,6 @@ -//! Bundler service responsible for managing bundles. -use std::sync::Arc; - -use super::oauth::Authenticator; - +//! Bundler service responsible for fetching bundles and sending them to the simulator. pub use crate::config::BuilderConfig; - +use crate::tasks::oauth::Authenticator; use oauth2::TokenResponse; use reqwest::Url; use serde::{Deserialize, Serialize}; @@ -12,8 +8,7 @@ use tokio::sync::mpsc::{UnboundedReceiver, unbounded_channel}; use tokio::task::JoinHandle; use zenith_types::ZenithEthBundle; -/// Holds a Signet bundle from the cache that has a unique identifier -/// and a Zenith bundle +/// Holds a bundle from the cache with a unique ID and a Zenith bundle. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Bundle { /// Cache identifier for the bundle @@ -22,38 +17,37 @@ pub struct Bundle { pub bundle: ZenithEthBundle, } -impl PartialEq for Bundle { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -impl Eq for Bundle {} - /// Response from the tx-pool containing a list of bundles. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TxPoolBundleResponse { - /// Bundle responses are availabel on the bundles property + /// Bundle responses are available on the bundles property. pub bundles: Vec, } -/// The BundlePoller polls the tx-pool for bundles and manages the seen bundles. +/// The BundlePoller polls the tx-pool for bundles. #[derive(Debug, Clone)] pub struct BundlePoller { /// The builder configuration values. pub config: BuilderConfig, /// Authentication module that periodically fetches and stores auth tokens. pub authenticator: Authenticator, + /// Defines the interval at which the bundler polls the tx-pool for bundles. + pub poll_interval_ms: u64, } -/// Implements a poller for the block builder to pull bundles from the tx cache. +/// Implements a poller for the block builder to pull bundles from the tx-pool. impl BundlePoller { /// Creates a new BundlePoller from the provided builder config. pub fn new(config: &BuilderConfig, authenticator: Authenticator) -> Self { - Self { config: config.clone(), authenticator } + Self { config: config.clone(), authenticator, poll_interval_ms: 1000 } } - /// Fetches bundles from the transaction cache and returns the (oldest? random?) bundle in the cache. + /// Creates a new BundlePoller from the provided builder config and with the specified poll interval in ms. + pub fn new_with_poll_interval_ms(config: &BuilderConfig, authenticator: Authenticator, poll_interval_ms: u64) -> Self { + Self { config: config.clone(), authenticator, poll_interval_ms } + } + + /// Fetches bundles from the transaction cache and returns them. pub async fn check_bundle_cache(&mut self) -> eyre::Result> { let bundle_url: Url = Url::parse(&self.config.tx_pool_url)?.join("bundles")?; let token = self.authenticator.fetch_oauth_token().await?; @@ -71,23 +65,31 @@ impl BundlePoller { Ok(resp.bundles) } - /// Spawns a task that simply sends out any bundles it ever finds - pub fn spawn(mut self) -> (UnboundedReceiver>, JoinHandle<()>) { + /// Spawns a task that sends bundles it finds to its channel sender. + pub fn spawn(mut self) -> (UnboundedReceiver, JoinHandle<()>) { let (outbound, inbound) = unbounded_channel(); let jh = tokio::spawn(async move { loop { if let Ok(bundles) = self.check_bundle_cache().await { tracing::debug!(count = ?bundles.len(), "found bundles"); - for bundle in bundles.iter() { - if let Err(err) = outbound.send(Arc::new(bundle.clone())) { - tracing::error!(err = ?err, "Failed to send bundle"); + for bundle in bundles.into_iter() { + if let Err(err) = outbound.send(bundle) { + tracing::error!(err = ?err, "Failed to send bundle - channel is dropped"); } } } - tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + tokio::time::sleep(tokio::time::Duration::from_millis(self.poll_interval_ms)).await; } }); (inbound, jh) } } + +impl PartialEq for Bundle { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Eq for Bundle {} From 5cfbdc9fcd2f746ab6da539c71a720d30db7418a Mon Sep 17 00:00:00 2001 From: dylan Date: Wed, 19 Mar 2025 14:09:07 -0600 Subject: [PATCH 06/53] fmt --- src/tasks/bundler.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/tasks/bundler.rs b/src/tasks/bundler.rs index accd943..1ea5eee 100644 --- a/src/tasks/bundler.rs +++ b/src/tasks/bundler.rs @@ -43,7 +43,11 @@ impl BundlePoller { } /// Creates a new BundlePoller from the provided builder config and with the specified poll interval in ms. - pub fn new_with_poll_interval_ms(config: &BuilderConfig, authenticator: Authenticator, poll_interval_ms: u64) -> Self { + pub fn new_with_poll_interval_ms( + config: &BuilderConfig, + authenticator: Authenticator, + poll_interval_ms: u64, + ) -> Self { Self { config: config.clone(), authenticator, poll_interval_ms } } From c4522ee516298d1846a99901200776f5afba6021 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 7 Apr 2025 10:01:38 +0200 Subject: [PATCH 07/53] fix: remove eq/partial eq --- src/tasks/bundler.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/tasks/bundler.rs b/src/tasks/bundler.rs index 1ea5eee..fb10dce 100644 --- a/src/tasks/bundler.rs +++ b/src/tasks/bundler.rs @@ -89,11 +89,3 @@ impl BundlePoller { (inbound, jh) } } - -impl PartialEq for Bundle { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -impl Eq for Bundle {} From 01a11e64239c1deda93dceb3acfdd64a650cffd7 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Tue, 8 Apr 2025 15:56:36 -0400 Subject: [PATCH 08/53] fix: various improvements to bundler tasks (#68) --- src/tasks/bundler.rs | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/src/tasks/bundler.rs b/src/tasks/bundler.rs index fb10dce..acc3cae 100644 --- a/src/tasks/bundler.rs +++ b/src/tasks/bundler.rs @@ -4,8 +4,10 @@ use crate::tasks::oauth::Authenticator; use oauth2::TokenResponse; use reqwest::Url; use serde::{Deserialize, Serialize}; -use tokio::sync::mpsc::{UnboundedReceiver, unbounded_channel}; +use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}; use tokio::task::JoinHandle; +use tokio::time; +use tracing::{Instrument, debug, trace}; use zenith_types::ZenithEthBundle; /// Holds a bundle from the cache with a unique ID and a Zenith bundle. @@ -69,12 +71,24 @@ impl BundlePoller { Ok(resp.bundles) } - /// Spawns a task that sends bundles it finds to its channel sender. - pub fn spawn(mut self) -> (UnboundedReceiver, JoinHandle<()>) { - let (outbound, inbound) = unbounded_channel(); - let jh = tokio::spawn(async move { - loop { - if let Ok(bundles) = self.check_bundle_cache().await { + async fn task_future(mut self, outbound: UnboundedSender) { + loop { + let span = tracing::debug_span!("BundlePoller::loop", url = %self.config.tx_pool_url); + + // Enter the span for the next check. + let _guard = span.enter(); + + // Check this here to avoid making the web request if we know + // we don't need the results. + if outbound.is_closed() { + trace!("No receivers left, shutting down"); + break; + } + // exit the span after the check. + drop(_guard); + + match self.check_bundle_cache().instrument(span.clone()).await { + Ok(bundles) => { tracing::debug!(count = ?bundles.len(), "found bundles"); for bundle in bundles.into_iter() { if let Err(err) = outbound.send(bundle) { @@ -82,9 +96,21 @@ impl BundlePoller { } } } - tokio::time::sleep(tokio::time::Duration::from_millis(self.poll_interval_ms)).await; + // If fetching was an error, we log and continue. We expect + // these to be transient network issues. + Err(e) => { + debug!(error = %e, "Error fetching bundles"); + } } - }); + time::sleep(time::Duration::from_millis(self.poll_interval_ms)).await; + } + } + + /// Spawns a task that sends bundles it finds to its channel sender. + pub fn spawn(self) -> (UnboundedReceiver, JoinHandle<()>) { + let (outbound, inbound) = unbounded_channel(); + + let jh = tokio::spawn(self.task_future(outbound)); (inbound, jh) } From 63742382d6f0ed487cd44c7ec21bcc9d74aa8b74 Mon Sep 17 00:00:00 2001 From: dylan Date: Sun, 16 Mar 2025 19:13:52 -0600 Subject: [PATCH 09/53] chore: update to alloy@0.11 - adds init4 metrics - updates provider types to account for alloy changes --- Cargo.toml | 6 ++++-- bin/submit_transaction.rs | 1 - src/config.rs | 45 ++++++++++++++++++++------------------- src/lib.rs | 3 +++ 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e4e8899..70b41b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,9 +24,9 @@ path = "bin/submit_transaction.rs" [dependencies] init4-bin-base = "0.1.0" -zenith-types = "0.13" +zenith-types = "0.15" -alloy = { version = "0.7.3", features = ["full", "json-rpc", "signer-aws", "rpc-types-mev", "rlp"] } +alloy = { version = "=0.11.1", features = ["full", "json-rpc", "signer-aws", "rpc-types-mev", "rlp", "node-bindings"] } aws-config = "1.1.7" aws-sdk-kms = "1.15.0" @@ -48,3 +48,5 @@ tokio = { version = "1.36.0", features = ["full", "macros", "rt-multi-thread"] } async-trait = "0.1.80" oauth2 = "4.4.2" +metrics = "0.24.1" +metrics-exporter-prometheus = "0.16.0" diff --git a/bin/submit_transaction.rs b/bin/submit_transaction.rs index 3955a49..708a37b 100644 --- a/bin/submit_transaction.rs +++ b/bin/submit_transaction.rs @@ -82,7 +82,6 @@ async fn connect_from_config() -> (Provider, Address, u64) { let signer = AwsSigner::new(client, kms_key_id.to_string(), Some(chain_id)).await.unwrap(); let provider = ProviderBuilder::new() - .with_recommended_fillers() .wallet(EthereumWallet::from(signer)) .on_builtin(&rpc_url) .await diff --git a/src/config.rs b/src/config.rs index 1e8a6fa..cb209ea 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,9 +9,9 @@ use alloy::{ WalletFiller, }, }, - transports::BoxTransport, }; use std::{borrow::Cow, env, num, str::FromStr}; + use zenith_types::Zenith; // Keys for .env variables that need to be set to configure the builder. @@ -125,7 +125,7 @@ impl ConfigError { } } -/// Provider type used to read & write. +/// Defines a full provider pub type Provider = FillProvider< JoinFill< JoinFill< @@ -134,26 +134,22 @@ pub type Provider = FillProvider< >, WalletFiller, >, - RootProvider, - BoxTransport, + RootProvider, Ethereum, >; -/// Provider type used to read-only. +/// Defines a read-only wallet pub type WalletlessProvider = FillProvider< JoinFill< Identity, JoinFill>>, >, - RootProvider, - BoxTransport, + RootProvider, Ethereum, >; -/// A Zenith contract instance, using some provider `P` (defaults to -/// [`Provider`]). -pub type ZenithInstance

= - Zenith::ZenithInstance; +/// Defines a [`Zenith`] instance that is generic over [`Provider`] +pub type ZenithInstance = Zenith::ZenithInstance<(), Provider, alloy::network::Ethereum>; impl BuilderConfig { /// Load the builder configuration from environment variables. @@ -210,32 +206,36 @@ impl BuilderConfig { /// Connect to the Rollup rpc provider. pub async fn connect_ru_provider(&self) -> Result { - ProviderBuilder::new() - .with_recommended_fillers() + let provider = ProviderBuilder::new() .on_builtin(&self.ru_rpc_url) .await - .map_err(Into::into) + .map_err(ConfigError::Provider)?; + + Ok(provider) } /// Connect to the Host rpc provider. pub async fn connect_host_provider(&self) -> Result { let builder_signer = self.connect_builder_signer().await?; - ProviderBuilder::new() - .with_recommended_fillers() + let provider = ProviderBuilder::new() .wallet(EthereumWallet::from(builder_signer)) .on_builtin(&self.host_rpc_url) .await - .map_err(Into::into) + .map_err(ConfigError::Provider)?; + + Ok(provider) } - /// Connect additional broadcast providers. + /// Connect additionally configured non-host providers to broadcast transactions to. pub async fn connect_additional_broadcast( &self, - ) -> Result>, ConfigError> { - let mut providers = Vec::with_capacity(self.tx_broadcast_urls.len()); + ) -> Result, ConfigError> { + let mut providers: Vec = + Vec::with_capacity(self.tx_broadcast_urls.len()); for url in self.tx_broadcast_urls.iter() { let provider = - ProviderBuilder::new().on_builtin(url).await.map_err(Into::::into)?; + ProviderBuilder::new().on_builtin(url).await.map_err(ConfigError::Provider)?; + providers.push(provider); } Ok(providers) @@ -278,5 +278,6 @@ pub fn load_url(key: &str) -> Result, ConfigError> { /// Load an address from an environment variable. pub fn load_address(key: &str) -> Result { let address = load_string(key)?; - Address::from_str(&address).map_err(Into::into) + Address::from_str(&address) + .map_err(|_| ConfigError::Var(format!("Invalid address format for {}", key))) } diff --git a/src/lib.rs b/src/lib.rs index 5d4e743..562d3c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,4 +27,7 @@ pub mod tasks; /// Utilities. pub mod utils; +/// Anonymous crate dependency imports. +use metrics as _; +use metrics_exporter_prometheus as _; use openssl as _; From b1324b0505daca1b8ffad81d36663e8529dc0224 Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 17 Mar 2025 12:36:59 -0600 Subject: [PATCH 10/53] refactors to account for init4 bin base in builder craate --- Cargo.toml | 2 -- src/config.rs | 8 ++++---- src/lib.rs | 2 -- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 70b41b2..d1b0f34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,5 +48,3 @@ tokio = { version = "1.36.0", features = ["full", "macros", "rt-multi-thread"] } async-trait = "0.1.80" oauth2 = "4.4.2" -metrics = "0.24.1" -metrics-exporter-prometheus = "0.16.0" diff --git a/src/config.rs b/src/config.rs index cb209ea..5fe6d36 100644 --- a/src/config.rs +++ b/src/config.rs @@ -125,7 +125,7 @@ impl ConfigError { } } -/// Defines a full provider +/// Defines a full provider. pub type Provider = FillProvider< JoinFill< JoinFill< @@ -138,7 +138,7 @@ pub type Provider = FillProvider< Ethereum, >; -/// Defines a read-only wallet +/// Defines a provider type used to read-only. pub type WalletlessProvider = FillProvider< JoinFill< Identity, @@ -149,7 +149,7 @@ pub type WalletlessProvider = FillProvider< >; /// Defines a [`Zenith`] instance that is generic over [`Provider`] -pub type ZenithInstance = Zenith::ZenithInstance<(), Provider, alloy::network::Ethereum>; +pub type ZenithInstance

= Zenith::ZenithInstance<(), P, alloy::network::Ethereum>; impl BuilderConfig { /// Load the builder configuration from environment variables. @@ -226,7 +226,7 @@ impl BuilderConfig { Ok(provider) } - /// Connect additionally configured non-host providers to broadcast transactions to. + /// Connect additional broadcast providers. pub async fn connect_additional_broadcast( &self, ) -> Result, ConfigError> { diff --git a/src/lib.rs b/src/lib.rs index 562d3c2..3ab4b90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,4 @@ pub mod tasks; pub mod utils; /// Anonymous crate dependency imports. -use metrics as _; -use metrics_exporter_prometheus as _; use openssl as _; From 976dd03c328feb330a159883c860a3fccecf282a Mon Sep 17 00:00:00 2001 From: dylan Date: Thu, 27 Mar 2025 12:58:32 -0600 Subject: [PATCH 11/53] use signet-sdk - align to alloy @ 0.12.6 - transfer off of zenith-rs and use signet-sdk instead --- Cargo.toml | 20 +++++++++++++++----- bin/submit_transaction.rs | 2 +- src/config.rs | 8 ++++---- src/tasks/block.rs | 11 ++++++----- src/tasks/bundler.rs | 2 +- src/tasks/submit.rs | 16 ++++++++-------- 6 files changed, 35 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d1b0f34..8d7a7a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,11 +22,21 @@ name = "transaction-submitter" path = "bin/submit_transaction.rs" [dependencies] -init4-bin-base = "0.1.0" - -zenith-types = "0.15" - -alloy = { version = "=0.11.1", features = ["full", "json-rpc", "signer-aws", "rpc-types-mev", "rlp", "node-bindings"] } +init4-bin-base = { path = "../bin-base" } + +signet-bundle = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/update-trevm" } +signet-types = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/update-trevm" } +signet-zenith = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/update-trevm" } + +alloy = { version = "0.12.6", features = [ + "full", + "json-rpc", + "signer-aws", + "rpc-types-mev", + "rlp", + "node-bindings", + "serde", +] } aws-config = "1.1.7" aws-sdk-kms = "1.15.0" diff --git a/bin/submit_transaction.rs b/bin/submit_transaction.rs index 708a37b..addb205 100644 --- a/bin/submit_transaction.rs +++ b/bin/submit_transaction.rs @@ -83,7 +83,7 @@ async fn connect_from_config() -> (Provider, Address, u64) { let provider = ProviderBuilder::new() .wallet(EthereumWallet::from(signer)) - .on_builtin(&rpc_url) + .connect(&rpc_url) .await .unwrap(); diff --git a/src/config.rs b/src/config.rs index 5fe6d36..6864b84 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,7 +12,7 @@ use alloy::{ }; use std::{borrow::Cow, env, num, str::FromStr}; -use zenith_types::Zenith; +use signet_zenith::Zenith; // Keys for .env variables that need to be set to configure the builder. const HOST_CHAIN_ID: &str = "HOST_CHAIN_ID"; @@ -207,7 +207,7 @@ impl BuilderConfig { /// Connect to the Rollup rpc provider. pub async fn connect_ru_provider(&self) -> Result { let provider = ProviderBuilder::new() - .on_builtin(&self.ru_rpc_url) + .connect(&self.ru_rpc_url) .await .map_err(ConfigError::Provider)?; @@ -219,7 +219,7 @@ impl BuilderConfig { let builder_signer = self.connect_builder_signer().await?; let provider = ProviderBuilder::new() .wallet(EthereumWallet::from(builder_signer)) - .on_builtin(&self.host_rpc_url) + .connect(&self.host_rpc_url) .await .map_err(ConfigError::Provider)?; @@ -234,7 +234,7 @@ impl BuilderConfig { Vec::with_capacity(self.tx_broadcast_urls.len()); for url in self.tx_broadcast_urls.iter() { let provider = - ProviderBuilder::new().on_builtin(url).await.map_err(ConfigError::Provider)?; + ProviderBuilder::new().connect(url).await.map_err(ConfigError::Provider)?; providers.push(provider); } diff --git a/src/tasks/block.rs b/src/tasks/block.rs index e750696..dd1a050 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -3,7 +3,7 @@ use super::oauth::Authenticator; use super::tx_poller::TxPoller; use crate::config::{BuilderConfig, WalletlessProvider}; use alloy::{ - consensus::{SidecarBuilder, SidecarCoder, TxEnvelope}, + consensus::{SidecarBuilder, SidecarCoder, transaction::TxEnvelope}, eips::eip2718::Decodable2718, primitives::{B256, Bytes, keccak256}, providers::Provider as _, @@ -13,7 +13,8 @@ use std::time::{SystemTime, UNIX_EPOCH}; use std::{sync::OnceLock, time::Duration}; use tokio::{sync::mpsc, task::JoinHandle}; use tracing::{Instrument, debug, error, info, trace}; -use zenith_types::{Alloy2718Coder, ZenithEthBundle, encode_txns}; +use signet_zenith::{Alloy2718Coder, encode_txns}; +use signet_bundle::SignetEthBundle; /// Ethereum's slot time in seconds. pub const ETHEREUM_SLOT_TIME: u64 = 12; @@ -183,7 +184,7 @@ impl BlockBuilder { } /// Simulates a Zenith bundle against the rollup state - async fn simulate_bundle(&mut self, bundle: &ZenithEthBundle) -> eyre::Result<()> { + async fn simulate_bundle(&mut self, bundle: &SignetEthBundle) -> eyre::Result<()> { // TODO: Simulate bundles with the Simulation Engine // [ENG-672](https://linear.app/initiates/issue/ENG-672/add-support-for-bundles) debug!(hash = ?bundle.bundle.bundle_hash(), block_number = ?bundle.block_number(), "bundle simulations is not implemented yet - skipping simulation"); @@ -269,7 +270,7 @@ mod tests { rpc::types::{TransactionRequest, mev::EthSendBundle}, signers::local::PrivateKeySigner, }; - use zenith_types::ZenithEthBundle; + use signet_bundle::SignetEthBundle; /// Create a mock bundle for testing with a single transaction async fn create_mock_bundle(wallet: &EthereumWallet) -> Bundle { @@ -294,7 +295,7 @@ mod tests { replacement_uuid: Some("replacement_uuid".to_owned()), }; - let zenith_bundle = ZenithEthBundle { bundle: eth_bundle, host_fills: None }; + let zenith_bundle = SignetEthBundle { bundle: eth_bundle, host_fills: None }; Bundle { id: "mock_bundle".to_owned(), bundle: zenith_bundle } } diff --git a/src/tasks/bundler.rs b/src/tasks/bundler.rs index acc3cae..753ddb3 100644 --- a/src/tasks/bundler.rs +++ b/src/tasks/bundler.rs @@ -16,7 +16,7 @@ pub struct Bundle { /// Cache identifier for the bundle pub id: String, /// The Zenith bundle for this bundle - pub bundle: ZenithEthBundle, + pub bundle: SignetEthBundle, } /// Response from the tx-pool containing a list of bundles. diff --git a/src/tasks/submit.rs b/src/tasks/submit.rs index 0f1b0bf..558167f 100644 --- a/src/tasks/submit.rs +++ b/src/tasks/submit.rs @@ -18,14 +18,14 @@ use alloy::{ use eyre::{bail, eyre}; use init4_bin_base::deps::metrics::{counter, histogram}; use oauth2::TokenResponse; +use signet_types::{SignRequest, SignResponse}; +use signet_zenith::{ + BundleHelper::{self, BlockHeader, FillPermit2, submitCall}, + Zenith::IncorrectHostBlock, +}; use std::time::Instant; use tokio::{sync::mpsc, task::JoinHandle}; use tracing::{debug, error, instrument, trace}; -use zenith_types::{ - BundleHelper::{self, FillPermit2}, - SignRequest, SignResponse, - Zenith::IncorrectHostBlock, -}; macro_rules! spawn_provider_send { ($provider:expr, $tx:expr) => { @@ -127,7 +127,7 @@ impl SubmitTask { s: FixedBytes<32>, in_progress: &InProgressBlock, ) -> eyre::Result { - let data = zenith_types::BundleHelper::submitCall { fills, header, v, r, s }.abi_encode(); + let data = submitCall { fills, header, v, r, s }.abi_encode(); let sidecar = in_progress.encode_blob::().build()?; Ok(TransactionRequest::default() @@ -151,7 +151,7 @@ impl SubmitTask { ) -> eyre::Result { let (v, r, s) = extract_signature_components(&resp.sig); - let header = zenith_types::BundleHelper::BlockHeader { + let header = BlockHeader { hostBlockNumber: resp.req.host_block_number, rollupChainId: U256::from(self.config.ru_chain_id), gasLimit: resp.req.gas_limit, @@ -167,7 +167,7 @@ impl SubmitTask { .with_gas_limit(1_000_000); if let Err(TransportError::ErrorResp(e)) = - self.host_provider.call(&tx).block(BlockNumberOrTag::Pending.into()).await + self.host_provider.call(tx.clone()).block(BlockNumberOrTag::Pending.into()).await { error!( code = e.code, From b22999627ecc2641485ed791b9036a1001311996 Mon Sep 17 00:00:00 2001 From: dylan Date: Thu, 27 Mar 2025 15:18:21 -0600 Subject: [PATCH 12/53] fix: update bin-base import --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8d7a7a9..b9231f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,11 +22,11 @@ name = "transaction-submitter" path = "bin/submit_transaction.rs" [dependencies] -init4-bin-base = { path = "../bin-base" } +init4-bin-base = { git = "https://github.com/init4tech/bin-base.git" } -signet-bundle = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/update-trevm" } -signet-types = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/update-trevm" } -signet-zenith = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/update-trevm" } +signet-bundle = { git = "https://github.com/init4tech/signet-sdk", branch = "main" } +signet-types = { git = "https://github.com/init4tech/signet-sdk", branch = "main" } +signet-zenith = { git = "https://github.com/init4tech/signet-sdk", branch = "main" } alloy = { version = "0.12.6", features = [ "full", From bafe07f43132742c1f5074f942fb0779617bffa6 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 7 Apr 2025 13:08:01 +0200 Subject: [PATCH 13/53] nits: doc updates --- src/config.rs | 9 ++++----- src/lib.rs | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/config.rs b/src/config.rs index 6864b84..705a567 100644 --- a/src/config.rs +++ b/src/config.rs @@ -10,9 +10,8 @@ use alloy::{ }, }, }; -use std::{borrow::Cow, env, num, str::FromStr}; - use signet_zenith::Zenith; +use std::{borrow::Cow, env, num, str::FromStr}; // Keys for .env variables that need to be set to configure the builder. const HOST_CHAIN_ID: &str = "HOST_CHAIN_ID"; @@ -125,7 +124,7 @@ impl ConfigError { } } -/// Defines a full provider. +/// Type alias for the provider used in the builder. pub type Provider = FillProvider< JoinFill< JoinFill< @@ -138,7 +137,7 @@ pub type Provider = FillProvider< Ethereum, >; -/// Defines a provider type used to read-only. +/// Type alias for the provider used in the builder, without a wallet. pub type WalletlessProvider = FillProvider< JoinFill< Identity, @@ -148,7 +147,7 @@ pub type WalletlessProvider = FillProvider< Ethereum, >; -/// Defines a [`Zenith`] instance that is generic over [`Provider`] +/// A [`Zenith`] contract instance using [`Provider`] as the provider. pub type ZenithInstance

= Zenith::ZenithInstance<(), P, alloy::network::Ethereum>; impl BuilderConfig { diff --git a/src/lib.rs b/src/lib.rs index 3ab4b90..10eefeb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,5 +27,5 @@ pub mod tasks; /// Utilities. pub mod utils; -/// Anonymous crate dependency imports. +// Anonymous import suppresses warnings about unused imports. use openssl as _; From e0c79f7552918e8f302af071b9dfd654b19f681c Mon Sep 17 00:00:00 2001 From: James Date: Mon, 7 Apr 2025 13:29:58 +0200 Subject: [PATCH 14/53] lint: fmt --- src/tasks/block.rs | 4 ++-- src/tasks/bundler.rs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/tasks/block.rs b/src/tasks/block.rs index dd1a050..a0c9c87 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -9,12 +9,12 @@ use alloy::{ providers::Provider as _, rlp::Buf, }; +use signet_bundle::SignetEthBundle; +use signet_zenith::{Alloy2718Coder, encode_txns}; use std::time::{SystemTime, UNIX_EPOCH}; use std::{sync::OnceLock, time::Duration}; use tokio::{sync::mpsc, task::JoinHandle}; use tracing::{Instrument, debug, error, info, trace}; -use signet_zenith::{Alloy2718Coder, encode_txns}; -use signet_bundle::SignetEthBundle; /// Ethereum's slot time in seconds. pub const ETHEREUM_SLOT_TIME: u64 = 12; diff --git a/src/tasks/bundler.rs b/src/tasks/bundler.rs index 753ddb3..d3ec34e 100644 --- a/src/tasks/bundler.rs +++ b/src/tasks/bundler.rs @@ -8,8 +8,7 @@ use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}; use tokio::task::JoinHandle; use tokio::time; use tracing::{Instrument, debug, trace}; -use zenith_types::ZenithEthBundle; - +use signet_bundle::SignetEthBundle; /// Holds a bundle from the cache with a unique ID and a Zenith bundle. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Bundle { From 785172b5f509dfdd8d688236162ef7f2da46ff78 Mon Sep 17 00:00:00 2001 From: dylan Date: Tue, 15 Apr 2025 13:30:12 -0600 Subject: [PATCH 15/53] feat: implements simulation for block building --- Cargo.toml | 9 +- bin/builder.rs | 33 +++-- src/tasks/block.rs | 348 +++++++++++--------------------------------- src/tasks/submit.rs | 18 +-- 4 files changed, 119 insertions(+), 289 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b9231f6..ed7dfbd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,9 +24,12 @@ path = "bin/submit_transaction.rs" [dependencies] init4-bin-base = { git = "https://github.com/init4tech/bin-base.git" } -signet-bundle = { git = "https://github.com/init4tech/signet-sdk", branch = "main" } -signet-types = { git = "https://github.com/init4tech/signet-sdk", branch = "main" } -signet-zenith = { git = "https://github.com/init4tech/signet-sdk", branch = "main" } +signet-zenith = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/sim-crate" } +signet-types = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/sim-crate" } +signet-bundle = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/sim-crate" } +signet-sim = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/sim-crate" } + +trevm = { version = "0.20.10", features = ["concurrent-db", "test-utils"] } alloy = { version = "0.12.6", features = [ "full", diff --git a/bin/builder.rs b/bin/builder.rs index 193a3b9..d1d8c3f 100644 --- a/bin/builder.rs +++ b/bin/builder.rs @@ -1,12 +1,11 @@ -#![allow(dead_code)] - -use builder::config::BuilderConfig; -use builder::service::serve_builder_with_span; -use builder::tasks::block::BlockBuilder; -use builder::tasks::metrics::MetricsTask; -use builder::tasks::oauth::Authenticator; -use builder::tasks::submit::SubmitTask; - +use builder::{ + config::BuilderConfig, + service::serve_builder_with_span, + tasks::{ + block::BlockBuilder, bundler, metrics::MetricsTask, oauth::Authenticator, + submit::SubmitTask, tx_poller, + }, +}; use tokio::select; #[tokio::main] @@ -39,14 +38,28 @@ async fn main() -> eyre::Result<()> { outbound_tx_channel: tx_channel, }; + let tx_poller = tx_poller::TxPoller::new(&config); + let (tx_receiver, tx_poller_jh) = tx_poller.spawn(); + + let bundle_poller = bundler::BundlePoller::new(&config, authenticator.clone()); + let (bundle_receiver, bundle_poller_jh) = bundle_poller.spawn(); + let authenticator_jh = authenticator.spawn(); + let (submit_channel, submit_jh) = submit.spawn(); - let build_jh = builder.spawn(submit_channel); + + let build_jh = builder.spawn(ru_provider, tx_receiver, bundle_receiver, submit_channel); let port = config.builder_port; let server = serve_builder_with_span(([0, 0, 0, 0], port), span); select! { + _ = tx_poller_jh => { + tracing::info!("tx_poller finished"); + }, + _ = bundle_poller_jh => { + tracing::info!("bundle_poller finished"); + }, _ = submit_jh => { tracing::info!("submit finished"); }, diff --git a/src/tasks/block.rs b/src/tasks/block.rs index a0c9c87..7192789 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -1,122 +1,24 @@ -use super::bundler::{Bundle, BundlePoller}; -use super::oauth::Authenticator; -use super::tx_poller::TxPoller; -use crate::config::{BuilderConfig, WalletlessProvider}; -use alloy::{ - consensus::{SidecarBuilder, SidecarCoder, transaction::TxEnvelope}, - eips::eip2718::Decodable2718, - primitives::{B256, Bytes, keccak256}, - providers::Provider as _, - rlp::Buf, +use crate::{ + config::{BuilderConfig, WalletlessProvider}, + tasks::{bundler::{BundlePoller, Bundle}, oauth::Authenticator, tx_poller::TxPoller}, +}; +use alloy::{consensus::TxEnvelope, eips::BlockId, genesis::Genesis}; +use signet_sim::{BlockBuild, BuiltBlock, SimCache}; +use signet_types::config::SignetSystemConstants; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use tokio::{select, sync::mpsc, task::JoinHandle}; +use tracing::{Instrument, info}; +use trevm::{ + NoopBlock, NoopCfg, + revm::{ + database::{AlloyDB, CacheDB, WrapDatabaseAsync}, + inspector::NoOpInspector, + }, }; -use signet_bundle::SignetEthBundle; -use signet_zenith::{Alloy2718Coder, encode_txns}; -use std::time::{SystemTime, UNIX_EPOCH}; -use std::{sync::OnceLock, time::Duration}; -use tokio::{sync::mpsc, task::JoinHandle}; -use tracing::{Instrument, debug, error, info, trace}; /// Ethereum's slot time in seconds. pub const ETHEREUM_SLOT_TIME: u64 = 12; -#[derive(Debug, Default, Clone)] -/// A block in progress. -pub struct InProgressBlock { - transactions: Vec, - raw_encoding: OnceLock, - hash: OnceLock, -} - -impl InProgressBlock { - /// Create a new `InProgressBlock` - pub const fn new() -> Self { - Self { transactions: Vec::new(), raw_encoding: OnceLock::new(), hash: OnceLock::new() } - } - - /// Get the number of transactions in the block. - pub fn len(&self) -> usize { - self.transactions.len() - } - - /// Check if the block is empty. - pub fn is_empty(&self) -> bool { - self.transactions.is_empty() - } - - /// Unseal the block - fn unseal(&mut self) { - self.raw_encoding.take(); - self.hash.take(); - } - - /// Seal the block by encoding the transactions and calculating the contentshash. - fn seal(&self) { - self.raw_encoding.get_or_init(|| encode_txns::(&self.transactions).into()); - self.hash.get_or_init(|| keccak256(self.raw_encoding.get().unwrap().as_ref())); - } - - /// Ingest a transaction into the in-progress block. Fails - pub fn ingest_tx(&mut self, tx: &TxEnvelope) { - trace!(hash = %tx.tx_hash(), "ingesting tx"); - self.unseal(); - self.transactions.push(tx.clone()); - } - - /// Remove a transaction from the in-progress block. - pub fn remove_tx(&mut self, tx: &TxEnvelope) { - trace!(hash = %tx.tx_hash(), "removing tx"); - self.unseal(); - self.transactions.retain(|t| t.tx_hash() != tx.tx_hash()); - } - - /// Ingest a bundle into the in-progress block. - /// Ignores Signed Orders for now. - pub fn ingest_bundle(&mut self, bundle: Bundle) { - trace!(bundle = %bundle.id, "ingesting bundle"); - - let txs = bundle - .bundle - .bundle - .txs - .into_iter() - .map(|tx| TxEnvelope::decode_2718(&mut tx.chunk())) - .collect::, _>>(); - - if let Ok(txs) = txs { - self.unseal(); - // extend the transactions with the decoded transactions. - // As this builder does not provide bundles landing "top of block", its fine to just extend. - self.transactions.extend(txs); - } else { - error!("failed to decode bundle. dropping"); - } - } - - /// Encode the in-progress block - fn encode_raw(&self) -> &Bytes { - self.seal(); - self.raw_encoding.get().unwrap() - } - - /// Calculate the hash of the in-progress block, finishing the block. - pub fn contents_hash(&self) -> B256 { - self.seal(); - *self.hash.get().unwrap() - } - - /// Convert the in-progress block to sign request contents. - pub fn encode_calldata(&self) -> &Bytes { - self.encode_raw() - } - - /// Convert the in-progress block to a blob transaction sidecar. - pub fn encode_blob(&self) -> SidecarBuilder { - let mut coder = SidecarBuilder::::default(); - coder.ingest(self.encode_raw()); - coder - } -} - /// BlockBuilder is a task that periodically builds a block then sends it for /// signing and submission. #[derive(Debug)] @@ -146,74 +48,9 @@ impl BlockBuilder { } } - /// Fetches transactions from the cache and ingests them into the in - /// progress block - async fn get_transactions(&mut self, in_progress: &mut InProgressBlock) { - trace!("query transactions from cache"); - let txns = self.tx_poller.check_tx_cache().await; - match txns { - Ok(txns) => { - trace!("got transactions response"); - for txn in txns.into_iter() { - in_progress.ingest_tx(&txn); - } - } - Err(e) => { - error!(error = %e, "error polling transactions"); - } - } - } - - /// Fetches bundles from the cache and ingests them into the in progress block - async fn get_bundles(&mut self, in_progress: &mut InProgressBlock) { - trace!("query bundles from cache"); - let bundles = self.bundle_poller.check_bundle_cache().await; - match bundles { - Ok(bundles) => { - for bundle in bundles { - match self.simulate_bundle(&bundle.bundle).await { - Ok(()) => in_progress.ingest_bundle(bundle.clone()), - Err(e) => error!(error = %e, id = ?bundle.id, "bundle simulation failed"), - } - } - } - Err(e) => { - error!(error = %e, "error polling bundles"); - } - } - } - - /// Simulates a Zenith bundle against the rollup state - async fn simulate_bundle(&mut self, bundle: &SignetEthBundle) -> eyre::Result<()> { - // TODO: Simulate bundles with the Simulation Engine - // [ENG-672](https://linear.app/initiates/issue/ENG-672/add-support-for-bundles) - debug!(hash = ?bundle.bundle.bundle_hash(), block_number = ?bundle.block_number(), "bundle simulations is not implemented yet - skipping simulation"); - Ok(()) - } - - async fn filter_transactions(&self, in_progress: &mut InProgressBlock) { - // query the rollup node to see which transaction(s) have been included - let mut confirmed_transactions = Vec::new(); - for transaction in in_progress.transactions.iter() { - let tx = self - .ru_provider - .get_transaction_by_hash(*transaction.tx_hash()) - .await - .expect("failed to get receipt"); - if tx.is_some() { - confirmed_transactions.push(transaction.clone()); - } - } - trace!(confirmed = confirmed_transactions.len(), "found confirmed transactions"); - - // remove already-confirmed transactions - for transaction in confirmed_transactions { - in_progress.remove_tx(&transaction); - } - } - // calculate the duration in seconds until the beginning of the next block slot. fn secs_to_next_slot(&self) -> u64 { + // TODO: Account for multiple builders and return the time to our next _assigned_ slot here. let curr_timestamp: u64 = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); let current_slot_time = (curr_timestamp - self.config.chain_offset) % ETHEREUM_SLOT_TIME; (ETHEREUM_SLOT_TIME - current_slot_time) % ETHEREUM_SLOT_TIME @@ -226,32 +63,77 @@ impl BlockBuilder { /// Spawn the block builder task, returning the inbound channel to it, and /// a handle to the running task. - pub fn spawn(mut self, outbound: mpsc::UnboundedSender) -> JoinHandle<()> { + pub fn spawn( + self, + ru_provider: WalletlessProvider, + mut tx_receiver: mpsc::UnboundedReceiver, + mut bundle_receiver: mpsc::UnboundedReceiver, + outbound: mpsc::UnboundedSender, + ) -> JoinHandle<()> { + let genesis = Genesis::default(); + let constants = SignetSystemConstants::try_from_genesis(&genesis).unwrap(); // TODO: get from actual genesis file. + let concurrency_limit = 1000; + let max_gas = 30_000_000; + tokio::spawn( async move { loop { - // sleep the buffer time + // setup during slot time dead zone + let alloy_db = AlloyDB::new(ru_provider.clone(), BlockId::from(1)); + let wrapped_db = WrapDatabaseAsync::new(alloy_db).unwrap(); + let cached_db = CacheDB::new(wrapped_db); + + let sim_items = SimCache::new(); + + let deadline = Instant::now() + .checked_add(Duration::from_secs(self.secs_to_next_target())) + .unwrap(); + + let block_builder: BlockBuild<_, NoOpInspector> = BlockBuild::new( + cached_db, + constants, + NoopCfg, + NoopBlock, + deadline, + concurrency_limit, + sim_items.clone(), + max_gas, + ); + + // sleep until next buffer time tokio::time::sleep(Duration::from_secs(self.secs_to_next_target())).await; info!("beginning block build cycle"); - // Build a block - let mut in_progress = InProgressBlock::default(); - self.get_transactions(&mut in_progress).await; - self.get_bundles(&mut in_progress).await; - - // Filter confirmed transactions from the block - self.filter_transactions(&mut in_progress).await; - - // submit the block if it has transactions - if !in_progress.is_empty() { - debug!(txns = in_progress.len(), "sending block to submit task"); - let in_progress_block = std::mem::take(&mut in_progress); - if outbound.send(in_progress_block).is_err() { - error!("downstream task gone"); - break; + tokio::spawn({ + let outbound = outbound.clone(); + async move { + let block = block_builder.build().await; + if let Err(e) = outbound.send(block) { + println!("failed to send built block: {}", e); + tracing::error!(error = %e, "failed to send built block"); + } else { + info!("block build cycle complete"); + } + } + }); + + // Feed it transactions and bundles until deadline + loop { + select! { + tx = tx_receiver.recv() => { + if let Some(tx) = tx { + sim_items.add_item(tx); + } + } + bundle = bundle_receiver.recv() => { + if let Some(bundle) = bundle { + sim_items.add_item(bundle.bundle); + } + } + _ = tokio::time::sleep_until(deadline.into()) => { + break; + } } - } else { - debug!("no transactions, skipping block submission"); } } } @@ -259,71 +141,3 @@ impl BlockBuilder { ) } } - -#[cfg(test)] -mod tests { - use super::*; - use alloy::primitives::Address; - use alloy::{ - eips::eip2718::Encodable2718, - network::{EthereumWallet, TransactionBuilder}, - rpc::types::{TransactionRequest, mev::EthSendBundle}, - signers::local::PrivateKeySigner, - }; - use signet_bundle::SignetEthBundle; - - /// Create a mock bundle for testing with a single transaction - async fn create_mock_bundle(wallet: &EthereumWallet) -> Bundle { - let tx = TransactionRequest::default() - .to(Address::ZERO) - .from(wallet.default_signer().address()) - .nonce(1) - .max_fee_per_gas(2) - .max_priority_fee_per_gas(3) - .gas_limit(4) - .build(wallet) - .await - .unwrap() - .encoded_2718(); - - let eth_bundle = EthSendBundle { - txs: vec![tx.into()], - block_number: 1, - min_timestamp: Some(u64::MIN), - max_timestamp: Some(u64::MAX), - reverting_tx_hashes: vec![], - replacement_uuid: Some("replacement_uuid".to_owned()), - }; - - let zenith_bundle = SignetEthBundle { bundle: eth_bundle, host_fills: None }; - - Bundle { id: "mock_bundle".to_owned(), bundle: zenith_bundle } - } - - #[tokio::test] - async fn test_ingest_bundle() { - // Setup random creds - let signer = PrivateKeySigner::random(); - let wallet = EthereumWallet::from(signer); - - // Create an empty InProgressBlock and bundle - let mut in_progress_block = InProgressBlock::new(); - let bundle = create_mock_bundle(&wallet).await; - - // Save previous hash for comparison - let prev_hash = in_progress_block.contents_hash(); - - // Ingest the bundle - in_progress_block.ingest_bundle(bundle); - - // Assert hash is changed after ingest - assert_ne!(prev_hash, in_progress_block.contents_hash(), "Bundle should change block hash"); - - // Assert that the transaction was persisted into block - assert_eq!(in_progress_block.len(), 1, "Bundle should be persisted"); - - // Assert that the block is properly sealed - let raw_encoding = in_progress_block.encode_raw(); - assert!(!raw_encoding.is_empty(), "Raw encoding should not be empty"); - } -} diff --git a/src/tasks/submit.rs b/src/tasks/submit.rs index 558167f..773e76d 100644 --- a/src/tasks/submit.rs +++ b/src/tasks/submit.rs @@ -1,9 +1,9 @@ use crate::{ config::{Provider, ZenithInstance}, signer::LocalOrAws, - tasks::block::InProgressBlock, utils::extract_signature_components, }; +use signet_sim::BuiltBlock; use alloy::{ consensus::{SimpleCoder, constants::GWEI_TO_WEI}, eips::BlockNumberOrTag, @@ -103,7 +103,7 @@ impl SubmitTask { /// Constructs the signing request from the in-progress block passed to it and assigns the /// correct height, chain ID, gas limit, and rollup reward address. #[instrument(skip_all)] - async fn construct_sig_request(&self, contents: &InProgressBlock) -> eyre::Result { + async fn construct_sig_request(&self, contents: &BuiltBlock) -> eyre::Result { let ru_chain_id = U256::from(self.config.ru_chain_id); let next_block_height = self.next_host_block_height().await?; @@ -125,7 +125,7 @@ impl SubmitTask { v: u8, r: FixedBytes<32>, s: FixedBytes<32>, - in_progress: &InProgressBlock, + in_progress: &BuiltBlock, ) -> eyre::Result { let data = submitCall { fills, header, v, r, s }.abi_encode(); @@ -147,7 +147,7 @@ impl SubmitTask { async fn submit_transaction( &self, resp: &SignResponse, - in_progress: &InProgressBlock, + in_progress: &BuiltBlock, ) -> eyre::Result { let (v, r, s) = extract_signature_components(&resp.sig); @@ -233,9 +233,9 @@ impl SubmitTask { } #[instrument(skip_all, err)] - async fn handle_inbound(&self, in_progress: &InProgressBlock) -> eyre::Result { - tracing::info!(txns = in_progress.len(), "handling inbound block"); - let sig_request = match self.construct_sig_request(in_progress).await { + async fn handle_inbound(&self, block: &BuiltBlock) -> eyre::Result { + tracing::info!(txns = block.tx_count(), "handling inbound block"); + let sig_request = match self.construct_sig_request(block).await { Ok(sig_request) => sig_request, Err(e) => { tracing::error!(error = %e, "error constructing signature request"); @@ -274,11 +274,11 @@ impl SubmitTask { resp }; - self.submit_transaction(&signed, in_progress).await + self.submit_transaction(&signed, block).await } /// Spawns the in progress block building task - pub fn spawn(self) -> (mpsc::UnboundedSender, JoinHandle<()>) { + pub fn spawn(self) -> (mpsc::UnboundedSender, JoinHandle<()>) { let (sender, mut inbound) = mpsc::unbounded_channel(); let handle = tokio::spawn(async move { loop { From ff5cb3151a14aa3f8381ddb675537710671b87d9 Mon Sep 17 00:00:00 2001 From: dylan Date: Tue, 15 Apr 2025 13:40:12 -0600 Subject: [PATCH 16/53] lint: fmt --- src/tasks/block.rs | 6 +++++- src/tasks/bundler.rs | 2 +- src/tasks/submit.rs | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/tasks/block.rs b/src/tasks/block.rs index 7192789..120dd31 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -1,6 +1,10 @@ use crate::{ config::{BuilderConfig, WalletlessProvider}, - tasks::{bundler::{BundlePoller, Bundle}, oauth::Authenticator, tx_poller::TxPoller}, + tasks::{ + bundler::{Bundle, BundlePoller}, + oauth::Authenticator, + tx_poller::TxPoller, + }, }; use alloy::{consensus::TxEnvelope, eips::BlockId, genesis::Genesis}; use signet_sim::{BlockBuild, BuiltBlock, SimCache}; diff --git a/src/tasks/bundler.rs b/src/tasks/bundler.rs index d3ec34e..d1b1220 100644 --- a/src/tasks/bundler.rs +++ b/src/tasks/bundler.rs @@ -4,11 +4,11 @@ use crate::tasks::oauth::Authenticator; use oauth2::TokenResponse; use reqwest::Url; use serde::{Deserialize, Serialize}; +use signet_bundle::SignetEthBundle; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}; use tokio::task::JoinHandle; use tokio::time; use tracing::{Instrument, debug, trace}; -use signet_bundle::SignetEthBundle; /// Holds a bundle from the cache with a unique ID and a Zenith bundle. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Bundle { diff --git a/src/tasks/submit.rs b/src/tasks/submit.rs index 773e76d..2b578cf 100644 --- a/src/tasks/submit.rs +++ b/src/tasks/submit.rs @@ -3,7 +3,6 @@ use crate::{ signer::LocalOrAws, utils::extract_signature_components, }; -use signet_sim::BuiltBlock; use alloy::{ consensus::{SimpleCoder, constants::GWEI_TO_WEI}, eips::BlockNumberOrTag, @@ -18,6 +17,7 @@ use alloy::{ use eyre::{bail, eyre}; use init4_bin_base::deps::metrics::{counter, histogram}; use oauth2::TokenResponse; +use signet_sim::BuiltBlock; use signet_types::{SignRequest, SignResponse}; use signet_zenith::{ BundleHelper::{self, BlockHeader, FillPermit2, submitCall}, From a142314cad407ec240ec7e0f5e62c82ddb53f28f Mon Sep 17 00:00:00 2001 From: dylan Date: Tue, 15 Apr 2025 13:47:32 -0600 Subject: [PATCH 17/53] cleanup --- src/tasks/bundler.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tasks/bundler.rs b/src/tasks/bundler.rs index d1b1220..9b526fc 100644 --- a/src/tasks/bundler.rs +++ b/src/tasks/bundler.rs @@ -12,9 +12,9 @@ use tracing::{Instrument, debug, trace}; /// Holds a bundle from the cache with a unique ID and a Zenith bundle. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Bundle { - /// Cache identifier for the bundle + /// Cache identifier for the bundle. pub id: String, - /// The Zenith bundle for this bundle + /// The corresponding Signet bundle. pub bundle: SignetEthBundle, } From 9d812ce88dd432df2c382f66795c4bde238c98c7 Mon Sep 17 00:00:00 2001 From: dylan Date: Thu, 17 Apr 2025 15:53:49 -0600 Subject: [PATCH 18/53] WIP: adding a block builder loop test --- Cargo.toml | 8 +- bin/builder.rs | 10 +- src/config.rs | 41 +++++++- src/tasks/block.rs | 189 +++++++++++++++++++++++++----------- src/tasks/oauth.rs | 1 + tests/block_builder_test.rs | 113 +++++++++++++++++++++ tests/bundle_poller_test.rs | 1 + tests/tx_poller_test.rs | 1 + 8 files changed, 298 insertions(+), 66 deletions(-) create mode 100644 tests/block_builder_test.rs diff --git a/Cargo.toml b/Cargo.toml index ed7dfbd..8ba9557 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,10 +24,10 @@ path = "bin/submit_transaction.rs" [dependencies] init4-bin-base = { git = "https://github.com/init4tech/bin-base.git" } -signet-zenith = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/sim-crate" } -signet-types = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/sim-crate" } -signet-bundle = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/sim-crate" } -signet-sim = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/sim-crate" } +signet-zenith = { path = "../signet-sdk/crates/zenith" } +signet-types = { path = "../signet-sdk/crates/types" } +signet-bundle = { path = "../signet-sdk/crates/bundle" } +signet-sim = { path = "../signet-sdk/crates/sim" } trevm = { version = "0.20.10", features = ["concurrent-db", "test-utils"] } diff --git a/bin/builder.rs b/bin/builder.rs index d1d8c3f..750f234 100644 --- a/bin/builder.rs +++ b/bin/builder.rs @@ -15,13 +15,14 @@ async fn main() -> eyre::Result<()> { let span = tracing::info_span!("zenith-builder"); let config = BuilderConfig::load_from_env()?.clone(); - let host_provider = config.connect_host_provider().await?; - let ru_provider = config.connect_ru_provider().await?; + let constants = config.load_pecorino_constants(); let authenticator = Authenticator::new(&config); - tracing::debug!(rpc_url = config.host_rpc_url.as_ref(), "instantiated provider"); + let host_provider = config.connect_host_provider().await?; + let ru_provider = config.connect_ru_provider().await?; let sequencer_signer = config.connect_sequencer_signer().await?; + let zenith = config.connect_zenith(host_provider.clone()); let metrics = MetricsTask { host_provider: host_provider.clone() }; @@ -48,7 +49,8 @@ async fn main() -> eyre::Result<()> { let (submit_channel, submit_jh) = submit.spawn(); - let build_jh = builder.spawn(ru_provider, tx_receiver, bundle_receiver, submit_channel); + let build_jh = + builder.spawn(constants, ru_provider, tx_receiver, bundle_receiver, submit_channel); let port = config.builder_port; let server = serve_builder_with_span(([0, 0, 0, 0], port), span); diff --git a/src/config.rs b/src/config.rs index 705a567..0b34167 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,7 @@ use crate::signer::{LocalOrAws, SignerError}; use alloy::{ network::{Ethereum, EthereumWallet}, - primitives::Address, + primitives::{Address, address}, providers::{ Identity, ProviderBuilder, RootProvider, fillers::{ @@ -10,6 +10,8 @@ use alloy::{ }, }, }; +use eyre::Result; +use signet_types::config::{HostConfig, PredeployTokens, RollupConfig, SignetSystemConstants}; use signet_zenith::Zenith; use std::{borrow::Cow, env, num, str::FromStr}; @@ -37,6 +39,7 @@ const OAUTH_CLIENT_ID: &str = "OAUTH_CLIENT_ID"; const OAUTH_CLIENT_SECRET: &str = "OAUTH_CLIENT_SECRET"; const OAUTH_AUTHENTICATE_URL: &str = "OAUTH_AUTHENTICATE_URL"; const OAUTH_TOKEN_URL: &str = "OAUTH_TOKEN_URL"; +const CONCURRENCY_LIMIT: &str = "CONCURRENCY_LIMIT"; /// Configuration for a builder running a specific rollup on a specific host /// chain. @@ -92,6 +95,8 @@ pub struct BuilderConfig { pub oauth_token_url: String, /// The oauth token refresh interval in seconds. pub oauth_token_refresh_interval: u64, + /// The max number of simultaneous block simulations to run. + pub concurrency_limit: usize, } /// Error loading the configuration. @@ -115,6 +120,9 @@ pub enum ConfigError { /// Error connecting to the signer #[error("failed to connect to signer: {0}")] Signer(#[from] SignerError), + /// Error parsing the provided genesis file + #[error("failed")] + Genesis(String), } impl ConfigError { @@ -184,6 +192,7 @@ impl BuilderConfig { oauth_authenticate_url: load_string(OAUTH_AUTHENTICATE_URL)?, oauth_token_url: load_string(OAUTH_TOKEN_URL)?, oauth_token_refresh_interval: load_u64(AUTH_TOKEN_REFRESH_INTERVAL)?, + concurrency_limit: load_u64(CONCURRENCY_LIMIT).map(|v| v as usize).unwrap_or(1000), }) } @@ -244,6 +253,36 @@ impl BuilderConfig { pub const fn connect_zenith(&self, provider: Provider) -> ZenithInstance { Zenith::new(self.zenith_address, provider) } + + /// Loads the Signet system constants for Pecorino. + pub fn load_pecorino_constants(&self) -> SignetSystemConstants { + let host = HostConfig::new( + self.host_chain_id, + 149984, + self.zenith_address, + address!("0x4E8cC181805aFC307C83298242271142b8e2f249"), + address!("0xd553C4CA4792Af71F4B61231409eaB321c1Dd2Ce"), + address!("0x1af3A16857C28917Ab2C4c78Be099fF251669200"), + PredeployTokens::new( + address!("0x885F8DB528dC8a38aA3DDad9D3F619746B4a6A81"), + address!("0x7970D259D4a96764Fa9B23FF0715A35f06f52D1A"), + address!("0x7970D259D4a96764Fa9B23FF0715A35f06f52D1A"), + ), + ); + let rollup = RollupConfig::new( + self.ru_chain_id, + address!("0x4E8cC181805aFC307C83298242271142b8e2f249"), + address!("0xd553C4CA4792Af71F4B61231409eaB321c1Dd2Ce"), + address!("0xe0eDA3701D44511ce419344A4CeD30B52c9Ba231"), + PredeployTokens::new( + address!("0x0B8BC5e60EE10957E0d1A0d95598fA63E65605e2"), + address!("0xF34326d3521F1b07d1aa63729cB14A372f8A737C"), + address!("0xE3d7066115f7d6b65F88Dff86288dB4756a7D733"), + ), + ); + + SignetSystemConstants::new(host, rollup) + } } /// Load a string from an environment variable. diff --git a/src/tasks/block.rs b/src/tasks/block.rs index 120dd31..d9a87e3 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -6,16 +6,20 @@ use crate::{ tx_poller::TxPoller, }, }; -use alloy::{consensus::TxEnvelope, eips::BlockId, genesis::Genesis}; -use signet_sim::{BlockBuild, BuiltBlock, SimCache}; +use alloy::{consensus::TxEnvelope, eips::BlockId, providers::Provider}; +use signet_sim::{BlockBuild, BuiltBlock, SimCache, SimItem}; use signet_types::config::SignetSystemConstants; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; -use tokio::{select, sync::mpsc, task::JoinHandle}; +use tokio::{ + select, + sync::mpsc::{self, UnboundedReceiver}, + task::JoinHandle, +}; use tracing::{Instrument, info}; use trevm::{ NoopBlock, NoopCfg, revm::{ - database::{AlloyDB, CacheDB, WrapDatabaseAsync}, + database::{AlloyDB, WrapDatabaseAsync}, inspector::NoOpInspector, }, }; @@ -54,7 +58,6 @@ impl BlockBuilder { // calculate the duration in seconds until the beginning of the next block slot. fn secs_to_next_slot(&self) -> u64 { - // TODO: Account for multiple builders and return the time to our next _assigned_ slot here. let curr_timestamp: u64 = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); let current_slot_time = (curr_timestamp - self.config.chain_offset) % ETHEREUM_SLOT_TIME; (ETHEREUM_SLOT_TIME - current_slot_time) % ETHEREUM_SLOT_TIME @@ -69,49 +72,83 @@ impl BlockBuilder { /// a handle to the running task. pub fn spawn( self, + constants: SignetSystemConstants, ru_provider: WalletlessProvider, - mut tx_receiver: mpsc::UnboundedReceiver, + mut tx_receiver: UnboundedReceiver, mut bundle_receiver: mpsc::UnboundedReceiver, - outbound: mpsc::UnboundedSender, + block_sender: mpsc::UnboundedSender, ) -> JoinHandle<()> { - let genesis = Genesis::default(); - let constants = SignetSystemConstants::try_from_genesis(&genesis).unwrap(); // TODO: get from actual genesis file. - let concurrency_limit = 1000; - let max_gas = 30_000_000; - + println!("GOT HERE 0"); tokio::spawn( async move { + // Create a sim item handler + let sim_items = SimCache::new(); + println!("got here 1"); + + tokio::spawn({ + let sim_items = sim_items.clone(); + async move { + println!("starting up the receiver"); + loop { + select! { + tx = tx_receiver.recv() => { + if let Some(tx) = tx { + println!("received transaction {}", tx.hash()); + sim_items.add_item(signet_sim::SimItem::Tx(tx.into())); + } + } + bundle = bundle_receiver.recv() => { + if let Some(bundle) = bundle { + println!("received bundle {}", bundle.id); + sim_items.add_item(SimItem::Bundle(bundle.bundle)); + } + } + } + } + } + }); + + println!("starting the block builder loop"); + loop { - // setup during slot time dead zone - let alloy_db = AlloyDB::new(ru_provider.clone(), BlockId::from(1)); - let wrapped_db = WrapDatabaseAsync::new(alloy_db).unwrap(); - let cached_db = CacheDB::new(wrapped_db); - - let sim_items = SimCache::new(); - - let deadline = Instant::now() - .checked_add(Duration::from_secs(self.secs_to_next_target())) - .unwrap(); - - let block_builder: BlockBuild<_, NoOpInspector> = BlockBuild::new( - cached_db, - constants, - NoopCfg, - NoopBlock, - deadline, - concurrency_limit, - sim_items.clone(), - max_gas, - ); - - // sleep until next buffer time - tokio::time::sleep(Duration::from_secs(self.secs_to_next_target())).await; - info!("beginning block build cycle"); + println!("STARTING 1"); + // Calculate the next wake up + let buffer = self.secs_to_next_target(); + let deadline = Instant::now().checked_add(Duration::from_secs(buffer)).unwrap(); + println!("DEADLINE {:?}", deadline.clone()); + + tokio::time::sleep(Duration::from_secs(buffer)).await; + + // Fetch latest block number from the rollup + let db = match create_db(&ru_provider).await { + Some(value) => value, + None => { + println!("failed to get a database - check runtime type"); + continue; + } + }; + + println!("SIM ITEMS LEN {}", sim_items.len()); tokio::spawn({ - let outbound = outbound.clone(); + let outbound = block_sender.clone(); + let sim_items = sim_items.clone(); + async move { + let block_builder: BlockBuild<_, NoOpInspector> = BlockBuild::new( + db, + constants, + NoopCfg, + NoopBlock, + deadline, + self.config.concurrency_limit.clone(), + sim_items.clone(), + self.config.rollup_block_gas_limit.clone(), + ); + let block = block_builder.build().await; + println!("GOT BLOCK {}", block.contents_hash()); + if let Err(e) = outbound.send(block) { println!("failed to send built block: {}", e); tracing::error!(error = %e, "failed to send built block"); @@ -120,28 +157,66 @@ impl BlockBuilder { } } }); - - // Feed it transactions and bundles until deadline - loop { - select! { - tx = tx_receiver.recv() => { - if let Some(tx) = tx { - sim_items.add_item(tx); - } - } - bundle = bundle_receiver.recv() => { - if let Some(bundle) = bundle { - sim_items.add_item(bundle.bundle); - } - } - _ = tokio::time::sleep_until(deadline.into()) => { - break; - } - } - } } } .in_current_span(), ) } } + +/// Creates an AlloyDB from a rollup provider +async fn create_db( + ru_provider: &alloy::providers::fillers::FillProvider< + alloy::providers::fillers::JoinFill< + alloy::providers::Identity, + alloy::providers::fillers::JoinFill< + alloy::providers::fillers::GasFiller, + alloy::providers::fillers::JoinFill< + alloy::providers::fillers::BlobGasFiller, + alloy::providers::fillers::JoinFill< + alloy::providers::fillers::NonceFiller, + alloy::providers::fillers::ChainIdFiller, + >, + >, + >, + >, + alloy::providers::RootProvider, + >, +) -> Option< + WrapDatabaseAsync< + AlloyDB< + alloy::network::Ethereum, + alloy::providers::fillers::FillProvider< + alloy::providers::fillers::JoinFill< + alloy::providers::Identity, + alloy::providers::fillers::JoinFill< + alloy::providers::fillers::GasFiller, + alloy::providers::fillers::JoinFill< + alloy::providers::fillers::BlobGasFiller, + alloy::providers::fillers::JoinFill< + alloy::providers::fillers::NonceFiller, + alloy::providers::fillers::ChainIdFiller, + >, + >, + >, + >, + alloy::providers::RootProvider, + >, + >, + >, +> { + let latest = match ru_provider.get_block_number().await { + Ok(block_number) => block_number, + Err(e) => { + tracing::error!(error = %e, "failed to get latest block number"); + println!("failed to get latest block number"); + // Should this do anything else? + return None; + } + }; + let alloy_db = AlloyDB::new(ru_provider.clone(), BlockId::from(latest)); + let wrapped_db = WrapDatabaseAsync::new(alloy_db).unwrap_or_else(|| { + panic!("failed to acquire async alloy_db; check which runtime you're using") + }); + Some(wrapped_db) +} diff --git a/src/tasks/oauth.rs b/src/tasks/oauth.rs index 64c8859..e5c4067 100644 --- a/src/tasks/oauth.rs +++ b/src/tasks/oauth.rs @@ -168,6 +168,7 @@ mod tests { tx_broadcast_urls: vec!["http://localhost:9000".into()], oauth_token_refresh_interval: 300, // 5 minutes builder_helper_address: Address::default(), + concurrency_limit: 1000, }; Ok(config) } diff --git a/tests/block_builder_test.rs b/tests/block_builder_test.rs new file mode 100644 index 0000000..2ab31c2 --- /dev/null +++ b/tests/block_builder_test.rs @@ -0,0 +1,113 @@ +#[cfg(test)] +mod tests { + use alloy::{ + consensus::{SignableTransaction, TxEip1559, TxEnvelope}, + node_bindings::Anvil, + primitives::{Address, TxKind, U256, bytes}, + providers::ProviderBuilder, + signers::{SignerSync, local::PrivateKeySigner}, + }; + use builder::{ + config::BuilderConfig, + tasks::{block::BlockBuilder, bundler::Bundle, oauth::Authenticator}, + }; + use eyre::Result; + use signet_sim::BuiltBlock; + use std::str::FromStr; + use tokio::sync::mpsc; + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_spawn() { + // Make a test config + let config = setup_test_config(); + let constants = config.load_pecorino_constants(); + + // Create an authenticator for bundle integration testing + let authenticator = Authenticator::new(&config); + + // Create an anvil instance for testing + let anvil_instance = Anvil::new().chain_id(14174).spawn(); + let keys = anvil_instance.keys(); + let test_key = PrivateKeySigner::from_signing_key(keys[0].clone().into()); + + // Create a rollup provider + let ru_provider = ProviderBuilder::new().on_http(anvil_instance.endpoint_url()); + + // Create a block builder + let block_builder = BlockBuilder::new(&config, authenticator, ru_provider.clone()); + + // Set up channels for tx, bundle, and outbound built blocks + let (tx_sender, tx_receiver) = mpsc::unbounded_channel::(); + let (_bundle_sender, bundle_receiver) = mpsc::unbounded_channel::(); + let (outbound_sender, mut outbound_receiver) = mpsc::unbounded_channel::(); + + // Spawn the block builder task + let _jh = block_builder.spawn( + constants, + ru_provider, + tx_receiver, + bundle_receiver, + outbound_sender, + ); + + // send two transactions + let tx_1 = new_test_tx(&test_key, 1, U256::from(1_f64)).unwrap(); + tx_sender.send(tx_1).unwrap(); + + let tx_2 = new_test_tx(&test_key, 2, U256::from(2_f64)).unwrap(); + tx_sender.send(tx_2).unwrap(); + + let block = outbound_receiver.recv().await; + + println!("block: {:?}", block); + assert!(block.is_some()); + assert!(block.unwrap().tx_count() == 2); + } + + fn setup_test_config() -> BuilderConfig { + let config = BuilderConfig { + host_chain_id: 17000, + ru_chain_id: 17001, + host_rpc_url: "https://host-rpc.example.com".into(), + ru_rpc_url: "https://rpc.pecorino.signet.sh".into(), + zenith_address: Address::default(), + quincey_url: "http://localhost:8080".into(), + builder_port: 8080, + sequencer_key: None, + builder_key: "0000000000000000000000000000000000000000000000000000000000000000".into(), + block_confirmation_buffer: 1, + chain_offset: 0, + target_slot_time: 1, + builder_rewards_address: Address::default(), + rollup_block_gas_limit: 100_000, + tx_pool_url: "http://localhost:9000/".into(), + tx_pool_cache_duration: 5, + oauth_client_id: "some_client_id".into(), + oauth_client_secret: "some_client_secret".into(), + oauth_authenticate_url: "http://localhost:9000".into(), + oauth_token_url: "http://localhost:9000".into(), + tx_broadcast_urls: vec!["http://localhost:9000".into()], + oauth_token_refresh_interval: 300, // 5 minutes + builder_helper_address: Address::default(), + concurrency_limit: 1000, + }; + config + } + + // Returns a new signed test transaction with default values + fn new_test_tx(wallet: &PrivateKeySigner, nonce: u64, value: U256) -> Result { + let tx = TxEip1559 { + chain_id: 17001, + nonce, + gas_limit: 50000, + to: TxKind::Call( + Address::from_str("0x0000000000000000000000000000000000000000").unwrap(), + ), + value, + input: bytes!(""), + ..Default::default() + }; + let signature = wallet.sign_hash_sync(&tx.signature_hash())?; + Ok(TxEnvelope::Eip1559(tx.into_signed(signature))) + } +} diff --git a/tests/bundle_poller_test.rs b/tests/bundle_poller_test.rs index 53fa51c..ee143fe 100644 --- a/tests/bundle_poller_test.rs +++ b/tests/bundle_poller_test.rs @@ -42,6 +42,7 @@ mod tests { tx_broadcast_urls: vec!["http://localhost:9000".into()], oauth_token_refresh_interval: 300, // 5 minutes builder_helper_address: Address::default(), + concurrency_limit: 1000, }; Ok(config) } diff --git a/tests/tx_poller_test.rs b/tests/tx_poller_test.rs index f0c4756..cd28e68 100644 --- a/tests/tx_poller_test.rs +++ b/tests/tx_poller_test.rs @@ -88,6 +88,7 @@ mod tests { oauth_token_url: "http://localhost:8080".into(), oauth_token_refresh_interval: 300, // 5 minutes builder_helper_address: Address::default(), + concurrency_limit: 1000, }; Ok(config) } From bf75913205940dca3008f2d13fd62705e62d2c3b Mon Sep 17 00:00:00 2001 From: dylan Date: Fri, 18 Apr 2025 13:26:03 -0600 Subject: [PATCH 19/53] tests are passing now --- Cargo.toml | 1 + bin/builder.rs | 12 +- src/lib.rs | 1 + src/tasks/block.rs | 234 ++++++++++++++---------------------- tests/block_builder_test.rs | 79 ++++++------ 5 files changed, 146 insertions(+), 181 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8ba9557..8b74a99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,3 +61,4 @@ tokio = { version = "1.36.0", features = ["full", "macros", "rt-multi-thread"] } async-trait = "0.1.80" oauth2 = "4.4.2" +tracing-subscriber = "0.3.19" diff --git a/bin/builder.rs b/bin/builder.rs index 750f234..41a5509 100644 --- a/bin/builder.rs +++ b/bin/builder.rs @@ -6,6 +6,7 @@ use builder::{ submit::SubmitTask, tx_poller, }, }; +use signet_sim::SimCache; use tokio::select; #[tokio::main] @@ -47,10 +48,12 @@ async fn main() -> eyre::Result<()> { let authenticator_jh = authenticator.spawn(); - let (submit_channel, submit_jh) = submit.spawn(); + let (_submit_channel, submit_jh) = submit.spawn(); - let build_jh = - builder.spawn(constants, ru_provider, tx_receiver, bundle_receiver, submit_channel); + let sim_items = SimCache::new(); + let sim_cache_jh = builder.spawn_cache_task(tx_receiver, bundle_receiver, sim_items.clone()); + + let build_jh = builder.handle_build(constants, ru_provider, sim_items.clone()); let port = config.builder_port; let server = serve_builder_with_span(([0, 0, 0, 0], port), span); @@ -62,6 +65,9 @@ async fn main() -> eyre::Result<()> { _ = bundle_poller_jh => { tracing::info!("bundle_poller finished"); }, + _ = sim_cache_jh => { + tracing::info!("sim cache task finished"); + } _ = submit_jh => { tracing::info!("submit finished"); }, diff --git a/src/lib.rs b/src/lib.rs index 10eefeb..d185db6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,3 +29,4 @@ pub mod utils; // Anonymous import suppresses warnings about unused imports. use openssl as _; +use tracing_subscriber as _; \ No newline at end of file diff --git a/src/tasks/block.rs b/src/tasks/block.rs index d9a87e3..f884b25 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -9,24 +9,26 @@ use crate::{ use alloy::{consensus::TxEnvelope, eips::BlockId, providers::Provider}; use signet_sim::{BlockBuild, BuiltBlock, SimCache, SimItem}; use signet_types::config::SignetSystemConstants; -use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use tokio::{ select, - sync::mpsc::{self, UnboundedReceiver}, - task::JoinHandle, + sync::mpsc::{self}, }; -use tracing::{Instrument, info}; use trevm::{ - NoopBlock, NoopCfg, + NoopBlock, revm::{ + context::CfgEnv, database::{AlloyDB, WrapDatabaseAsync}, inspector::NoOpInspector, + primitives::hardfork::SpecId, }, }; /// Ethereum's slot time in seconds. pub const ETHEREUM_SLOT_TIME: u64 = 12; +/// Pecorino Chain ID +pub const PECORINO_CHAIN_ID: u64 = 14174; + /// BlockBuilder is a task that periodically builds a block then sends it for /// signing and submission. #[derive(Debug)] @@ -56,155 +58,65 @@ impl BlockBuilder { } } - // calculate the duration in seconds until the beginning of the next block slot. - fn secs_to_next_slot(&self) -> u64 { - let curr_timestamp: u64 = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); - let current_slot_time = (curr_timestamp - self.config.chain_offset) % ETHEREUM_SLOT_TIME; - (ETHEREUM_SLOT_TIME - current_slot_time) % ETHEREUM_SLOT_TIME - } - - // add a buffer to the beginning of the block slot. - fn secs_to_next_target(&self) -> u64 { - self.secs_to_next_slot() + self.config.target_slot_time - } - /// Spawn the block builder task, returning the inbound channel to it, and /// a handle to the running task. - pub fn spawn( - self, + pub async fn handle_build( + &self, constants: SignetSystemConstants, ru_provider: WalletlessProvider, - mut tx_receiver: UnboundedReceiver, + sim_items: SimCache, + ) -> Result> { + let db = create_db(ru_provider).await.unwrap(); + + // TODO: add real slot calculator + let finish_by = std::time::Instant::now() + std::time::Duration::from_millis(200); + + dbg!(sim_items.read_best(2)); + dbg!(sim_items.len()); + + let block_build: BlockBuild<_, NoOpInspector> = BlockBuild::new( + db, + constants, + PecorinoCfg {}, + NoopBlock, + finish_by, + self.config.concurrency_limit, + sim_items, + self.config.rollup_block_gas_limit, + ); + + let block = block_build.build().await; + + Ok(block) + } + + /// Spawns a task that receives transactions and bundles from the pollers and + /// adds them to the shared cache. + pub async fn spawn_cache_task( + &self, + mut tx_receiver: mpsc::UnboundedReceiver, mut bundle_receiver: mpsc::UnboundedReceiver, - block_sender: mpsc::UnboundedSender, - ) -> JoinHandle<()> { - println!("GOT HERE 0"); - tokio::spawn( - async move { - // Create a sim item handler - let sim_items = SimCache::new(); - println!("got here 1"); - - tokio::spawn({ - let sim_items = sim_items.clone(); - async move { - println!("starting up the receiver"); - loop { - select! { - tx = tx_receiver.recv() => { - if let Some(tx) = tx { - println!("received transaction {}", tx.hash()); - sim_items.add_item(signet_sim::SimItem::Tx(tx.into())); - } - } - bundle = bundle_receiver.recv() => { - if let Some(bundle) = bundle { - println!("received bundle {}", bundle.id); - sim_items.add_item(SimItem::Bundle(bundle.bundle)); - } - } - } - } + cache: SimCache, + ) { + loop { + select! { + maybe_tx = tx_receiver.recv() => { + if let Some(tx) = maybe_tx { + cache.add_item(SimItem::Tx(tx)); + } + } + maybe_bundle = bundle_receiver.recv() => { + if let Some(bundle) = maybe_bundle { + cache.add_item(SimItem::Bundle(bundle.bundle)); } - }); - - println!("starting the block builder loop"); - - loop { - println!("STARTING 1"); - // Calculate the next wake up - let buffer = self.secs_to_next_target(); - let deadline = Instant::now().checked_add(Duration::from_secs(buffer)).unwrap(); - println!("DEADLINE {:?}", deadline.clone()); - - tokio::time::sleep(Duration::from_secs(buffer)).await; - - // Fetch latest block number from the rollup - let db = match create_db(&ru_provider).await { - Some(value) => value, - None => { - println!("failed to get a database - check runtime type"); - continue; - } - }; - - println!("SIM ITEMS LEN {}", sim_items.len()); - - tokio::spawn({ - let outbound = block_sender.clone(); - let sim_items = sim_items.clone(); - - async move { - let block_builder: BlockBuild<_, NoOpInspector> = BlockBuild::new( - db, - constants, - NoopCfg, - NoopBlock, - deadline, - self.config.concurrency_limit.clone(), - sim_items.clone(), - self.config.rollup_block_gas_limit.clone(), - ); - - let block = block_builder.build().await; - println!("GOT BLOCK {}", block.contents_hash()); - - if let Err(e) = outbound.send(block) { - println!("failed to send built block: {}", e); - tracing::error!(error = %e, "failed to send built block"); - } else { - info!("block build cycle complete"); - } - } - }); } } - .in_current_span(), - ) + } } } /// Creates an AlloyDB from a rollup provider -async fn create_db( - ru_provider: &alloy::providers::fillers::FillProvider< - alloy::providers::fillers::JoinFill< - alloy::providers::Identity, - alloy::providers::fillers::JoinFill< - alloy::providers::fillers::GasFiller, - alloy::providers::fillers::JoinFill< - alloy::providers::fillers::BlobGasFiller, - alloy::providers::fillers::JoinFill< - alloy::providers::fillers::NonceFiller, - alloy::providers::fillers::ChainIdFiller, - >, - >, - >, - >, - alloy::providers::RootProvider, - >, -) -> Option< - WrapDatabaseAsync< - AlloyDB< - alloy::network::Ethereum, - alloy::providers::fillers::FillProvider< - alloy::providers::fillers::JoinFill< - alloy::providers::Identity, - alloy::providers::fillers::JoinFill< - alloy::providers::fillers::GasFiller, - alloy::providers::fillers::JoinFill< - alloy::providers::fillers::BlobGasFiller, - alloy::providers::fillers::JoinFill< - alloy::providers::fillers::NonceFiller, - alloy::providers::fillers::ChainIdFiller, - >, - >, - >, - >, - alloy::providers::RootProvider, - >, - >, - >, -> { +async fn create_db(ru_provider: WalletlessProvider) -> Option { let latest = match ru_provider.get_block_number().await { Ok(block_number) => block_number, Err(e) => { @@ -220,3 +132,41 @@ async fn create_db( }); Some(wrapped_db) } + +/// The wrapped alloy database type that is compatible with Db + DatabaseRef +type WrapAlloyDatabaseAsync = WrapDatabaseAsync< + AlloyDB< + alloy::network::Ethereum, + alloy::providers::fillers::FillProvider< + alloy::providers::fillers::JoinFill< + alloy::providers::Identity, + alloy::providers::fillers::JoinFill< + alloy::providers::fillers::GasFiller, + alloy::providers::fillers::JoinFill< + alloy::providers::fillers::BlobGasFiller, + alloy::providers::fillers::JoinFill< + alloy::providers::fillers::NonceFiller, + alloy::providers::fillers::ChainIdFiller, + >, + >, + >, + >, + alloy::providers::RootProvider, + >, + >, +>; + +/// Configuration struct for Pecorino network values +#[derive(Debug, Clone)] +pub struct PecorinoCfg {} + +impl Copy for PecorinoCfg {} + +impl trevm::Cfg for PecorinoCfg { + fn fill_cfg_env(&self, cfg_env: &mut CfgEnv) { + let CfgEnv { chain_id, spec, .. } = cfg_env; + + *chain_id = PECORINO_CHAIN_ID; + *spec = SpecId::default(); + } +} diff --git a/tests/block_builder_test.rs b/tests/block_builder_test.rs index 2ab31c2..b1934e3 100644 --- a/tests/block_builder_test.rs +++ b/tests/block_builder_test.rs @@ -3,21 +3,29 @@ mod tests { use alloy::{ consensus::{SignableTransaction, TxEip1559, TxEnvelope}, node_bindings::Anvil, - primitives::{Address, TxKind, U256, bytes}, + primitives::{Address, TxKind, U256}, providers::ProviderBuilder, signers::{SignerSync, local::PrivateKeySigner}, }; use builder::{ config::BuilderConfig, - tasks::{block::BlockBuilder, bundler::Bundle, oauth::Authenticator}, + tasks::{ + block::{BlockBuilder, PECORINO_CHAIN_ID}, + oauth::Authenticator, + }, }; use eyre::Result; - use signet_sim::BuiltBlock; + use signet_sim::{SimCache, SimItem}; use std::str::FromStr; - use tokio::sync::mpsc; + use tracing_subscriber::{EnvFilter, Layer, layer::SubscriberExt, util::SubscriberInitExt}; - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn test_spawn() { + let filter = EnvFilter::from_default_env(); + let fmt = tracing_subscriber::fmt::layer().with_filter(filter); + let registry = tracing_subscriber::registry().with(fmt); + registry.try_init().unwrap(); + // Make a test config let config = setup_test_config(); let constants = config.load_pecorino_constants(); @@ -26,9 +34,12 @@ mod tests { let authenticator = Authenticator::new(&config); // Create an anvil instance for testing - let anvil_instance = Anvil::new().chain_id(14174).spawn(); + let anvil_instance = Anvil::new().chain_id(PECORINO_CHAIN_ID).spawn(); + + // Create a wallet let keys = anvil_instance.keys(); - let test_key = PrivateKeySigner::from_signing_key(keys[0].clone().into()); + let test_key_0 = PrivateKeySigner::from_signing_key(keys[0].clone().into()); + let test_key_1 = PrivateKeySigner::from_signing_key(keys[1].clone().into()); // Create a rollup provider let ru_provider = ProviderBuilder::new().on_http(anvil_instance.endpoint_url()); @@ -36,38 +47,28 @@ mod tests { // Create a block builder let block_builder = BlockBuilder::new(&config, authenticator, ru_provider.clone()); - // Set up channels for tx, bundle, and outbound built blocks - let (tx_sender, tx_receiver) = mpsc::unbounded_channel::(); - let (_bundle_sender, bundle_receiver) = mpsc::unbounded_channel::(); - let (outbound_sender, mut outbound_receiver) = mpsc::unbounded_channel::(); + // Setup a sim cache + let sim_items = SimCache::new(); - // Spawn the block builder task - let _jh = block_builder.spawn( - constants, - ru_provider, - tx_receiver, - bundle_receiver, - outbound_sender, - ); + // Add two transactions from two senders to the sim cache + let tx_1 = new_signed_tx(&test_key_0, 0, U256::from(1_f64), 11_000).unwrap(); + sim_items.add_item(SimItem::Tx(tx_1)); - // send two transactions - let tx_1 = new_test_tx(&test_key, 1, U256::from(1_f64)).unwrap(); - tx_sender.send(tx_1).unwrap(); + let tx_2 = new_signed_tx(&test_key_1, 0, U256::from(2_f64), 10_000).unwrap(); + sim_items.add_item(SimItem::Tx(tx_2)); - let tx_2 = new_test_tx(&test_key, 2, U256::from(2_f64)).unwrap(); - tx_sender.send(tx_2).unwrap(); - - let block = outbound_receiver.recv().await; + // Spawn the block builder task + let got = block_builder.handle_build(constants, ru_provider, sim_items).await; - println!("block: {:?}", block); - assert!(block.is_some()); - assert!(block.unwrap().tx_count() == 2); + // Assert on the built block + assert!(got.is_ok()); + assert!(got.unwrap().tx_count() == 2); } fn setup_test_config() -> BuilderConfig { let config = BuilderConfig { - host_chain_id: 17000, - ru_chain_id: 17001, + host_chain_id: 1, + ru_chain_id: PECORINO_CHAIN_ID, host_rpc_url: "https://host-rpc.example.com".into(), ru_rpc_url: "https://rpc.pecorino.signet.sh".into(), zenith_address: Address::default(), @@ -79,7 +80,7 @@ mod tests { chain_offset: 0, target_slot_time: 1, builder_rewards_address: Address::default(), - rollup_block_gas_limit: 100_000, + rollup_block_gas_limit: 100_000_000, tx_pool_url: "http://localhost:9000/".into(), tx_pool_cache_duration: 5, oauth_client_id: "some_client_id".into(), @@ -95,16 +96,22 @@ mod tests { } // Returns a new signed test transaction with default values - fn new_test_tx(wallet: &PrivateKeySigner, nonce: u64, value: U256) -> Result { + fn new_signed_tx( + wallet: &PrivateKeySigner, + nonce: u64, + value: U256, + mpfpg: u128, + ) -> Result { let tx = TxEip1559 { - chain_id: 17001, + chain_id: PECORINO_CHAIN_ID, nonce, - gas_limit: 50000, + max_fee_per_gas: 50_000, + max_priority_fee_per_gas: mpfpg, to: TxKind::Call( Address::from_str("0x0000000000000000000000000000000000000000").unwrap(), ), value, - input: bytes!(""), + gas_limit: 50_000, ..Default::default() }; let signature = wallet.sign_hash_sync(&tx.signature_hash())?; From 55b2193ab3f51a40f257452b29132cbf99d0c1c4 Mon Sep 17 00:00:00 2001 From: dylan Date: Fri, 18 Apr 2025 19:45:48 -0600 Subject: [PATCH 20/53] adds slot calculator to block builder --- bin/builder.rs | 14 +++-- src/config.rs | 4 ++ src/tasks/block.rs | 146 ++++++++++++++++++++++++++------------------- 3 files changed, 98 insertions(+), 66 deletions(-) diff --git a/bin/builder.rs b/bin/builder.rs index 41a5509..643c650 100644 --- a/bin/builder.rs +++ b/bin/builder.rs @@ -7,6 +7,8 @@ use builder::{ }, }; use signet_sim::SimCache; +use signet_types::SlotCalculator; +use std::sync::Arc; use tokio::select; #[tokio::main] @@ -29,7 +31,6 @@ async fn main() -> eyre::Result<()> { let metrics = MetricsTask { host_provider: host_provider.clone() }; let (tx_channel, metrics_jh) = metrics.spawn(); - let builder = BlockBuilder::new(&config, authenticator.clone(), ru_provider.clone()); let submit = SubmitTask { authenticator: authenticator.clone(), host_provider, @@ -48,12 +49,17 @@ async fn main() -> eyre::Result<()> { let authenticator_jh = authenticator.spawn(); - let (_submit_channel, submit_jh) = submit.spawn(); + let (submit_channel, submit_jh) = submit.spawn(); let sim_items = SimCache::new(); - let sim_cache_jh = builder.spawn_cache_task(tx_receiver, bundle_receiver, sim_items.clone()); - let build_jh = builder.handle_build(constants, ru_provider, sim_items.clone()); + let slot_calculator = SlotCalculator::pecorino(); + let builder = Arc::new(BlockBuilder::new(&config, ru_provider.clone(), slot_calculator)); + + let sim_cache_jh = + builder.clone().spawn_cache_handler(tx_receiver, bundle_receiver, sim_items.clone()); + + let build_jh = builder.clone().spawn_builder_task(constants, sim_items.clone(), submit_channel); let port = config.builder_port; let server = serve_builder_with_span(([0, 0, 0, 0], port), span); diff --git a/src/config.rs b/src/config.rs index 0b34167..011b862 100644 --- a/src/config.rs +++ b/src/config.rs @@ -40,6 +40,7 @@ const OAUTH_CLIENT_SECRET: &str = "OAUTH_CLIENT_SECRET"; const OAUTH_AUTHENTICATE_URL: &str = "OAUTH_AUTHENTICATE_URL"; const OAUTH_TOKEN_URL: &str = "OAUTH_TOKEN_URL"; const CONCURRENCY_LIMIT: &str = "CONCURRENCY_LIMIT"; +const START_TIMESTAMP: &str = "START_TIMESTAMP"; /// Configuration for a builder running a specific rollup on a specific host /// chain. @@ -97,6 +98,8 @@ pub struct BuilderConfig { pub oauth_token_refresh_interval: u64, /// The max number of simultaneous block simulations to run. pub concurrency_limit: usize, + /// The anchor for slot time and number calculations before adjusting for chain offset. + pub start_timestamp: u64, } /// Error loading the configuration. @@ -193,6 +196,7 @@ impl BuilderConfig { oauth_token_url: load_string(OAUTH_TOKEN_URL)?, oauth_token_refresh_interval: load_u64(AUTH_TOKEN_REFRESH_INTERVAL)?, concurrency_limit: load_u64(CONCURRENCY_LIMIT).map(|v| v as usize).unwrap_or(1000), + start_timestamp: load_u64(START_TIMESTAMP)?, }) } diff --git a/src/tasks/block.rs b/src/tasks/block.rs index f884b25..debd2de 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -1,17 +1,18 @@ use crate::{ config::{BuilderConfig, WalletlessProvider}, - tasks::{ - bundler::{Bundle, BundlePoller}, - oauth::Authenticator, - tx_poller::TxPoller, - }, + tasks::bundler::Bundle, }; use alloy::{consensus::TxEnvelope, eips::BlockId, providers::Provider}; use signet_sim::{BlockBuild, BuiltBlock, SimCache, SimItem}; -use signet_types::config::SignetSystemConstants; +use signet_types::{SlotCalculator, config::SignetSystemConstants}; +use std::{ + sync::Arc, + time::{Duration, Instant, SystemTime, UNIX_EPOCH}, +}; use tokio::{ select, sync::mpsc::{self}, + task::JoinHandle, }; use trevm::{ NoopBlock, @@ -23,9 +24,6 @@ use trevm::{ }, }; -/// Ethereum's slot time in seconds. -pub const ETHEREUM_SLOT_TIME: u64 = 12; - /// Pecorino Chain ID pub const PECORINO_CHAIN_ID: u64 = 14174; @@ -37,42 +35,28 @@ pub struct BlockBuilder { pub config: BuilderConfig, /// A provider that cannot sign transactions. pub ru_provider: WalletlessProvider, - /// A poller for fetching transactions. - pub tx_poller: TxPoller, - /// A poller for fetching bundles. - pub bundle_poller: BundlePoller, + /// The slot calculator for waking up and sleeping the builder correctly + pub slot_calculator: SlotCalculator, } impl BlockBuilder { - /// Create a new block builder with the given config. + /// Creates a new block builder that builds blocks based on the given provider. pub fn new( config: &BuilderConfig, - authenticator: Authenticator, ru_provider: WalletlessProvider, + slot_calculator: SlotCalculator, ) -> Self { - Self { - config: config.clone(), - ru_provider, - tx_poller: TxPoller::new(config), - bundle_poller: BundlePoller::new(config, authenticator), - } + Self { config: config.clone(), ru_provider, slot_calculator } } - /// Spawn the block builder task, returning the inbound channel to it, and - /// a handle to the running task. + /// Handles building a single block. pub async fn handle_build( &self, constants: SignetSystemConstants, - ru_provider: WalletlessProvider, sim_items: SimCache, + finish_by: Instant, ) -> Result> { - let db = create_db(ru_provider).await.unwrap(); - - // TODO: add real slot calculator - let finish_by = std::time::Instant::now() + std::time::Duration::from_millis(200); - - dbg!(sim_items.read_best(2)); - dbg!(sim_items.len()); + let db = self.create_db().await.unwrap(); let block_build: BlockBuild<_, NoOpInspector> = BlockBuild::new( db, @@ -90,47 +74,85 @@ impl BlockBuilder { Ok(block) } - /// Spawns a task that receives transactions and bundles from the pollers and - /// adds them to the shared cache. - pub async fn spawn_cache_task( - &self, + /// Scans the tx and bundle receivers for new items and adds them to the cache. + pub fn spawn_cache_handler( + self: Arc, mut tx_receiver: mpsc::UnboundedReceiver, mut bundle_receiver: mpsc::UnboundedReceiver, cache: SimCache, - ) { - loop { - select! { - maybe_tx = tx_receiver.recv() => { - if let Some(tx) = maybe_tx { - cache.add_item(SimItem::Tx(tx)); + ) -> JoinHandle<()> { + let jh = tokio::spawn(async move { + loop { + select! { + maybe_tx = tx_receiver.recv() => { + if let Some(tx) = maybe_tx { + cache.add_item(SimItem::Tx(tx)); + } + } + maybe_bundle = bundle_receiver.recv() => { + if let Some(bundle) = maybe_bundle { + cache.add_item(SimItem::Bundle(bundle.bundle)); + } } } - maybe_bundle = bundle_receiver.recv() => { - if let Some(bundle) = maybe_bundle { - cache.add_item(SimItem::Bundle(bundle.bundle)); + } + }); + jh + } + + /// Spawns the block building task. + pub fn spawn_builder_task( + self: Arc, + constants: SignetSystemConstants, + cache: SimCache, + submit_sender: mpsc::UnboundedSender, + ) -> JoinHandle<()> { + let jh = tokio::spawn(async move { + loop { + let sim_cache = cache.clone(); + + let finish_by = self.calculate_deadline(); + tracing::info!("simulating until target slot deadline"); + + // sleep until next wake period + tracing::info!("starting block build"); + match self.handle_build(constants, sim_cache, finish_by).await { + Ok(block) => { + let _ = submit_sender.send(block); + } + Err(e) => { + tracing::error!(err = %e, "failed to send block"); + continue; } } } - } + }); + jh } -} -/// Creates an AlloyDB from a rollup provider -async fn create_db(ru_provider: WalletlessProvider) -> Option { - let latest = match ru_provider.get_block_number().await { - Ok(block_number) => block_number, - Err(e) => { - tracing::error!(error = %e, "failed to get latest block number"); - println!("failed to get latest block number"); - // Should this do anything else? - return None; - } - }; - let alloy_db = AlloyDB::new(ru_provider.clone(), BlockId::from(latest)); - let wrapped_db = WrapDatabaseAsync::new(alloy_db).unwrap_or_else(|| { - panic!("failed to acquire async alloy_db; check which runtime you're using") - }); - Some(wrapped_db) + /// Returns the instant at which simulation must stop. + pub fn calculate_deadline(&self) -> Instant { + let now = SystemTime::now(); + let unix_seconds = now.duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs(); + + Instant::now().checked_add(Duration::from_secs(unix_seconds)).unwrap() + } + + /// Creates an AlloyDB from a rollup provider + async fn create_db(&self) -> Option { + let latest = match self.ru_provider.get_block_number().await { + Ok(block_number) => block_number, + Err(e) => { + tracing::error!(error = %e, "failed to get latest block number"); + return None; + } + }; + let alloy_db = AlloyDB::new(self.ru_provider.clone(), BlockId::from(latest)); + let wrapped_db = WrapDatabaseAsync::new(alloy_db).unwrap_or_else(|| { + panic!("failed to acquire async alloy_db; check which runtime you're using") + }); + Some(wrapped_db) + } } /// The wrapped alloy database type that is compatible with Db + DatabaseRef From fd4bdcd8a96f81c68a92bbe3eee6b150033e8b4f Mon Sep 17 00:00:00 2001 From: dylan Date: Fri, 18 Apr 2025 20:37:36 -0600 Subject: [PATCH 21/53] refactors test utils across tasks --- src/lib.rs | 5 +- src/tasks/oauth.rs | 45 ++--------- src/test_utils.rs | 72 +++++++++++++++++ tests/block_builder_test.rs | 154 ++++++++++++++++++------------------ tests/bundle_poller_test.rs | 40 +--------- tests/tx_poller_test.rs | 63 ++------------- 6 files changed, 172 insertions(+), 207 deletions(-) create mode 100644 src/test_utils.rs diff --git a/src/lib.rs b/src/lib.rs index d185db6..bb39edf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,9 @@ pub mod tasks; /// Utilities. pub mod utils; +/// Test utilitites +pub mod test_utils; + // Anonymous import suppresses warnings about unused imports. use openssl as _; -use tracing_subscriber as _; \ No newline at end of file +use tracing_subscriber as _; diff --git a/src/tasks/oauth.rs b/src/tasks/oauth.rs index e5c4067..93949a8 100644 --- a/src/tasks/oauth.rs +++ b/src/tasks/oauth.rs @@ -122,54 +122,21 @@ impl Authenticator { } mod tests { - use crate::config::BuilderConfig; - use alloy::primitives::Address; - use eyre::Result; - #[ignore = "integration test"] #[tokio::test] - async fn test_authenticator() -> Result<()> { + async fn test_authenticator() -> eyre::Result<()> { use super::*; + use crate::test_utils::setup_test_config; use oauth2::TokenResponse; let config = setup_test_config()?; let auth = Authenticator::new(&config); - let token = auth.fetch_oauth_token().await?; - dbg!(&token); + + let _ = auth.fetch_oauth_token().await?; + let token = auth.token().await.unwrap(); - println!("{:?}", token); + assert!(!token.access_token().secret().is_empty()); Ok(()) } - - #[allow(dead_code)] - fn setup_test_config() -> Result { - let config = BuilderConfig { - host_chain_id: 17000, - ru_chain_id: 17001, - host_rpc_url: "host-rpc.example.com".into(), - ru_rpc_url: "ru-rpc.example.com".into(), - zenith_address: Address::default(), - quincey_url: "http://localhost:8080".into(), - builder_port: 8080, - sequencer_key: None, - builder_key: "0000000000000000000000000000000000000000000000000000000000000000".into(), - block_confirmation_buffer: 1, - chain_offset: 0, - target_slot_time: 1, - builder_rewards_address: Address::default(), - rollup_block_gas_limit: 100_000, - tx_pool_url: "http://localhost:9000/".into(), - tx_pool_cache_duration: 5, - oauth_client_id: "some_client_id".into(), - oauth_client_secret: "some_client_secret".into(), - oauth_authenticate_url: "http://localhost:9000".into(), - oauth_token_url: "http://localhost:9000".into(), - tx_broadcast_urls: vec!["http://localhost:9000".into()], - oauth_token_refresh_interval: 300, // 5 minutes - builder_helper_address: Address::default(), - concurrency_limit: 1000, - }; - Ok(config) - } } diff --git a/src/test_utils.rs b/src/test_utils.rs new file mode 100644 index 0000000..20c5fa9 --- /dev/null +++ b/src/test_utils.rs @@ -0,0 +1,72 @@ +//! Test utilities for testing builder tasks +use crate::{config::BuilderConfig, tasks::block::PECORINO_CHAIN_ID}; +use alloy::{ + consensus::{SignableTransaction, TxEip1559, TxEnvelope}, + primitives::{Address, TxKind, U256}, + signers::{SignerSync, local::PrivateKeySigner}, +}; +use eyre::Result; +use std::str::FromStr; +use tracing_subscriber::{EnvFilter, Layer, layer::SubscriberExt, util::SubscriberInitExt}; + +/// Sets up a block builder with test values +pub fn setup_test_config() -> Result { + let config = BuilderConfig { + host_chain_id: 17000, + ru_chain_id: 17001, + host_rpc_url: "host-rpc.example.com".into(), + ru_rpc_url: "ru-rpc.example.com".into(), + tx_broadcast_urls: vec!["http://localhost:9000".into()], + zenith_address: Address::default(), + quincey_url: "http://localhost:8080".into(), + builder_port: 8080, + sequencer_key: None, + builder_key: "0000000000000000000000000000000000000000000000000000000000000000".into(), + block_confirmation_buffer: 1, + chain_offset: 0, + target_slot_time: 1, + builder_rewards_address: Address::default(), + rollup_block_gas_limit: 100_000, + tx_pool_url: "http://localhost:9000/".into(), + tx_pool_cache_duration: 5, + oauth_client_id: "some_client_id".into(), + oauth_client_secret: "some_client_secret".into(), + oauth_authenticate_url: "http://localhost:8080".into(), + oauth_token_url: "http://localhost:8080".into(), + oauth_token_refresh_interval: 300, // 5 minutes + builder_helper_address: Address::default(), + concurrency_limit: 1000, + start_timestamp: 1740681556, // pecorino start timestamp as sane default + }; + Ok(config) +} + +/// Returns a new signed test transaction with the provided nonce, value, and mpfpg. +pub fn new_signed_tx( + wallet: &PrivateKeySigner, + nonce: u64, + value: U256, + mpfpg: u128, +) -> Result { + let tx = TxEip1559 { + chain_id: PECORINO_CHAIN_ID, + nonce, + max_fee_per_gas: 50_000, + max_priority_fee_per_gas: mpfpg, + to: TxKind::Call(Address::from_str("0x0000000000000000000000000000000000000000").unwrap()), + value, + gas_limit: 50_000, + ..Default::default() + }; + let signature = wallet.sign_hash_sync(&tx.signature_hash())?; + Ok(TxEnvelope::Eip1559(tx.into_signed(signature))) +} + +/// Initializes a logger that prints during testing +pub fn setup_logging() { + // Initialize logging + let filter = EnvFilter::from_default_env(); + let fmt = tracing_subscriber::fmt::layer().with_filter(filter); + let registry = tracing_subscriber::registry().with(fmt); + let _ = registry.try_init(); +} diff --git a/tests/block_builder_test.rs b/tests/block_builder_test.rs index b1934e3..6f8693a 100644 --- a/tests/block_builder_test.rs +++ b/tests/block_builder_test.rs @@ -1,38 +1,30 @@ #[cfg(test)] mod tests { use alloy::{ - consensus::{SignableTransaction, TxEip1559, TxEnvelope}, - node_bindings::Anvil, - primitives::{Address, TxKind, U256}, - providers::ProviderBuilder, - signers::{SignerSync, local::PrivateKeySigner}, + node_bindings::Anvil, primitives::U256, providers::ProviderBuilder, + signers::local::PrivateKeySigner, }; use builder::{ - config::BuilderConfig, - tasks::{ - block::{BlockBuilder, PECORINO_CHAIN_ID}, - oauth::Authenticator, - }, + tasks::block::{BlockBuilder, PECORINO_CHAIN_ID}, + test_utils::{new_signed_tx, setup_logging, setup_test_config}, }; - use eyre::Result; + use signet_sim::{SimCache, SimItem}; - use std::str::FromStr; - use tracing_subscriber::{EnvFilter, Layer, layer::SubscriberExt, util::SubscriberInitExt}; + use signet_types::SlotCalculator; + use std::{ + sync::Arc, + time::{Duration, Instant, SystemTime, UNIX_EPOCH}, + }; + use tokio::{sync::mpsc::unbounded_channel, time::timeout}; - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - async fn test_spawn() { - let filter = EnvFilter::from_default_env(); - let fmt = tracing_subscriber::fmt::layer().with_filter(filter); - let registry = tracing_subscriber::registry().with(fmt); - registry.try_init().unwrap(); + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_handle_build() { + setup_logging(); // Make a test config - let config = setup_test_config(); + let config = setup_test_config().unwrap(); let constants = config.load_pecorino_constants(); - // Create an authenticator for bundle integration testing - let authenticator = Authenticator::new(&config); - // Create an anvil instance for testing let anvil_instance = Anvil::new().chain_id(PECORINO_CHAIN_ID).spawn(); @@ -44,8 +36,14 @@ mod tests { // Create a rollup provider let ru_provider = ProviderBuilder::new().on_http(anvil_instance.endpoint_url()); - // Create a block builder - let block_builder = BlockBuilder::new(&config, authenticator, ru_provider.clone()); + // Create a block builder with a slot calculator for testing + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Clock may have gone backwards") + .as_secs(); + dbg!(now); + let slot_calculator = SlotCalculator::new(now, 0, 12); + let block_builder = BlockBuilder::new(&config, ru_provider.clone(), slot_calculator); // Setup a sim cache let sim_items = SimCache::new(); @@ -57,64 +55,68 @@ mod tests { let tx_2 = new_signed_tx(&test_key_1, 0, U256::from(2_f64), 10_000).unwrap(); sim_items.add_item(SimItem::Tx(tx_2)); + let finish_by = Instant::now() + Duration::from_secs(2); + // Spawn the block builder task - let got = block_builder.handle_build(constants, ru_provider, sim_items).await; + let got = block_builder.handle_build(constants, sim_items, finish_by).await; // Assert on the built block assert!(got.is_ok()); assert!(got.unwrap().tx_count() == 2); } - fn setup_test_config() -> BuilderConfig { - let config = BuilderConfig { - host_chain_id: 1, - ru_chain_id: PECORINO_CHAIN_ID, - host_rpc_url: "https://host-rpc.example.com".into(), - ru_rpc_url: "https://rpc.pecorino.signet.sh".into(), - zenith_address: Address::default(), - quincey_url: "http://localhost:8080".into(), - builder_port: 8080, - sequencer_key: None, - builder_key: "0000000000000000000000000000000000000000000000000000000000000000".into(), - block_confirmation_buffer: 1, - chain_offset: 0, - target_slot_time: 1, - builder_rewards_address: Address::default(), - rollup_block_gas_limit: 100_000_000, - tx_pool_url: "http://localhost:9000/".into(), - tx_pool_cache_duration: 5, - oauth_client_id: "some_client_id".into(), - oauth_client_secret: "some_client_secret".into(), - oauth_authenticate_url: "http://localhost:9000".into(), - oauth_token_url: "http://localhost:9000".into(), - tx_broadcast_urls: vec!["http://localhost:9000".into()], - oauth_token_refresh_interval: 300, // 5 minutes - builder_helper_address: Address::default(), - concurrency_limit: 1000, - }; - config - } + /// Tests the full block builder loop + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_spawn() { + setup_logging(); + + // Make a test config + let config = setup_test_config().unwrap(); + let constants = config.load_pecorino_constants(); + + // Create an anvil instance for testing + let anvil_instance = Anvil::new().chain_id(PECORINO_CHAIN_ID).spawn(); + + // Create a wallet + let keys = anvil_instance.keys(); + let test_key_0 = PrivateKeySigner::from_signing_key(keys[0].clone().into()); + let test_key_1 = PrivateKeySigner::from_signing_key(keys[1].clone().into()); + + // Plumb inputs for the test setup + let (tx_sender, tx_receiver) = unbounded_channel(); + let (_, bundle_receiver) = unbounded_channel(); + let (block_sender, mut block_receiver) = unbounded_channel(); + + // Create a rollup provider + let ru_provider = ProviderBuilder::new().on_http(anvil_instance.endpoint_url()); + + // Create a builder with a test slot calculator + let slot_calculator = SlotCalculator::new( + config.start_timestamp, + config.chain_offset, + config.target_slot_time, + ); + let sim_cache = SimCache::new(); + let builder = Arc::new(BlockBuilder::new(&config, ru_provider.clone(), slot_calculator)); + + // Create a sim cache and start filling it with items + let _ = + builder.clone().spawn_cache_handler(tx_receiver, bundle_receiver, sim_cache.clone()); + + // Finally, Kick off the block builder task. + let _ = builder.clone().spawn_builder_task(constants, sim_cache.clone(), block_sender); + + let tx_1 = new_signed_tx(&test_key_0, 0, U256::from(1_f64), 11_000).unwrap(); + let tx_2 = new_signed_tx(&test_key_1, 0, U256::from(2_f64), 10_000).unwrap(); + tx_sender.send(tx_1).unwrap(); + tx_sender.send(tx_2).unwrap(); - // Returns a new signed test transaction with default values - fn new_signed_tx( - wallet: &PrivateKeySigner, - nonce: u64, - value: U256, - mpfpg: u128, - ) -> Result { - let tx = TxEip1559 { - chain_id: PECORINO_CHAIN_ID, - nonce, - max_fee_per_gas: 50_000, - max_priority_fee_per_gas: mpfpg, - to: TxKind::Call( - Address::from_str("0x0000000000000000000000000000000000000000").unwrap(), - ), - value, - gas_limit: 50_000, - ..Default::default() - }; - let signature = wallet.sign_hash_sync(&tx.signature_hash())?; - Ok(TxEnvelope::Eip1559(tx.into_signed(signature))) + // Wait for a block with timeout + let result = timeout(Duration::from_secs(5), block_receiver.recv()).await; + assert!(result.is_ok(), "Did not receive block within 5 seconds"); + let block = result.unwrap(); + dbg!(&block); + assert!(block.is_some(), "Block channel closed without receiving a block"); + assert!(block.unwrap().tx_count() == 2); // TODO: Why is this failing? I'm seeing EVM errors but haven't tracked them down yet. } } diff --git a/tests/bundle_poller_test.rs b/tests/bundle_poller_test.rs index ee143fe..995419d 100644 --- a/tests/bundle_poller_test.rs +++ b/tests/bundle_poller_test.rs @@ -1,49 +1,17 @@ mod tests { - use alloy::primitives::Address; - use builder::{config::BuilderConfig, tasks::oauth::Authenticator}; + use builder::{tasks::oauth::Authenticator, test_utils}; use eyre::Result; #[ignore = "integration test"] #[tokio::test] async fn test_bundle_poller_roundtrip() -> Result<()> { - let config = setup_test_config().await.unwrap(); + let config = test_utils::setup_test_config().unwrap(); let auth = Authenticator::new(&config); + let mut bundle_poller = builder::tasks::bundler::BundlePoller::new(&config, auth); - let got = bundle_poller.check_bundle_cache().await?; - dbg!(got); + let _ = bundle_poller.check_bundle_cache().await?; Ok(()) } - - async fn setup_test_config() -> Result { - let config = BuilderConfig { - host_chain_id: 17000, - ru_chain_id: 17001, - host_rpc_url: "host-rpc.example.com".into(), - ru_rpc_url: "ru-rpc.example.com".into(), - zenith_address: Address::default(), - quincey_url: "http://localhost:8080".into(), - builder_port: 8080, - sequencer_key: None, - builder_key: "0000000000000000000000000000000000000000000000000000000000000000".into(), - block_confirmation_buffer: 1, - chain_offset: 0, - target_slot_time: 1, - builder_rewards_address: Address::default(), - rollup_block_gas_limit: 100_000, - tx_pool_url: "http://localhost:9000/".into(), - // tx_pool_url: "https://transactions.holesky.signet.sh".into(), - tx_pool_cache_duration: 5, - oauth_client_id: "some_client_id".into(), - oauth_client_secret: "some_client_secret".into(), - oauth_authenticate_url: "http://localhost:8080".into(), - oauth_token_url: "http://localhost:8080".into(), - tx_broadcast_urls: vec!["http://localhost:9000".into()], - oauth_token_refresh_interval: 300, // 5 minutes - builder_helper_address: Address::default(), - concurrency_limit: 1000, - }; - Ok(config) - } } diff --git a/tests/tx_poller_test.rs b/tests/tx_poller_test.rs index cd28e68..3d8e5c6 100644 --- a/tests/tx_poller_test.rs +++ b/tests/tx_poller_test.rs @@ -1,18 +1,18 @@ mod tests { - use std::str::FromStr; - - use alloy::consensus::{SignableTransaction, TxEip1559, TxEnvelope}; - use alloy::primitives::{Address, TxKind, U256, bytes}; - use alloy::signers::{SignerSync, local::PrivateKeySigner}; + use alloy::primitives::U256; + use alloy::signers::local::PrivateKeySigner; use builder::config::BuilderConfig; use builder::tasks::tx_poller; + use builder::test_utils::{new_signed_tx, setup_logging, setup_test_config}; // Import the refactored function use eyre::{Ok, Result}; #[ignore = "integration test"] #[tokio::test] async fn test_tx_roundtrip() -> Result<()> { + setup_logging(); + // Create a new test environment - let config = setup_test_config().await?; + let config = setup_test_config()?; // Post a transaction to the cache post_tx(&config).await?; @@ -31,8 +31,9 @@ mod tests { async fn post_tx(config: &BuilderConfig) -> Result<()> { let client = reqwest::Client::new(); + let wallet = PrivateKeySigner::random(); - let tx_envelope = new_test_tx(&wallet)?; + let tx_envelope = new_signed_tx(&wallet, 1, U256::from(1), 10_000)?; let url = format!("{}/transactions", config.tx_pool_url); let response = client.post(&url).json(&tx_envelope).send().await?; @@ -44,52 +45,4 @@ mod tests { Ok(()) } - - // Returns a new signed test transaction with default values - fn new_test_tx(wallet: &PrivateKeySigner) -> Result { - let tx = TxEip1559 { - chain_id: 17001, - nonce: 1, - gas_limit: 50000, - to: TxKind::Call( - Address::from_str("0x0000000000000000000000000000000000000000").unwrap(), - ), - value: U256::from(1_f64), - input: bytes!(""), - ..Default::default() - }; - let signature = wallet.sign_hash_sync(&tx.signature_hash())?; - Ok(TxEnvelope::Eip1559(tx.into_signed(signature))) - } - - // Sets up a block builder with test values - pub async fn setup_test_config() -> Result { - let config = BuilderConfig { - host_chain_id: 17000, - ru_chain_id: 17001, - host_rpc_url: "host-rpc.example.com".into(), - ru_rpc_url: "ru-rpc.example.com".into(), - tx_broadcast_urls: vec!["http://localhost:9000".into()], - zenith_address: Address::default(), - quincey_url: "http://localhost:8080".into(), - builder_port: 8080, - sequencer_key: None, - builder_key: "0000000000000000000000000000000000000000000000000000000000000000".into(), - block_confirmation_buffer: 1, - chain_offset: 0, - target_slot_time: 1, - builder_rewards_address: Address::default(), - rollup_block_gas_limit: 100_000, - tx_pool_url: "http://localhost:9000/".into(), - tx_pool_cache_duration: 5, - oauth_client_id: "some_client_id".into(), - oauth_client_secret: "some_client_secret".into(), - oauth_authenticate_url: "http://localhost:8080".into(), - oauth_token_url: "http://localhost:8080".into(), - oauth_token_refresh_interval: 300, // 5 minutes - builder_helper_address: Address::default(), - concurrency_limit: 1000, - }; - Ok(config) - } } From dd4b37782fd74487ca2c3878026eb13f577e6e07 Mon Sep 17 00:00:00 2001 From: dylan Date: Fri, 18 Apr 2025 20:38:34 -0600 Subject: [PATCH 22/53] fmt --- src/tasks/oauth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tasks/oauth.rs b/src/tasks/oauth.rs index 93949a8..69d3049 100644 --- a/src/tasks/oauth.rs +++ b/src/tasks/oauth.rs @@ -131,7 +131,7 @@ mod tests { let config = setup_test_config()?; let auth = Authenticator::new(&config); - + let _ = auth.fetch_oauth_token().await?; let token = auth.token().await.unwrap(); From 1ee991f9085332305bb807455ff8ab6961d5c9fd Mon Sep 17 00:00:00 2001 From: dylan Date: Fri, 18 Apr 2025 20:47:59 -0600 Subject: [PATCH 23/53] fixup clippy and fmt --- bin/builder.rs | 3 +- src/config.rs | 2 +- src/tasks/block.rs | 75 ++++++++++++++++++++++++++++--------- tests/block_builder_test.rs | 25 ++++++++++--- 4 files changed, 78 insertions(+), 27 deletions(-) diff --git a/bin/builder.rs b/bin/builder.rs index 643c650..f2779c4 100644 --- a/bin/builder.rs +++ b/bin/builder.rs @@ -52,8 +52,7 @@ async fn main() -> eyre::Result<()> { let (submit_channel, submit_jh) = submit.spawn(); let sim_items = SimCache::new(); - - let slot_calculator = SlotCalculator::pecorino(); + let slot_calculator = SlotCalculator::new(config.start_timestamp, config.chain_offset, config.target_slot_time); let builder = Arc::new(BlockBuilder::new(&config, ru_provider.clone(), slot_calculator)); let sim_cache_jh = diff --git a/src/config.rs b/src/config.rs index 011b862..e4e9079 100644 --- a/src/config.rs +++ b/src/config.rs @@ -259,7 +259,7 @@ impl BuilderConfig { } /// Loads the Signet system constants for Pecorino. - pub fn load_pecorino_constants(&self) -> SignetSystemConstants { + pub const fn load_pecorino_constants(&self) -> SignetSystemConstants { let host = HostConfig::new( self.host_chain_id, 149984, diff --git a/src/tasks/block.rs b/src/tasks/block.rs index debd2de..c332dd2 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -24,23 +24,30 @@ use trevm::{ }, }; -/// Pecorino Chain ID +/// Pecorino Chain ID used for the Pecorino network. pub const PECORINO_CHAIN_ID: u64 = 14174; -/// BlockBuilder is a task that periodically builds a block then sends it for -/// signing and submission. +/// `BlockBuilder` is responsible for periodically building blocks and submitting them for signing and inclusion in the blockchain. #[derive(Debug)] pub struct BlockBuilder { - /// Configuration. + /// Configuration for the builder. pub config: BuilderConfig, - /// A provider that cannot sign transactions. + /// A provider that cannot sign transactions, used for interacting with the rollup. pub ru_provider: WalletlessProvider, - /// The slot calculator for waking up and sleeping the builder correctly + /// The slot calculator for determining when to wake up and build blocks. pub slot_calculator: SlotCalculator, } impl BlockBuilder { - /// Creates a new block builder that builds blocks based on the given provider. + /// Creates a new `BlockBuilder` instance. + /// + /// # Arguments + /// - `config`: The configuration for the builder. + /// - `ru_provider`: A provider for interacting with the rollup. + /// - `slot_calculator`: A slot calculator for managing block timing. + /// + /// # Returns + /// A new `BlockBuilder` instance. pub fn new( config: &BuilderConfig, ru_provider: WalletlessProvider, @@ -50,6 +57,14 @@ impl BlockBuilder { } /// Handles building a single block. + /// + /// # Arguments + /// - `constants`: The system constants for the rollup. + /// - `sim_items`: The simulation cache containing transactions and bundles. + /// - `finish_by`: The deadline by which the block must be built. + /// + /// # Returns + /// A `Result` containing the built block or an error. pub async fn handle_build( &self, constants: SignetSystemConstants, @@ -74,14 +89,22 @@ impl BlockBuilder { Ok(block) } - /// Scans the tx and bundle receivers for new items and adds them to the cache. + /// Spawns a task to handle incoming transactions and bundles, adding them to the simulation cache. + /// + /// # Arguments + /// - `tx_receiver`: A channel receiver for incoming transactions. + /// - `bundle_receiver`: A channel receiver for incoming bundles. + /// - `cache`: The simulation cache to store the received items. + /// + /// # Returns + /// A `JoinHandle` for the spawned task. pub fn spawn_cache_handler( self: Arc, mut tx_receiver: mpsc::UnboundedReceiver, mut bundle_receiver: mpsc::UnboundedReceiver, cache: SimCache, ) -> JoinHandle<()> { - let jh = tokio::spawn(async move { + tokio::spawn(async move { loop { select! { maybe_tx = tx_receiver.recv() => { @@ -96,18 +119,25 @@ impl BlockBuilder { } } } - }); - jh + }) } /// Spawns the block building task. + /// + /// # Arguments + /// - `constants`: The system constants for the rollup. + /// - `cache`: The simulation cache containing transactions and bundles. + /// - `submit_sender`: A channel sender for submitting built blocks. + /// + /// # Returns + /// A `JoinHandle` for the spawned task. pub fn spawn_builder_task( self: Arc, constants: SignetSystemConstants, cache: SimCache, submit_sender: mpsc::UnboundedSender, ) -> JoinHandle<()> { - let jh = tokio::spawn(async move { + tokio::spawn(async move { loop { let sim_cache = cache.clone(); @@ -126,11 +156,13 @@ impl BlockBuilder { } } } - }); - jh + }) } - /// Returns the instant at which simulation must stop. + /// Calculates the deadline for the current block simulation. + /// + /// # Returns + /// An `Instant` representing the deadline. pub fn calculate_deadline(&self) -> Instant { let now = SystemTime::now(); let unix_seconds = now.duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs(); @@ -138,7 +170,10 @@ impl BlockBuilder { Instant::now().checked_add(Duration::from_secs(unix_seconds)).unwrap() } - /// Creates an AlloyDB from a rollup provider + /// Creates an `AlloyDB` instance from the rollup provider. + /// + /// # Returns + /// An `Option` containing the wrapped database or `None` if an error occurs. async fn create_db(&self) -> Option { let latest = match self.ru_provider.get_block_number().await { Ok(block_number) => block_number, @@ -155,7 +190,7 @@ impl BlockBuilder { } } -/// The wrapped alloy database type that is compatible with Db + DatabaseRef +/// The wrapped alloy database type that is compatible with `Db` and `DatabaseRef`. type WrapAlloyDatabaseAsync = WrapDatabaseAsync< AlloyDB< alloy::network::Ethereum, @@ -178,13 +213,17 @@ type WrapAlloyDatabaseAsync = WrapDatabaseAsync< >, >; -/// Configuration struct for Pecorino network values +/// Configuration struct for Pecorino network values. #[derive(Debug, Clone)] pub struct PecorinoCfg {} impl Copy for PecorinoCfg {} impl trevm::Cfg for PecorinoCfg { + /// Fills the configuration environment with Pecorino-specific values. + /// + /// # Arguments + /// - `cfg_env`: The configuration environment to be filled. fn fill_cfg_env(&self, cfg_env: &mut CfgEnv) { let CfgEnv { chain_id, spec, .. } = cfg_env; diff --git a/tests/block_builder_test.rs b/tests/block_builder_test.rs index 6f8693a..e29f4ae 100644 --- a/tests/block_builder_test.rs +++ b/tests/block_builder_test.rs @@ -1,3 +1,4 @@ +//! Tests for the block building task. #[cfg(test)] mod tests { use alloy::{ @@ -17,6 +18,11 @@ mod tests { }; use tokio::{sync::mpsc::unbounded_channel, time::timeout}; + /// Tests the `handle_build` method of the `BlockBuilder`. + /// + /// This test sets up a simulated environment using Anvil, creates a block builder, + /// and verifies that the block builder can successfully build a block containing + /// transactions from multiple senders. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_handle_build() { setup_logging(); @@ -65,7 +71,11 @@ mod tests { assert!(got.unwrap().tx_count() == 2); } - /// Tests the full block builder loop + /// Tests the full block builder loop, including transaction ingestion and block simulation. + /// + /// This test sets up a simulated environment using Anvil, creates a block builder, + /// and verifies that the builder can process incoming transactions and produce a block + /// within a specified timeout. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_spawn() { setup_logging(); @@ -96,16 +106,18 @@ mod tests { config.chain_offset, config.target_slot_time, ); - let sim_cache = SimCache::new(); let builder = Arc::new(BlockBuilder::new(&config, ru_provider.clone(), slot_calculator)); + // Create a shared sim cache + let sim_cache = SimCache::new(); + // Create a sim cache and start filling it with items - let _ = - builder.clone().spawn_cache_handler(tx_receiver, bundle_receiver, sim_cache.clone()); + builder.clone().spawn_cache_handler(tx_receiver, bundle_receiver, sim_cache.clone()); // Finally, Kick off the block builder task. - let _ = builder.clone().spawn_builder_task(constants, sim_cache.clone(), block_sender); + builder.clone().spawn_builder_task(constants, sim_cache.clone(), block_sender); + // Feed in transactions to the tx_sender and wait for the block to be simulated let tx_1 = new_signed_tx(&test_key_0, 0, U256::from(1_f64), 11_000).unwrap(); let tx_2 = new_signed_tx(&test_key_1, 0, U256::from(2_f64), 10_000).unwrap(); tx_sender.send(tx_1).unwrap(); @@ -114,8 +126,9 @@ mod tests { // Wait for a block with timeout let result = timeout(Duration::from_secs(5), block_receiver.recv()).await; assert!(result.is_ok(), "Did not receive block within 5 seconds"); + + // Assert on the block let block = result.unwrap(); - dbg!(&block); assert!(block.is_some(), "Block channel closed without receiving a block"); assert!(block.unwrap().tx_count() == 2); // TODO: Why is this failing? I'm seeing EVM errors but haven't tracked them down yet. } From 50162b7e314194d36ffdb910c81be81fc4d0c06c Mon Sep 17 00:00:00 2001 From: dylan Date: Fri, 18 Apr 2025 21:19:08 -0600 Subject: [PATCH 24/53] update cargo paths to sdk --- Cargo.toml | 8 ++++---- bin/builder.rs | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8b74a99..4dc0bf7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,10 +24,10 @@ path = "bin/submit_transaction.rs" [dependencies] init4-bin-base = { git = "https://github.com/init4tech/bin-base.git" } -signet-zenith = { path = "../signet-sdk/crates/zenith" } -signet-types = { path = "../signet-sdk/crates/types" } -signet-bundle = { path = "../signet-sdk/crates/bundle" } -signet-sim = { path = "../signet-sdk/crates/sim" } +signet-zenith = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/sim-crate" } +signet-types = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/sim-crate" } +signet-bundle = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/sim-crate" } +signet-sim = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/sim-crate" } trevm = { version = "0.20.10", features = ["concurrent-db", "test-utils"] } diff --git a/bin/builder.rs b/bin/builder.rs index f2779c4..d170b45 100644 --- a/bin/builder.rs +++ b/bin/builder.rs @@ -52,7 +52,8 @@ async fn main() -> eyre::Result<()> { let (submit_channel, submit_jh) = submit.spawn(); let sim_items = SimCache::new(); - let slot_calculator = SlotCalculator::new(config.start_timestamp, config.chain_offset, config.target_slot_time); + let slot_calculator = + SlotCalculator::new(config.start_timestamp, config.chain_offset, config.target_slot_time); let builder = Arc::new(BlockBuilder::new(&config, ru_provider.clone(), slot_calculator)); let sim_cache_jh = From 14a0a62628846a75f760cb3cfd0df34f6cbfb4bd Mon Sep 17 00:00:00 2001 From: dylan Date: Wed, 23 Apr 2025 11:51:08 -0600 Subject: [PATCH 25/53] mark block builder test as integration test --- tests/block_builder_test.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/block_builder_test.rs b/tests/block_builder_test.rs index e29f4ae..1c85850 100644 --- a/tests/block_builder_test.rs +++ b/tests/block_builder_test.rs @@ -23,6 +23,7 @@ mod tests { /// This test sets up a simulated environment using Anvil, creates a block builder, /// and verifies that the block builder can successfully build a block containing /// transactions from multiple senders. + #[ignore = "integration test"] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_handle_build() { setup_logging(); From 1125f4d5bc1e69557b8ba1d4a781ef15d293018a Mon Sep 17 00:00:00 2001 From: dylan Date: Wed, 23 Apr 2025 14:38:22 -0600 Subject: [PATCH 26/53] cleanup --- bin/builder.rs | 9 +++++---- src/tasks/block.rs | 29 ++++++++++++++++++----------- tests/block_builder_test.rs | 12 ++++++------ 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/bin/builder.rs b/bin/builder.rs index d170b45..1ac5abb 100644 --- a/bin/builder.rs +++ b/bin/builder.rs @@ -2,7 +2,7 @@ use builder::{ config::BuilderConfig, service::serve_builder_with_span, tasks::{ - block::BlockBuilder, bundler, metrics::MetricsTask, oauth::Authenticator, + block::Simulator, bundler, metrics::MetricsTask, oauth::Authenticator, submit::SubmitTask, tx_poller, }, }; @@ -54,12 +54,13 @@ async fn main() -> eyre::Result<()> { let sim_items = SimCache::new(); let slot_calculator = SlotCalculator::new(config.start_timestamp, config.chain_offset, config.target_slot_time); - let builder = Arc::new(BlockBuilder::new(&config, ru_provider.clone(), slot_calculator)); + + let sim = Arc::new(Simulator::new(&config, ru_provider.clone(), slot_calculator)); let sim_cache_jh = - builder.clone().spawn_cache_handler(tx_receiver, bundle_receiver, sim_items.clone()); + sim.clone().spawn_cache_handler(tx_receiver, bundle_receiver, sim_items.clone()); - let build_jh = builder.clone().spawn_builder_task(constants, sim_items.clone(), submit_channel); + let build_jh = sim.clone().spawn_builder_task(constants, sim_items.clone(), submit_channel); let port = config.builder_port; let server = serve_builder_with_span(([0, 0, 0, 0], port), span); diff --git a/src/tasks/block.rs b/src/tasks/block.rs index c332dd2..2bfe001 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -13,6 +13,7 @@ use tokio::{ select, sync::mpsc::{self}, task::JoinHandle, + time::sleep, }; use trevm::{ NoopBlock, @@ -27,9 +28,10 @@ use trevm::{ /// Pecorino Chain ID used for the Pecorino network. pub const PECORINO_CHAIN_ID: u64 = 14174; -/// `BlockBuilder` is responsible for periodically building blocks and submitting them for signing and inclusion in the blockchain. +/// `Simulator` is responsible for periodically building blocks and submitting them for +/// signing and inclusion in the blockchain. #[derive(Debug)] -pub struct BlockBuilder { +pub struct Simulator { /// Configuration for the builder. pub config: BuilderConfig, /// A provider that cannot sign transactions, used for interacting with the rollup. @@ -38,8 +40,8 @@ pub struct BlockBuilder { pub slot_calculator: SlotCalculator, } -impl BlockBuilder { - /// Creates a new `BlockBuilder` instance. +impl Simulator { + /// Creates a new `Simulator` instance. /// /// # Arguments /// - `config`: The configuration for the builder. @@ -47,7 +49,7 @@ impl BlockBuilder { /// - `slot_calculator`: A slot calculator for managing block timing. /// /// # Returns - /// A new `BlockBuilder` instance. + /// A new `Simulator` instance. pub fn new( config: &BuilderConfig, ru_provider: WalletlessProvider, @@ -71,8 +73,9 @@ impl BlockBuilder { sim_items: SimCache, finish_by: Instant, ) -> Result> { - let db = self.create_db().await.unwrap(); + tracing::debug!(finish_by = ?finish_by, "starting block simulation"); + let db = self.create_db().await.unwrap(); let block_build: BlockBuild<_, NoOpInspector> = BlockBuild::new( db, constants, @@ -85,11 +88,13 @@ impl BlockBuilder { ); let block = block_build.build().await; + tracing::debug!(block = ?block, "finished block simulation"); Ok(block) } - /// Spawns a task to handle incoming transactions and bundles, adding them to the simulation cache. + /// Spawns a task to handle incoming transactions and bundles, adding them + /// to the simulation cache. /// /// # Arguments /// - `tx_receiver`: A channel receiver for incoming transactions. @@ -104,16 +109,20 @@ impl BlockBuilder { mut bundle_receiver: mpsc::UnboundedReceiver, cache: SimCache, ) -> JoinHandle<()> { + tracing::debug!("starting up cache handler"); + tokio::spawn(async move { loop { select! { maybe_tx = tx_receiver.recv() => { if let Some(tx) = maybe_tx { + tracing::debug!(tx = ?tx.hash(), "received transaction"); cache.add_item(SimItem::Tx(tx)); } } maybe_bundle = bundle_receiver.recv() => { if let Some(bundle) = maybe_bundle { + tracing::debug!(bundle = ?bundle.id, "received bundle"); cache.add_item(SimItem::Bundle(bundle.bundle)); } } @@ -137,17 +146,15 @@ impl BlockBuilder { cache: SimCache, submit_sender: mpsc::UnboundedSender, ) -> JoinHandle<()> { + tracing::debug!("starting builder task"); tokio::spawn(async move { loop { let sim_cache = cache.clone(); - let finish_by = self.calculate_deadline(); - tracing::info!("simulating until target slot deadline"); - // sleep until next wake period - tracing::info!("starting block build"); match self.handle_build(constants, sim_cache, finish_by).await { Ok(block) => { + tracing::debug!(block = ?block, "built block"); let _ = submit_sender.send(block); } Err(e) => { diff --git a/tests/block_builder_test.rs b/tests/block_builder_test.rs index 1c85850..4f8085e 100644 --- a/tests/block_builder_test.rs +++ b/tests/block_builder_test.rs @@ -6,7 +6,7 @@ mod tests { signers::local::PrivateKeySigner, }; use builder::{ - tasks::block::{BlockBuilder, PECORINO_CHAIN_ID}, + tasks::block::{Simulator, PECORINO_CHAIN_ID}, test_utils::{new_signed_tx, setup_logging, setup_test_config}, }; @@ -18,7 +18,7 @@ mod tests { }; use tokio::{sync::mpsc::unbounded_channel, time::timeout}; - /// Tests the `handle_build` method of the `BlockBuilder`. + /// Tests the `handle_build` method of the `Simulator`. /// /// This test sets up a simulated environment using Anvil, creates a block builder, /// and verifies that the block builder can successfully build a block containing @@ -50,7 +50,7 @@ mod tests { .as_secs(); dbg!(now); let slot_calculator = SlotCalculator::new(now, 0, 12); - let block_builder = BlockBuilder::new(&config, ru_provider.clone(), slot_calculator); + let block_builder = Simulator::new(&config, ru_provider.clone(), slot_calculator); // Setup a sim cache let sim_items = SimCache::new(); @@ -107,16 +107,16 @@ mod tests { config.chain_offset, config.target_slot_time, ); - let builder = Arc::new(BlockBuilder::new(&config, ru_provider.clone(), slot_calculator)); + let sim = Arc::new(Simulator::new(&config, ru_provider.clone(), slot_calculator)); // Create a shared sim cache let sim_cache = SimCache::new(); // Create a sim cache and start filling it with items - builder.clone().spawn_cache_handler(tx_receiver, bundle_receiver, sim_cache.clone()); + sim.clone().spawn_cache_handler(tx_receiver, bundle_receiver, sim_cache.clone()); // Finally, Kick off the block builder task. - builder.clone().spawn_builder_task(constants, sim_cache.clone(), block_sender); + sim.clone().spawn_builder_task(constants, sim_cache.clone(), block_sender); // Feed in transactions to the tx_sender and wait for the block to be simulated let tx_1 = new_signed_tx(&test_key_0, 0, U256::from(1_f64), 11_000).unwrap(); From acb3d42d0ebbf88842d1a0953997e00257213710 Mon Sep 17 00:00:00 2001 From: dylan Date: Wed, 23 Apr 2025 14:38:49 -0600 Subject: [PATCH 27/53] clippy + fmt --- bin/builder.rs | 6 +++--- src/tasks/block.rs | 1 - tests/block_builder_test.rs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/bin/builder.rs b/bin/builder.rs index 1ac5abb..db3b7cd 100644 --- a/bin/builder.rs +++ b/bin/builder.rs @@ -2,8 +2,8 @@ use builder::{ config::BuilderConfig, service::serve_builder_with_span, tasks::{ - block::Simulator, bundler, metrics::MetricsTask, oauth::Authenticator, - submit::SubmitTask, tx_poller, + block::Simulator, bundler, metrics::MetricsTask, oauth::Authenticator, submit::SubmitTask, + tx_poller, }, }; use signet_sim::SimCache; @@ -54,7 +54,7 @@ async fn main() -> eyre::Result<()> { let sim_items = SimCache::new(); let slot_calculator = SlotCalculator::new(config.start_timestamp, config.chain_offset, config.target_slot_time); - + let sim = Arc::new(Simulator::new(&config, ru_provider.clone(), slot_calculator)); let sim_cache_jh = diff --git a/src/tasks/block.rs b/src/tasks/block.rs index 2bfe001..ff774ca 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -13,7 +13,6 @@ use tokio::{ select, sync::mpsc::{self}, task::JoinHandle, - time::sleep, }; use trevm::{ NoopBlock, diff --git a/tests/block_builder_test.rs b/tests/block_builder_test.rs index 4f8085e..57ee023 100644 --- a/tests/block_builder_test.rs +++ b/tests/block_builder_test.rs @@ -6,7 +6,7 @@ mod tests { signers::local::PrivateKeySigner, }; use builder::{ - tasks::block::{Simulator, PECORINO_CHAIN_ID}, + tasks::block::{PECORINO_CHAIN_ID, Simulator}, test_utils::{new_signed_tx, setup_logging, setup_test_config}, }; From 4639365775b4d5e08ccd92d9749ba4d848d0a0b9 Mon Sep 17 00:00:00 2001 From: dylan Date: Wed, 23 Apr 2025 16:25:57 -0600 Subject: [PATCH 28/53] cleanup --- bin/builder.rs | 2 +- src/tasks/block.rs | 5 +++-- tests/block_builder_test.rs | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bin/builder.rs b/bin/builder.rs index db3b7cd..1f89f5b 100644 --- a/bin/builder.rs +++ b/bin/builder.rs @@ -60,7 +60,7 @@ async fn main() -> eyre::Result<()> { let sim_cache_jh = sim.clone().spawn_cache_handler(tx_receiver, bundle_receiver, sim_items.clone()); - let build_jh = sim.clone().spawn_builder_task(constants, sim_items.clone(), submit_channel); + let build_jh = sim.clone().spawn_simulator_task(constants, sim_items.clone(), submit_channel); let port = config.builder_port; let server = serve_builder_with_span(([0, 0, 0, 0], port), span); diff --git a/src/tasks/block.rs b/src/tasks/block.rs index ff774ca..e09d0bf 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -130,7 +130,8 @@ impl Simulator { }) } - /// Spawns the block building task. + /// Spawns the simulator task, which handles the setup and sets the deadline + /// for the each round of simulation. /// /// # Arguments /// - `constants`: The system constants for the rollup. @@ -139,7 +140,7 @@ impl Simulator { /// /// # Returns /// A `JoinHandle` for the spawned task. - pub fn spawn_builder_task( + pub fn spawn_simulator_task( self: Arc, constants: SignetSystemConstants, cache: SimCache, diff --git a/tests/block_builder_test.rs b/tests/block_builder_test.rs index 57ee023..428c84a 100644 --- a/tests/block_builder_test.rs +++ b/tests/block_builder_test.rs @@ -116,7 +116,7 @@ mod tests { sim.clone().spawn_cache_handler(tx_receiver, bundle_receiver, sim_cache.clone()); // Finally, Kick off the block builder task. - sim.clone().spawn_builder_task(constants, sim_cache.clone(), block_sender); + sim.clone().spawn_simulator_task(constants, sim_cache.clone(), block_sender); // Feed in transactions to the tx_sender and wait for the block to be simulated let tx_1 = new_signed_tx(&test_key_0, 0, U256::from(1_f64), 11_000).unwrap(); From 9e1bc74524b7e91eed8974b801c3a9c04c44c3c6 Mon Sep 17 00:00:00 2001 From: dylan Date: Thu, 24 Apr 2025 11:36:02 -0600 Subject: [PATCH 29/53] adds basefee checker to sim cache handling --- bin/builder.rs | 7 ++-- src/tasks/block.rs | 71 +++++++++++++++++++++++++++++++------ tests/block_builder_test.rs | 6 ++-- 3 files changed, 68 insertions(+), 16 deletions(-) diff --git a/bin/builder.rs b/bin/builder.rs index 1f89f5b..23c4f5a 100644 --- a/bin/builder.rs +++ b/bin/builder.rs @@ -57,8 +57,8 @@ async fn main() -> eyre::Result<()> { let sim = Arc::new(Simulator::new(&config, ru_provider.clone(), slot_calculator)); - let sim_cache_jh = - sim.clone().spawn_cache_handler(tx_receiver, bundle_receiver, sim_items.clone()); + let (basefee_jh, sim_cache_jh) = + sim.clone().spawn_cache_task(tx_receiver, bundle_receiver, sim_items.clone()); let build_jh = sim.clone().spawn_simulator_task(constants, sim_items.clone(), submit_channel); @@ -75,6 +75,9 @@ async fn main() -> eyre::Result<()> { _ = sim_cache_jh => { tracing::info!("sim cache task finished"); } + _ = basefee_jh => { + tracing::info!("basefee task finished"); + } _ = submit_jh => { tracing::info!("submit finished"); }, diff --git a/src/tasks/block.rs b/src/tasks/block.rs index e09d0bf..fa14f5a 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -2,7 +2,11 @@ use crate::{ config::{BuilderConfig, WalletlessProvider}, tasks::bundler::Bundle, }; -use alloy::{consensus::TxEnvelope, eips::BlockId, providers::Provider}; +use alloy::{ + consensus::TxEnvelope, + eips::{BlockId, BlockNumberOrTag::Latest}, + providers::Provider, +}; use signet_sim::{BlockBuild, BuiltBlock, SimCache, SimItem}; use signet_types::{SlotCalculator, config::SignetSystemConstants}; use std::{ @@ -11,8 +15,12 @@ use std::{ }; use tokio::{ select, - sync::mpsc::{self}, + sync::{ + RwLock, + mpsc::{self}, + }, task::JoinHandle, + time::sleep, }; use trevm::{ NoopBlock, @@ -92,8 +100,8 @@ impl Simulator { Ok(block) } - /// Spawns a task to handle incoming transactions and bundles, adding them - /// to the simulation cache. + /// Spawns two tasks: one to handle incoming transactions and bundles, + /// adding them to the simulation cache, and one to track the latest basefee. /// /// # Arguments /// - `tx_receiver`: A channel receiver for incoming transactions. @@ -101,33 +109,74 @@ impl Simulator { /// - `cache`: The simulation cache to store the received items. /// /// # Returns - /// A `JoinHandle` for the spawned task. - pub fn spawn_cache_handler( + /// A `JoinHandle` for the basefee updater and a `JoinHandle` for the + /// cache handler. + pub fn spawn_cache_task( self: Arc, mut tx_receiver: mpsc::UnboundedReceiver, mut bundle_receiver: mpsc::UnboundedReceiver, cache: SimCache, - ) -> JoinHandle<()> { + ) -> (JoinHandle<()>, JoinHandle<()>) { tracing::debug!("starting up cache handler"); - tokio::spawn(async move { + let shared_price = Arc::new(RwLock::new(0_u64)); + let price_updater = Arc::clone(&shared_price); + let price_reader = Arc::clone(&shared_price); + + // Update the basefee on a per-block cadence + let basefee_jh = tokio::spawn(async move { + loop { + // calculate next slot position plus a small buffer to + let time_remaining = self.slot_calculator.slot_duration() + - self.slot_calculator.current_timepoint_within_slot() + + 1; + tracing::debug!(time_remaining = ?time_remaining, "sleeping until next slot"); + + // wait until that point in time + sleep(Duration::from_secs(time_remaining)).await; + + // update the basefee with that price + tracing::debug!("checking latest basefee"); + let resp = self.ru_provider.get_block_by_number(Latest).await; + + if let Ok(maybe_block) = resp { + match maybe_block { + Some(block) => { + let basefee = block.header.base_fee_per_gas.unwrap_or_default(); + let mut w = price_updater.write().await; + *w = basefee; + tracing::debug!(basefee = %basefee, "basefee updated"); + } + None => { + tracing::debug!("no block found; basefee not updated.") + } + } + } + } + }); + + // Update the sim cache whenever a transaction or bundle is received with respect to the basefee + let cache_jh = tokio::spawn(async move { loop { + let p = price_reader.read().await; select! { maybe_tx = tx_receiver.recv() => { if let Some(tx) = maybe_tx { tracing::debug!(tx = ?tx.hash(), "received transaction"); - cache.add_item(SimItem::Tx(tx)); + cache.add_item(SimItem::Tx(tx), *p); } } maybe_bundle = bundle_receiver.recv() => { if let Some(bundle) = maybe_bundle { tracing::debug!(bundle = ?bundle.id, "received bundle"); - cache.add_item(SimItem::Bundle(bundle.bundle)); + cache.add_item(SimItem::Bundle(bundle.bundle), *p); } } } } - }) + }); + + (basefee_jh, cache_jh) } /// Spawns the simulator task, which handles the setup and sets the deadline diff --git a/tests/block_builder_test.rs b/tests/block_builder_test.rs index 428c84a..ca35d51 100644 --- a/tests/block_builder_test.rs +++ b/tests/block_builder_test.rs @@ -57,10 +57,10 @@ mod tests { // Add two transactions from two senders to the sim cache let tx_1 = new_signed_tx(&test_key_0, 0, U256::from(1_f64), 11_000).unwrap(); - sim_items.add_item(SimItem::Tx(tx_1)); + sim_items.add_item(SimItem::Tx(tx_1), 0); let tx_2 = new_signed_tx(&test_key_1, 0, U256::from(2_f64), 10_000).unwrap(); - sim_items.add_item(SimItem::Tx(tx_2)); + sim_items.add_item(SimItem::Tx(tx_2), 0); let finish_by = Instant::now() + Duration::from_secs(2); @@ -113,7 +113,7 @@ mod tests { let sim_cache = SimCache::new(); // Create a sim cache and start filling it with items - sim.clone().spawn_cache_handler(tx_receiver, bundle_receiver, sim_cache.clone()); + sim.clone().spawn_cache_task(tx_receiver, bundle_receiver, sim_cache.clone()); // Finally, Kick off the block builder task. sim.clone().spawn_simulator_task(constants, sim_cache.clone(), block_sender); From 06da0b5c05d320da03b5d236ec23fdaee915d4a5 Mon Sep 17 00:00:00 2001 From: dylan Date: Thu, 24 Apr 2025 11:47:48 -0600 Subject: [PATCH 30/53] fix pointer changes --- src/tasks/submit.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tasks/submit.rs b/src/tasks/submit.rs index 2b578cf..df8cd2c 100644 --- a/src/tasks/submit.rs +++ b/src/tasks/submit.rs @@ -113,7 +113,7 @@ impl SubmitTask { ru_chain_id, gas_limit: U256::from(self.config.rollup_block_gas_limit), ru_reward_address: self.config.builder_rewards_address, - contents: contents.contents_hash(), + contents: *contents.contents_hash(), }) } @@ -156,7 +156,7 @@ impl SubmitTask { rollupChainId: U256::from(self.config.ru_chain_id), gasLimit: resp.req.gas_limit, rewardAddress: resp.req.ru_reward_address, - blockDataHash: in_progress.contents_hash(), + blockDataHash: *in_progress.contents_hash(), }; let fills = vec![]; // NB: ignored until fills are implemented From 70fdac121b9f9bd772c578873afd1a9e3ec4e05c Mon Sep 17 00:00:00 2001 From: dylan Date: Thu, 24 Apr 2025 12:28:21 -0600 Subject: [PATCH 31/53] ignore test spawn --- tests/block_builder_test.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/block_builder_test.rs b/tests/block_builder_test.rs index ca35d51..d109e9f 100644 --- a/tests/block_builder_test.rs +++ b/tests/block_builder_test.rs @@ -77,6 +77,7 @@ mod tests { /// This test sets up a simulated environment using Anvil, creates a block builder, /// and verifies that the builder can process incoming transactions and produce a block /// within a specified timeout. + #[ignore = "integration test"] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_spawn() { setup_logging(); From 6b59e39ff0ff8a47cec55e4663ea32dee20315ba Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 28 Apr 2025 11:09:32 -0600 Subject: [PATCH 32/53] update signet-sim deps to main branch --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4dc0bf7..640528b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,10 +24,10 @@ path = "bin/submit_transaction.rs" [dependencies] init4-bin-base = { git = "https://github.com/init4tech/bin-base.git" } -signet-zenith = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/sim-crate" } -signet-types = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/sim-crate" } -signet-bundle = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/sim-crate" } -signet-sim = { git = "https://github.com/init4tech/signet-sdk", branch = "prestwich/sim-crate" } +signet-zenith = { git = "https://github.com/init4tech/signet-sdk", branch = "main" } +signet-types = { git = "https://github.com/init4tech/signet-sdk", branch = "main" } +signet-bundle = { git = "https://github.com/init4tech/signet-sdk", branch = "main" } +signet-sim = { git = "https://github.com/init4tech/signet-sdk", branch = "main" } trevm = { version = "0.20.10", features = ["concurrent-db", "test-utils"] } From 09fc552babcb152119e0334550c1f04753036745 Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 28 Apr 2025 11:10:01 -0600 Subject: [PATCH 33/53] try_join the setup of host, rollup, and sequencer services --- bin/builder.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bin/builder.rs b/bin/builder.rs index 23c4f5a..5f7dcfe 100644 --- a/bin/builder.rs +++ b/bin/builder.rs @@ -21,10 +21,11 @@ async fn main() -> eyre::Result<()> { let constants = config.load_pecorino_constants(); let authenticator = Authenticator::new(&config); - let host_provider = config.connect_host_provider().await?; - let ru_provider = config.connect_ru_provider().await?; - - let sequencer_signer = config.connect_sequencer_signer().await?; + let (host_provider, ru_provider, sequencer_signer) = tokio::try_join!( + config.connect_host_provider(), + config.connect_ru_provider(), + config.connect_sequencer_signer(), + )?; let zenith = config.connect_zenith(host_provider.clone()); From 830f297131be2762fc665ea00d1e88e11565df5d Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 28 Apr 2025 11:20:29 -0600 Subject: [PATCH 34/53] removes unused error from ConfigError --- src/config.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/config.rs b/src/config.rs index e4e9079..36622f6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -123,9 +123,6 @@ pub enum ConfigError { /// Error connecting to the signer #[error("failed to connect to signer: {0}")] Signer(#[from] SignerError), - /// Error parsing the provided genesis file - #[error("failed")] - Genesis(String), } impl ConfigError { From 1ad4974e73af42694551e240a365a0b6803c8a41 Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 28 Apr 2025 11:46:34 -0600 Subject: [PATCH 35/53] use AtomicU64 instead of RwLock in cache task handler --- src/tasks/block.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/tasks/block.rs b/src/tasks/block.rs index fa14f5a..d9e8319 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -10,15 +10,15 @@ use alloy::{ use signet_sim::{BlockBuild, BuiltBlock, SimCache, SimItem}; use signet_types::{SlotCalculator, config::SignetSystemConstants}; use std::{ - sync::Arc, + sync::{ + Arc, + atomic::{AtomicU64, Ordering}, + }, time::{Duration, Instant, SystemTime, UNIX_EPOCH}, }; use tokio::{ select, - sync::{ - RwLock, - mpsc::{self}, - }, + sync::mpsc::{self}, task::JoinHandle, time::sleep, }; @@ -119,7 +119,7 @@ impl Simulator { ) -> (JoinHandle<()>, JoinHandle<()>) { tracing::debug!("starting up cache handler"); - let shared_price = Arc::new(RwLock::new(0_u64)); + let shared_price = Arc::new(AtomicU64::new(0_u64)); let price_updater = Arc::clone(&shared_price); let price_reader = Arc::clone(&shared_price); @@ -143,8 +143,7 @@ impl Simulator { match maybe_block { Some(block) => { let basefee = block.header.base_fee_per_gas.unwrap_or_default(); - let mut w = price_updater.write().await; - *w = basefee; + price_updater.store(basefee, Ordering::Relaxed); tracing::debug!(basefee = %basefee, "basefee updated"); } None => { @@ -158,18 +157,18 @@ impl Simulator { // Update the sim cache whenever a transaction or bundle is received with respect to the basefee let cache_jh = tokio::spawn(async move { loop { - let p = price_reader.read().await; + let p = price_reader.load(Ordering::Relaxed); select! { maybe_tx = tx_receiver.recv() => { if let Some(tx) = maybe_tx { tracing::debug!(tx = ?tx.hash(), "received transaction"); - cache.add_item(SimItem::Tx(tx), *p); + cache.add_item(SimItem::Tx(tx), p); } } maybe_bundle = bundle_receiver.recv() => { if let Some(bundle) = maybe_bundle { tracing::debug!(bundle = ?bundle.id, "received bundle"); - cache.add_item(SimItem::Bundle(bundle.bundle), *p); + cache.add_item(SimItem::Bundle(bundle.bundle), p); } } } From b4c64da68b4ff18d7a6d86554243ad0a812de768 Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 28 Apr 2025 12:59:25 -0600 Subject: [PATCH 36/53] cleanup & refactors - adds newlines after arguments and returns sections in comments - pulls several functions out into methods --- src/tasks/block.rs | 213 +++++++++++++++++++++++++++++++-------------- 1 file changed, 148 insertions(+), 65 deletions(-) diff --git a/src/tasks/block.rs b/src/tasks/block.rs index d9e8319..860fd51 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -51,11 +51,13 @@ impl Simulator { /// Creates a new `Simulator` instance. /// /// # Arguments + /// /// - `config`: The configuration for the builder. /// - `ru_provider`: A provider for interacting with the rollup. /// - `slot_calculator`: A slot calculator for managing block timing. /// /// # Returns + /// /// A new `Simulator` instance. pub fn new( config: &BuilderConfig, @@ -68,11 +70,13 @@ impl Simulator { /// Handles building a single block. /// /// # Arguments + /// /// - `constants`: The system constants for the rollup. /// - `sim_items`: The simulation cache containing transactions and bundles. /// - `finish_by`: The deadline by which the block must be built. /// /// # Returns + /// /// A `Result` containing the built block or an error. pub async fn handle_build( &self, @@ -104,89 +108,109 @@ impl Simulator { /// adding them to the simulation cache, and one to track the latest basefee. /// /// # Arguments + /// /// - `tx_receiver`: A channel receiver for incoming transactions. /// - `bundle_receiver`: A channel receiver for incoming bundles. /// - `cache`: The simulation cache to store the received items. /// /// # Returns + /// /// A `JoinHandle` for the basefee updater and a `JoinHandle` for the /// cache handler. pub fn spawn_cache_task( self: Arc, - mut tx_receiver: mpsc::UnboundedReceiver, - mut bundle_receiver: mpsc::UnboundedReceiver, + tx_receiver: mpsc::UnboundedReceiver, + bundle_receiver: mpsc::UnboundedReceiver, cache: SimCache, ) -> (JoinHandle<()>, JoinHandle<()>) { tracing::debug!("starting up cache handler"); - let shared_price = Arc::new(AtomicU64::new(0_u64)); - let price_updater = Arc::clone(&shared_price); - let price_reader = Arc::clone(&shared_price); + let basefee_price = Arc::new(AtomicU64::new(0_u64)); + let basefee_reader = Arc::clone(&basefee_price); // Update the basefee on a per-block cadence - let basefee_jh = tokio::spawn(async move { - loop { - // calculate next slot position plus a small buffer to - let time_remaining = self.slot_calculator.slot_duration() - - self.slot_calculator.current_timepoint_within_slot() - + 1; - tracing::debug!(time_remaining = ?time_remaining, "sleeping until next slot"); - - // wait until that point in time - sleep(Duration::from_secs(time_remaining)).await; - - // update the basefee with that price - tracing::debug!("checking latest basefee"); - let resp = self.ru_provider.get_block_by_number(Latest).await; - - if let Ok(maybe_block) = resp { - match maybe_block { - Some(block) => { - let basefee = block.header.base_fee_per_gas.unwrap_or_default(); - price_updater.store(basefee, Ordering::Relaxed); - tracing::debug!(basefee = %basefee, "basefee updated"); - } - None => { - tracing::debug!("no block found; basefee not updated.") - } - } - } - } - }); + let basefee_jh = + tokio::spawn(async move { self.basefee_updater(Arc::clone(&basefee_price)).await }); // Update the sim cache whenever a transaction or bundle is received with respect to the basefee let cache_jh = tokio::spawn(async move { - loop { - let p = price_reader.load(Ordering::Relaxed); - select! { - maybe_tx = tx_receiver.recv() => { - if let Some(tx) = maybe_tx { - tracing::debug!(tx = ?tx.hash(), "received transaction"); - cache.add_item(SimItem::Tx(tx), p); - } - } - maybe_bundle = bundle_receiver.recv() => { - if let Some(bundle) = maybe_bundle { - tracing::debug!(bundle = ?bundle.id, "received bundle"); - cache.add_item(SimItem::Bundle(bundle.bundle), p); - } - } - } - } + cache_updater(tx_receiver, bundle_receiver, cache, basefee_reader).await }); (basefee_jh, cache_jh) } + /// Periodically updates the shared basefee by querying the latest block. + /// + /// This function calculates the remaining time until the next slot, + /// sleeps until that time, and then retrieves the latest basefee from the rollup provider. + /// The updated basefee is stored in the provided `AtomicU64`. + /// + /// This function runs continuously. + /// + /// # Arguments + /// + /// - `price`: A shared `Arc` used to store the updated basefee value. + async fn basefee_updater(self: Arc, price: Arc) { + loop { + // calculate start of next slot plus a small buffer + let time_remaining = self.slot_calculator.slot_duration() + - self.slot_calculator.current_timepoint_within_slot() + + 1; + tracing::debug!(time_remaining = ?time_remaining, "basefee updater sleeping until next slot"); + + // wait until that point in time + sleep(Duration::from_secs(time_remaining)).await; + + // update the basefee with that price + self.check_basefee(&price).await; + } + } + + /// Queries the latest block from the rollup provider and updates the shared + /// basefee value if a block is found. + /// + /// This function retrieves the latest block using the provider, extracts the + /// `base_fee_per_gas` field from the block header (defaulting to zero if missing), + /// and updates the shared `AtomicU64` price tracker. If no block is available, + /// it logs a message without updating the price. + /// + /// # Arguments + /// + /// - `price`: A shared `Arc` used to store the updated basefee. + async fn check_basefee(&self, price: &Arc) { + tracing::debug!("checking latest basefee"); + let resp = self.ru_provider.get_block_by_number(Latest).await; + if let Err(e) = resp { + tracing::debug!(err = %e, "basefee check failed with rpc error"); + return; + } + + if let Ok(maybe_block) = resp { + match maybe_block { + Some(block) => { + let basefee = block.header.base_fee_per_gas.unwrap_or_default(); + price.store(basefee, Ordering::Relaxed); + tracing::debug!(basefee = %basefee, "basefee updated"); + } + None => { + tracing::debug!("no block found; basefee not updated.") + } + } + } + } + /// Spawns the simulator task, which handles the setup and sets the deadline /// for the each round of simulation. /// /// # Arguments + /// /// - `constants`: The system constants for the rollup. /// - `cache`: The simulation cache containing transactions and bundles. /// - `submit_sender`: A channel sender for submitting built blocks. /// /// # Returns + /// /// A `JoinHandle` for the spawned task. pub fn spawn_simulator_task( self: Arc, @@ -195,28 +219,49 @@ impl Simulator { submit_sender: mpsc::UnboundedSender, ) -> JoinHandle<()> { tracing::debug!("starting builder task"); - tokio::spawn(async move { - loop { - let sim_cache = cache.clone(); - let finish_by = self.calculate_deadline(); + tokio::spawn(async move { self.run_simulator(constants, cache, submit_sender).await }) + } - match self.handle_build(constants, sim_cache, finish_by).await { - Ok(block) => { - tracing::debug!(block = ?block, "built block"); - let _ = submit_sender.send(block); - } - Err(e) => { - tracing::error!(err = %e, "failed to send block"); - continue; - } + /// Continuously runs the block simulation and submission loop. + /// + /// This function clones the simulation cache, calculates a deadline for block building, + /// attempts to build a block using the latest cache and constants, and submits the built + /// block through the provided channel. If an error occurs during block building or submission, + /// it logs the error and continues the loop. + /// + /// This function runs indefinitely and never returns. + /// + /// # Arguments + /// - `constants`: The system constants for the rollup. + /// - `cache`: The simulation cache containing transactions and bundles. + /// - `submit_sender`: A channel sender used to submit built blocks. + async fn run_simulator( + self: Arc, + constants: SignetSystemConstants, + cache: SimCache, + submit_sender: mpsc::UnboundedSender, + ) { + loop { + let sim_cache = cache.clone(); + let finish_by = self.calculate_deadline(); + + match self.handle_build(constants, sim_cache, finish_by).await { + Ok(block) => { + tracing::debug!(block = ?block, "built block"); + let _ = submit_sender.send(block); + } + Err(e) => { + tracing::error!(err = %e, "failed to send block"); + continue; } } - }) + } } /// Calculates the deadline for the current block simulation. /// /// # Returns + /// /// An `Instant` representing the deadline. pub fn calculate_deadline(&self) -> Instant { let now = SystemTime::now(); @@ -228,6 +273,7 @@ impl Simulator { /// Creates an `AlloyDB` instance from the rollup provider. /// /// # Returns + /// /// An `Option` containing the wrapped database or `None` if an error occurs. async fn create_db(&self) -> Option { let latest = match self.ru_provider.get_block_number().await { @@ -245,6 +291,43 @@ impl Simulator { } } +/// Continuously updates the simulation cache with incoming transactions and bundles. +/// +/// This function listens for new transactions and bundles on their respective +/// channels and adds them to the simulation cache using the latest observed basefee. +/// +/// # Arguments +/// - `tx_receiver`: A receiver channel for incoming Ethereum transactions. +/// - `bundle_receiver`: A receiver channel for incoming transaction bundles. +/// - `cache`: The simulation cache used to store transactions and bundles. +/// - `price_reader`: An `Arc` providing the latest basefee for simulation pricing. +async fn cache_updater( + mut tx_receiver: mpsc::UnboundedReceiver< + alloy::consensus::EthereumTxEnvelope, + >, + mut bundle_receiver: mpsc::UnboundedReceiver, + cache: SimCache, + price_reader: Arc, +) -> ! { + loop { + let p = price_reader.load(Ordering::Relaxed); + select! { + maybe_tx = tx_receiver.recv() => { + if let Some(tx) = maybe_tx { + tracing::debug!(tx = ?tx.hash(), "received transaction"); + cache.add_item(SimItem::Tx(tx), p); + } + } + maybe_bundle = bundle_receiver.recv() => { + if let Some(bundle) = maybe_bundle { + tracing::debug!(bundle = ?bundle.id, "received bundle"); + cache.add_item(SimItem::Bundle(bundle.bundle), p); + } + } + } + } +} + /// The wrapped alloy database type that is compatible with `Db` and `DatabaseRef`. type WrapAlloyDatabaseAsync = WrapDatabaseAsync< AlloyDB< From 8300f03d290fc0680574546e430353a5c00c4379 Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 28 Apr 2025 13:48:58 -0600 Subject: [PATCH 37/53] removes unnecessary `SimItem` usage from cache updater --- src/tasks/block.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tasks/block.rs b/src/tasks/block.rs index 860fd51..f085bb9 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -7,7 +7,7 @@ use alloy::{ eips::{BlockId, BlockNumberOrTag::Latest}, providers::Provider, }; -use signet_sim::{BlockBuild, BuiltBlock, SimCache, SimItem}; +use signet_sim::{BlockBuild, BuiltBlock, SimCache}; use signet_types::{SlotCalculator, config::SignetSystemConstants}; use std::{ sync::{ @@ -219,6 +219,7 @@ impl Simulator { submit_sender: mpsc::UnboundedSender, ) -> JoinHandle<()> { tracing::debug!("starting builder task"); + tokio::spawn(async move { self.run_simulator(constants, cache, submit_sender).await }) } @@ -315,13 +316,13 @@ async fn cache_updater( maybe_tx = tx_receiver.recv() => { if let Some(tx) = maybe_tx { tracing::debug!(tx = ?tx.hash(), "received transaction"); - cache.add_item(SimItem::Tx(tx), p); + cache.add_item(tx, p); } } maybe_bundle = bundle_receiver.recv() => { if let Some(bundle) = maybe_bundle { tracing::debug!(bundle = ?bundle.id, "received bundle"); - cache.add_item(SimItem::Bundle(bundle.bundle), p); + cache.add_item(bundle.bundle, p); } } } From f27dde8ca6b76cc127f7370e0621afe2f19219c8 Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 28 Apr 2025 13:51:30 -0600 Subject: [PATCH 38/53] makes tx-pool hold a `Client` reference for reuse --- src/tasks/bundler.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/tasks/bundler.rs b/src/tasks/bundler.rs index 9b526fc..8664874 100644 --- a/src/tasks/bundler.rs +++ b/src/tasks/bundler.rs @@ -2,7 +2,7 @@ pub use crate::config::BuilderConfig; use crate::tasks::oauth::Authenticator; use oauth2::TokenResponse; -use reqwest::Url; +use reqwest::{Client, Url}; use serde::{Deserialize, Serialize}; use signet_bundle::SignetEthBundle; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}; @@ -32,6 +32,8 @@ pub struct BundlePoller { pub config: BuilderConfig, /// Authentication module that periodically fetches and stores auth tokens. pub authenticator: Authenticator, + /// Holds a Reqwest client + pub client: Client, /// Defines the interval at which the bundler polls the tx-pool for bundles. pub poll_interval_ms: u64, } @@ -40,7 +42,12 @@ pub struct BundlePoller { impl BundlePoller { /// Creates a new BundlePoller from the provided builder config. pub fn new(config: &BuilderConfig, authenticator: Authenticator) -> Self { - Self { config: config.clone(), authenticator, poll_interval_ms: 1000 } + Self { + config: config.clone(), + authenticator, + client: Client::new(), + poll_interval_ms: 1000, + } } /// Creates a new BundlePoller from the provided builder config and with the specified poll interval in ms. @@ -49,7 +56,7 @@ impl BundlePoller { authenticator: Authenticator, poll_interval_ms: u64, ) -> Self { - Self { config: config.clone(), authenticator, poll_interval_ms } + Self { config: config.clone(), authenticator, client: Client::new(), poll_interval_ms } } /// Fetches bundles from the transaction cache and returns them. @@ -57,7 +64,8 @@ impl BundlePoller { let bundle_url: Url = Url::parse(&self.config.tx_pool_url)?.join("bundles")?; let token = self.authenticator.fetch_oauth_token().await?; - let result = reqwest::Client::new() + let result = self + .client .get(bundle_url) .bearer_auth(token.access_token().secret()) .send() From f63fe6b3441aab3951c353e0ae77ccec01fdcf10 Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 28 Apr 2025 13:57:43 -0600 Subject: [PATCH 39/53] update comment --- src/tasks/tx_poller.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tasks/tx_poller.rs b/src/tasks/tx_poller.rs index abc9430..bbf2b53 100644 --- a/src/tasks/tx_poller.rs +++ b/src/tasks/tx_poller.rs @@ -26,7 +26,8 @@ pub struct TxPoller { pub poll_interval_ms: u64, } -/// [`TxPoller`] implements a poller task that fetches unique transactions from the transaction pool. +/// [`TxPoller`] implements a poller task that fetches transactions from the transaction pool +/// and sends them into the provided channel sender. impl TxPoller { /// Returns a new [`TxPoller`] with the given config. /// * Defaults to 1000ms poll interval (1s). From 79977bdd8f63c091434b5b4b57ca976a8b7dc23f Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 28 Apr 2025 15:01:41 -0600 Subject: [PATCH 40/53] sets concurrency limit by calling `available_parallelism` or overriding with env config --- src/config.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 36622f6..6808a31 100644 --- a/src/config.rs +++ b/src/config.rs @@ -123,6 +123,9 @@ pub enum ConfigError { /// Error connecting to the signer #[error("failed to connect to signer: {0}")] Signer(#[from] SignerError), + /// Error checking available system concurrency + #[error("failed to determine system concurrency: {0}")] + Io(#[from] std::io::Error), } impl ConfigError { @@ -192,7 +195,7 @@ impl BuilderConfig { oauth_authenticate_url: load_string(OAUTH_AUTHENTICATE_URL)?, oauth_token_url: load_string(OAUTH_TOKEN_URL)?, oauth_token_refresh_interval: load_u64(AUTH_TOKEN_REFRESH_INTERVAL)?, - concurrency_limit: load_u64(CONCURRENCY_LIMIT).map(|v| v as usize).unwrap_or(1000), + concurrency_limit: load_concurrency_limit()?, start_timestamp: load_u64(START_TIMESTAMP)?, }) } @@ -320,3 +323,15 @@ pub fn load_address(key: &str) -> Result { Address::from_str(&address) .map_err(|_| ConfigError::Var(format!("Invalid address format for {}", key))) } + +/// Checks the configured concurrency parameter and, if none is set, checks the available +/// system concurrency with `std::thread::available_parallelism` and returns that. +pub fn load_concurrency_limit() -> Result { + match load_u16(CONCURRENCY_LIMIT) { + Ok(env) => Ok(env as usize), + Err(_) => { + let limit = std::thread::available_parallelism()?.get(); + Ok(limit) + } + } +} From c8deba9ca8330def74895fdaf2b110894d55ee6c Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 28 Apr 2025 15:50:15 -0600 Subject: [PATCH 41/53] refactors the rollup provider type - refactors the rollup provider type from a FillProvider to a RootProvider - defines the alloy database provider type around this - updates relevant connection functions to use the new RuProvider type - renames Provider to HostProvider - renames WalletlessProvider to RuProvider --- bin/submit_transaction.rs | 6 ++--- src/config.rs | 45 ++++++++++++++----------------------- src/tasks/block.rs | 44 +++++++++++------------------------- src/tasks/metrics.rs | 4 ++-- src/tasks/submit.rs | 4 ++-- tests/block_builder_test.rs | 6 ++--- 6 files changed, 40 insertions(+), 69 deletions(-) diff --git a/bin/submit_transaction.rs b/bin/submit_transaction.rs index addb205..6aec853 100644 --- a/bin/submit_transaction.rs +++ b/bin/submit_transaction.rs @@ -6,7 +6,7 @@ use alloy::{ signers::aws::AwsSigner, }; use aws_config::BehaviorVersion; -use builder::config::{Provider, load_address, load_string, load_u64, load_url}; +use builder::config::{HostProvider, load_address, load_string, load_u64, load_url}; use init4_bin_base::{ deps::metrics::{counter, histogram}, init4, @@ -30,7 +30,7 @@ async fn main() { } } -async fn send_transaction(provider: Provider, recipient_address: Address) { +async fn send_transaction(provider: HostProvider, recipient_address: Address) { // construct simple transaction to send ETH to a recipient let tx = TransactionRequest::default() .with_from(provider.default_signer_address()) @@ -67,7 +67,7 @@ async fn send_transaction(provider: Provider, recipient_address: Address) { histogram!("txn_submitter.tx_mine_time").record(mine_time as f64); } -async fn connect_from_config() -> (Provider, Address, u64) { +async fn connect_from_config() -> (HostProvider, Address, u64) { // load signer config values from .env let rpc_url = load_url("RPC_URL").unwrap(); let chain_id = load_u64("CHAIN_ID").unwrap(); diff --git a/src/config.rs b/src/config.rs index 6808a31..c15f72e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -11,6 +11,7 @@ use alloy::{ }, }; use eyre::Result; +use oauth2::url; use signet_types::config::{HostConfig, PredeployTokens, RollupConfig, SignetSystemConstants}; use signet_zenith::Zenith; use std::{borrow::Cow, env, num, str::FromStr}; @@ -135,8 +136,8 @@ impl ConfigError { } } -/// Type alias for the provider used in the builder. -pub type Provider = FillProvider< +/// Type alias for the provider used to build and submit blocks to the host. +pub type HostProvider = FillProvider< JoinFill< JoinFill< Identity, @@ -148,18 +149,11 @@ pub type Provider = FillProvider< Ethereum, >; -/// Type alias for the provider used in the builder, without a wallet. -pub type WalletlessProvider = FillProvider< - JoinFill< - Identity, - JoinFill>>, - >, - RootProvider, - Ethereum, ->; +/// Type alias for the provider used to simulate against rollup state. +pub type RuProvider = RootProvider; /// A [`Zenith`] contract instance using [`Provider`] as the provider. -pub type ZenithInstance

= Zenith::ZenithInstance<(), P, alloy::network::Ethereum>; +pub type ZenithInstance

= Zenith::ZenithInstance<(), P, alloy::network::Ethereum>; impl BuilderConfig { /// Load the builder configuration from environment variables. @@ -217,17 +211,14 @@ impl BuilderConfig { } /// Connect to the Rollup rpc provider. - pub async fn connect_ru_provider(&self) -> Result { - let provider = ProviderBuilder::new() - .connect(&self.ru_rpc_url) - .await - .map_err(ConfigError::Provider)?; - + pub async fn connect_ru_provider(&self) -> Result, ConfigError> { + let url = url::Url::parse(&self.ru_rpc_url).expect("failed to parse URL"); + let provider = RootProvider::::new_http(url); Ok(provider) } /// Connect to the Host rpc provider. - pub async fn connect_host_provider(&self) -> Result { + pub async fn connect_host_provider(&self) -> Result { let builder_signer = self.connect_builder_signer().await?; let provider = ProviderBuilder::new() .wallet(EthereumWallet::from(builder_signer)) @@ -239,22 +230,20 @@ impl BuilderConfig { } /// Connect additional broadcast providers. - pub async fn connect_additional_broadcast( - &self, - ) -> Result, ConfigError> { - let mut providers: Vec = - Vec::with_capacity(self.tx_broadcast_urls.len()); - for url in self.tx_broadcast_urls.iter() { - let provider = - ProviderBuilder::new().connect(url).await.map_err(ConfigError::Provider)?; + pub async fn connect_additional_broadcast(&self) -> Result, ConfigError> { + let mut providers: Vec = Vec::with_capacity(self.tx_broadcast_urls.len()); + for url_str in self.tx_broadcast_urls.iter() { + let url = url::Url::parse(url_str).expect("failed to parse URL"); + let provider = RootProvider::new_http(url); providers.push(provider); } + Ok(providers) } /// Connect to the Zenith instance, using the specified provider. - pub const fn connect_zenith(&self, provider: Provider) -> ZenithInstance { + pub const fn connect_zenith(&self, provider: HostProvider) -> ZenithInstance { Zenith::new(self.zenith_address, provider) } diff --git a/src/tasks/block.rs b/src/tasks/block.rs index f085bb9..1dc5013 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -1,10 +1,11 @@ use crate::{ - config::{BuilderConfig, WalletlessProvider}, + config::{BuilderConfig, RuProvider}, tasks::bundler::Bundle, }; use alloy::{ consensus::TxEnvelope, eips::{BlockId, BlockNumberOrTag::Latest}, + network::Ethereum, providers::Provider, }; use signet_sim::{BlockBuild, BuiltBlock, SimCache}; @@ -42,11 +43,13 @@ pub struct Simulator { /// Configuration for the builder. pub config: BuilderConfig, /// A provider that cannot sign transactions, used for interacting with the rollup. - pub ru_provider: WalletlessProvider, + pub ru_provider: RuProvider, /// The slot calculator for determining when to wake up and build blocks. pub slot_calculator: SlotCalculator, } +type AlloyDatabaseProvider = WrapDatabaseAsync>; + impl Simulator { /// Creates a new `Simulator` instance. /// @@ -61,7 +64,7 @@ impl Simulator { /// A new `Simulator` instance. pub fn new( config: &BuilderConfig, - ru_provider: WalletlessProvider, + ru_provider: RuProvider, slot_calculator: SlotCalculator, ) -> Self { Self { config: config.clone(), ru_provider, slot_calculator } @@ -276,7 +279,7 @@ impl Simulator { /// # Returns /// /// An `Option` containing the wrapped database or `None` if an error occurs. - async fn create_db(&self) -> Option { + async fn create_db(&self) -> Option { let latest = match self.ru_provider.get_block_number().await { Ok(block_number) => block_number, Err(e) => { @@ -284,10 +287,12 @@ impl Simulator { return None; } }; - let alloy_db = AlloyDB::new(self.ru_provider.clone(), BlockId::from(latest)); - let wrapped_db = WrapDatabaseAsync::new(alloy_db).unwrap_or_else(|| { - panic!("failed to acquire async alloy_db; check which runtime you're using") - }); + let alloy_db: AlloyDB = + AlloyDB::new(self.ru_provider.clone(), BlockId::from(latest)); + let wrapped_db: WrapDatabaseAsync> = + WrapDatabaseAsync::new(alloy_db).unwrap_or_else(|| { + panic!("failed to acquire async alloy_db; check which runtime you're using") + }); Some(wrapped_db) } } @@ -329,29 +334,6 @@ async fn cache_updater( } } -/// The wrapped alloy database type that is compatible with `Db` and `DatabaseRef`. -type WrapAlloyDatabaseAsync = WrapDatabaseAsync< - AlloyDB< - alloy::network::Ethereum, - alloy::providers::fillers::FillProvider< - alloy::providers::fillers::JoinFill< - alloy::providers::Identity, - alloy::providers::fillers::JoinFill< - alloy::providers::fillers::GasFiller, - alloy::providers::fillers::JoinFill< - alloy::providers::fillers::BlobGasFiller, - alloy::providers::fillers::JoinFill< - alloy::providers::fillers::NonceFiller, - alloy::providers::fillers::ChainIdFiller, - >, - >, - >, - >, - alloy::providers::RootProvider, - >, - >, ->; - /// Configuration struct for Pecorino network values. #[derive(Debug, Clone)] pub struct PecorinoCfg {} diff --git a/src/tasks/metrics.rs b/src/tasks/metrics.rs index 57d0334..6b41196 100644 --- a/src/tasks/metrics.rs +++ b/src/tasks/metrics.rs @@ -1,4 +1,4 @@ -use crate::config::Provider; +use crate::config::HostProvider; use alloy::{primitives::TxHash, providers::Provider as _}; use init4_bin_base::deps::metrics::{counter, histogram}; use std::time::Instant; @@ -9,7 +9,7 @@ use tracing::{debug, error}; #[derive(Debug, Clone)] pub struct MetricsTask { /// Ethereum Provider - pub host_provider: Provider, + pub host_provider: HostProvider, } impl MetricsTask { diff --git a/src/tasks/submit.rs b/src/tasks/submit.rs index df8cd2c..925ebb1 100644 --- a/src/tasks/submit.rs +++ b/src/tasks/submit.rs @@ -1,5 +1,5 @@ use crate::{ - config::{Provider, ZenithInstance}, + config::{HostProvider, ZenithInstance}, signer::LocalOrAws, utils::extract_signature_components, }; @@ -58,7 +58,7 @@ pub enum ControlFlow { #[derive(Debug, Clone)] pub struct SubmitTask { /// Ethereum Provider - pub host_provider: Provider, + pub host_provider: HostProvider, /// Zenith pub zenith: ZenithInstance, /// Reqwest diff --git a/tests/block_builder_test.rs b/tests/block_builder_test.rs index d109e9f..0c4c9af 100644 --- a/tests/block_builder_test.rs +++ b/tests/block_builder_test.rs @@ -2,7 +2,7 @@ #[cfg(test)] mod tests { use alloy::{ - node_bindings::Anvil, primitives::U256, providers::ProviderBuilder, + network::Ethereum, node_bindings::Anvil, primitives::U256, providers::RootProvider, signers::local::PrivateKeySigner, }; use builder::{ @@ -41,7 +41,7 @@ mod tests { let test_key_1 = PrivateKeySigner::from_signing_key(keys[1].clone().into()); // Create a rollup provider - let ru_provider = ProviderBuilder::new().on_http(anvil_instance.endpoint_url()); + let ru_provider = RootProvider::::new_http(anvil_instance.endpoint_url()); // Create a block builder with a slot calculator for testing let now = SystemTime::now() @@ -100,7 +100,7 @@ mod tests { let (block_sender, mut block_receiver) = unbounded_channel(); // Create a rollup provider - let ru_provider = ProviderBuilder::new().on_http(anvil_instance.endpoint_url()); + let ru_provider = RootProvider::::new_http(anvil_instance.endpoint_url()); // Create a builder with a test slot calculator let slot_calculator = SlotCalculator::new( From 8980e2330326ef2fa16ee8c22a6af9b4a7ee7552 Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 28 Apr 2025 16:19:03 -0600 Subject: [PATCH 42/53] creates mod consts --- src/consts.rs | 4 ++++ src/lib.rs | 3 +++ src/tasks/block.rs | 4 +--- src/test_utils.rs | 2 +- tests/block_builder_test.rs | 3 ++- 5 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 src/consts.rs diff --git a/src/consts.rs b/src/consts.rs new file mode 100644 index 0000000..c0656d0 --- /dev/null +++ b/src/consts.rs @@ -0,0 +1,4 @@ +//! Constants used in the builder. + +/// Pecorino Chain ID used for the Pecorino network. +pub const PECORINO_CHAIN_ID: u64 = 14174; diff --git a/src/lib.rs b/src/lib.rs index bb39edf..12f5d2d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,9 @@ #![deny(unused_must_use, rust_2018_idioms)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +/// Constants for the Builder. +pub mod consts; + /// Configuration for the Builder binary. pub mod config; diff --git a/src/tasks/block.rs b/src/tasks/block.rs index 1dc5013..1e12463 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -1,5 +1,6 @@ use crate::{ config::{BuilderConfig, RuProvider}, + consts::PECORINO_CHAIN_ID, tasks::bundler::Bundle, }; use alloy::{ @@ -33,9 +34,6 @@ use trevm::{ }, }; -/// Pecorino Chain ID used for the Pecorino network. -pub const PECORINO_CHAIN_ID: u64 = 14174; - /// `Simulator` is responsible for periodically building blocks and submitting them for /// signing and inclusion in the blockchain. #[derive(Debug)] diff --git a/src/test_utils.rs b/src/test_utils.rs index 20c5fa9..5cefbfa 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -1,5 +1,5 @@ //! Test utilities for testing builder tasks -use crate::{config::BuilderConfig, tasks::block::PECORINO_CHAIN_ID}; +use crate::{config::BuilderConfig, consts::PECORINO_CHAIN_ID}; use alloy::{ consensus::{SignableTransaction, TxEip1559, TxEnvelope}, primitives::{Address, TxKind, U256}, diff --git a/tests/block_builder_test.rs b/tests/block_builder_test.rs index 0c4c9af..29fdda9 100644 --- a/tests/block_builder_test.rs +++ b/tests/block_builder_test.rs @@ -6,7 +6,8 @@ mod tests { signers::local::PrivateKeySigner, }; use builder::{ - tasks::block::{PECORINO_CHAIN_ID, Simulator}, + consts::PECORINO_CHAIN_ID, + tasks::block::Simulator, test_utils::{new_signed_tx, setup_logging, setup_test_config}, }; From 4e97d89873325b4c6426f1741bc444c8b1498d1d Mon Sep 17 00:00:00 2001 From: dylan Date: Tue, 29 Apr 2025 11:05:16 -0600 Subject: [PATCH 43/53] pulls chain configs out into constants module --- src/config.rs | 31 +++++++++++++------------ src/constants.rs | 45 +++++++++++++++++++++++++++++++++++++ src/consts.rs | 4 ---- src/lib.rs | 2 +- src/tasks/block.rs | 2 +- src/test_utils.rs | 2 +- tests/block_builder_test.rs | 2 +- 7 files changed, 66 insertions(+), 22 deletions(-) create mode 100644 src/constants.rs delete mode 100644 src/consts.rs diff --git a/src/config.rs b/src/config.rs index c15f72e..fe4ac00 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,7 @@ -use crate::signer::{LocalOrAws, SignerError}; +use crate::{ + constants::{self, HOST_WBTC}, + signer::{LocalOrAws, SignerError}, +}; use alloy::{ network::{Ethereum, EthereumWallet}, primitives::{Address, address}, @@ -251,26 +254,26 @@ impl BuilderConfig { pub const fn load_pecorino_constants(&self) -> SignetSystemConstants { let host = HostConfig::new( self.host_chain_id, - 149984, + constants::PECORINO_DEPLOY_HEIGHT, self.zenith_address, - address!("0x4E8cC181805aFC307C83298242271142b8e2f249"), - address!("0xd553C4CA4792Af71F4B61231409eaB321c1Dd2Ce"), - address!("0x1af3A16857C28917Ab2C4c78Be099fF251669200"), + constants::HOST_ORDERS, + constants::HOST_PASSAGE, + constants::HOST_TRANSACTOR, PredeployTokens::new( - address!("0x885F8DB528dC8a38aA3DDad9D3F619746B4a6A81"), - address!("0x7970D259D4a96764Fa9B23FF0715A35f06f52D1A"), - address!("0x7970D259D4a96764Fa9B23FF0715A35f06f52D1A"), + constants::HOST_USDC, + constants::HOST_USDT, + constants::HOST_WBTC, ), ); let rollup = RollupConfig::new( self.ru_chain_id, - address!("0x4E8cC181805aFC307C83298242271142b8e2f249"), - address!("0xd553C4CA4792Af71F4B61231409eaB321c1Dd2Ce"), - address!("0xe0eDA3701D44511ce419344A4CeD30B52c9Ba231"), + constants::ROLLUP_ORDERS, + constants::ROLLUP_PASSAGE, + constants::BASE_FEE_RECIPIENT, PredeployTokens::new( - address!("0x0B8BC5e60EE10957E0d1A0d95598fA63E65605e2"), - address!("0xF34326d3521F1b07d1aa63729cB14A372f8A737C"), - address!("0xE3d7066115f7d6b65F88Dff86288dB4756a7D733"), + constants::ROLLUP_USDC, + constants::ROLLUP_USDT, + constants::ROLLUP_WBTC, ), ); diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..4c56048 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,45 @@ +//! Constants used in the builder. + +use alloy::primitives::{Address, address}; + +/// Pecorino Chain ID used for the Pecorino network. +pub const PECORINO_CHAIN_ID: u64 = 14174; + +/// Block number at which the Pecorino rollup contract is deployed. +pub const PECORINO_DEPLOY_HEIGHT: u64 = 149984; + +/// Address of the orders contract on the host. +pub const HOST_ORDERS: Address = address!("0x4E8cC181805aFC307C83298242271142b8e2f249"); + +/// Address of the passage contract on the host. +pub const HOST_PASSAGE: Address = address!("0xd553C4CA4792Af71F4B61231409eaB321c1Dd2Ce"); + +/// Address of the transactor contract on the host. +pub const HOST_TRANSACTOR: Address = address!("0x1af3A16857C28917Ab2C4c78Be099fF251669200"); + +/// Address of the USDC token contract on the host. +pub const HOST_USDC: Address = address!("0x885F8DB528dC8a38aA3DDad9D3F619746B4a6A81"); + +/// Address of the USDT token contract on the host. +pub const HOST_USDT: Address = address!("0x7970D259D4a96764Fa9B23FF0715A35f06f52D1A"); + +/// Address of the WBTC token contract on the host. +pub const HOST_WBTC: Address = address!("0x7970D259D4a96764Fa9B23FF0715A35f06f52D1A"); + +/// Address of the orders contract on the rollup. +pub const ROLLUP_ORDERS: Address = address!("0x4E8cC181805aFC307C83298242271142b8e2f249"); + +/// Address of the passage contract on the rollup. +pub const ROLLUP_PASSAGE: Address = address!("0xd553C4CA4792Af71F4B61231409eaB321c1Dd2Ce"); + +/// Base fee recipient address. +pub const BASE_FEE_RECIPIENT: Address = address!("0xe0eDA3701D44511ce419344A4CeD30B52c9Ba231"); + +/// Address of the USDC token contract on the rollup. +pub const ROLLUP_USDC: Address = address!("0x0B8BC5e60EE10957E0d1A0d95598fA63E65605e2"); + +/// Address of the USDT token contract on the rollup. +pub const ROLLUP_USDT: Address = address!("0xF34326d3521F1b07d1aa63729cB14A372f8A737C"); + +/// Address of the WBTC token contract on the rollup. +pub const ROLLUP_WBTC: Address = address!("0xE3d7066115f7d6b65F88Dff86288dB4756a7D733"); \ No newline at end of file diff --git a/src/consts.rs b/src/consts.rs deleted file mode 100644 index c0656d0..0000000 --- a/src/consts.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! Constants used in the builder. - -/// Pecorino Chain ID used for the Pecorino network. -pub const PECORINO_CHAIN_ID: u64 = 14174; diff --git a/src/lib.rs b/src/lib.rs index 12f5d2d..1f64552 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] /// Constants for the Builder. -pub mod consts; +pub mod constants; /// Configuration for the Builder binary. pub mod config; diff --git a/src/tasks/block.rs b/src/tasks/block.rs index 1e12463..fdde0f6 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -1,6 +1,6 @@ use crate::{ config::{BuilderConfig, RuProvider}, - consts::PECORINO_CHAIN_ID, + constants::PECORINO_CHAIN_ID, tasks::bundler::Bundle, }; use alloy::{ diff --git a/src/test_utils.rs b/src/test_utils.rs index 5cefbfa..77f7169 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -1,5 +1,5 @@ //! Test utilities for testing builder tasks -use crate::{config::BuilderConfig, consts::PECORINO_CHAIN_ID}; +use crate::{config::BuilderConfig, constants::PECORINO_CHAIN_ID}; use alloy::{ consensus::{SignableTransaction, TxEip1559, TxEnvelope}, primitives::{Address, TxKind, U256}, diff --git a/tests/block_builder_test.rs b/tests/block_builder_test.rs index 29fdda9..c04f977 100644 --- a/tests/block_builder_test.rs +++ b/tests/block_builder_test.rs @@ -6,7 +6,7 @@ mod tests { signers::local::PrivateKeySigner, }; use builder::{ - consts::PECORINO_CHAIN_ID, + constants::PECORINO_CHAIN_ID, tasks::block::Simulator, test_utils::{new_signed_tx, setup_logging, setup_test_config}, }; From 31d62d65ee0c33c950303357b9644f7c3bf61f68 Mon Sep 17 00:00:00 2001 From: dylan Date: Tue, 29 Apr 2025 11:18:09 -0600 Subject: [PATCH 44/53] clippy + fmt --- src/config.rs | 10 +++------- src/constants.rs | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/config.rs b/src/config.rs index fe4ac00..d24351a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,10 +1,10 @@ use crate::{ - constants::{self, HOST_WBTC}, + constants, signer::{LocalOrAws, SignerError}, }; use alloy::{ network::{Ethereum, EthereumWallet}, - primitives::{Address, address}, + primitives::Address, providers::{ Identity, ProviderBuilder, RootProvider, fillers::{ @@ -259,11 +259,7 @@ impl BuilderConfig { constants::HOST_ORDERS, constants::HOST_PASSAGE, constants::HOST_TRANSACTOR, - PredeployTokens::new( - constants::HOST_USDC, - constants::HOST_USDT, - constants::HOST_WBTC, - ), + PredeployTokens::new(constants::HOST_USDC, constants::HOST_USDT, constants::HOST_WBTC), ); let rollup = RollupConfig::new( self.ru_chain_id, diff --git a/src/constants.rs b/src/constants.rs index 4c56048..68fecc8 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -42,4 +42,4 @@ pub const ROLLUP_USDC: Address = address!("0x0B8BC5e60EE10957E0d1A0d95598fA63E65 pub const ROLLUP_USDT: Address = address!("0xF34326d3521F1b07d1aa63729cB14A372f8A737C"); /// Address of the WBTC token contract on the rollup. -pub const ROLLUP_WBTC: Address = address!("0xE3d7066115f7d6b65F88Dff86288dB4756a7D733"); \ No newline at end of file +pub const ROLLUP_WBTC: Address = address!("0xE3d7066115f7d6b65F88Dff86288dB4756a7D733"); From a0e11a4f6420a5b1dc270f8715042617268a505b Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 5 May 2025 13:43:14 -0600 Subject: [PATCH 45/53] address review feedback --- Cargo.toml | 1 + bin/builder.rs | 2 +- src/config.rs | 4 +- src/constants.rs | 3 + src/tasks/block.rs | 208 ++++++++++++++++++++++++++++++++---- src/test_utils.rs | 34 +++++- tests/block_builder_test.rs | 17 +-- 7 files changed, 238 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 640528b..6c05fd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,3 +62,4 @@ tokio = { version = "1.36.0", features = ["full", "macros", "rt-multi-thread"] } async-trait = "0.1.80" oauth2 = "4.4.2" tracing-subscriber = "0.3.19" +chrono = "0.4.41" diff --git a/bin/builder.rs b/bin/builder.rs index 5f7dcfe..ed74d82 100644 --- a/bin/builder.rs +++ b/bin/builder.rs @@ -59,7 +59,7 @@ async fn main() -> eyre::Result<()> { let sim = Arc::new(Simulator::new(&config, ru_provider.clone(), slot_calculator)); let (basefee_jh, sim_cache_jh) = - sim.clone().spawn_cache_task(tx_receiver, bundle_receiver, sim_items.clone()); + sim.clone().spawn_cache_tasks(tx_receiver, bundle_receiver, sim_items.clone()); let build_jh = sim.clone().spawn_simulator_task(constants, sim_items.clone(), submit_channel); diff --git a/src/config.rs b/src/config.rs index d24351a..b442212 100644 --- a/src/config.rs +++ b/src/config.rs @@ -127,8 +127,8 @@ pub enum ConfigError { /// Error connecting to the signer #[error("failed to connect to signer: {0}")] Signer(#[from] SignerError), - /// Error checking available system concurrency - #[error("failed to determine system concurrency: {0}")] + /// I/O error + #[error("I/O error: {0}")] Io(#[from] std::io::Error), } diff --git a/src/constants.rs b/src/constants.rs index 68fecc8..4917d80 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -2,6 +2,9 @@ use alloy::primitives::{Address, address}; +/// The default basefee to use for simulation if RPC fails. +pub const BASEFEE_DEFAULT: u64 = 7; + /// Pecorino Chain ID used for the Pecorino network. pub const PECORINO_CHAIN_ID: u64 = 14174; diff --git a/src/tasks/block.rs b/src/tasks/block.rs index fdde0f6..2f1641f 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -1,14 +1,25 @@ +//! `block.rs` contains the Simulator and everything that wires it into an +//! actor that handles the simulation of a stream of bundles and transactions +//! and turns them into valid Pecorino blocks for network submission. +//! +//! # Architecture +//! +//! +//! use crate::{ config::{BuilderConfig, RuProvider}, - constants::PECORINO_CHAIN_ID, + constants::{BASEFEE_DEFAULT, PECORINO_CHAIN_ID}, tasks::bundler::Bundle, }; use alloy::{ consensus::TxEnvelope, eips::{BlockId, BlockNumberOrTag::Latest}, network::Ethereum, + primitives::{Address, B256, FixedBytes, U256}, providers::Provider, }; +use chrono::{DateTime, Utc}; +use eyre::Report; use signet_sim::{BlockBuild, BuiltBlock, SimCache}; use signet_types::{SlotCalculator, config::SignetSystemConstants}; use std::{ @@ -18,6 +29,7 @@ use std::{ }, time::{Duration, Instant, SystemTime, UNIX_EPOCH}, }; +use thiserror::Error; use tokio::{ select, sync::mpsc::{self}, @@ -25,17 +37,27 @@ use tokio::{ time::sleep, }; use trevm::{ - NoopBlock, + Block, revm::{ - context::CfgEnv, + context::{BlockEnv, CfgEnv}, + context_interface::block::BlobExcessGasAndPrice, database::{AlloyDB, WrapDatabaseAsync}, inspector::NoOpInspector, - primitives::hardfork::SpecId, + primitives::hardfork::SpecId::{self}, }, }; +/// Different error types that the Simulator handles +#[derive(Debug, Error)] +pub enum SimulatorError { + /// Wraps errors encountered when interacting with the RPC + #[error("RPC error: {0}")] + Rpc(#[source] Report), +} + /// `Simulator` is responsible for periodically building blocks and submitting them for -/// signing and inclusion in the blockchain. +/// signing and inclusion in the blockchain. It wraps a rollup provider and a slot +/// calculator with a builder configuration. #[derive(Debug)] pub struct Simulator { /// Configuration for the builder. @@ -84,15 +106,15 @@ impl Simulator { constants: SignetSystemConstants, sim_items: SimCache, finish_by: Instant, - ) -> Result> { - tracing::debug!(finish_by = ?finish_by, "starting block simulation"); - + block: PecorinoBlockEnv, + ) -> Result { let db = self.create_db().await.unwrap(); + let block_build: BlockBuild<_, NoOpInspector> = BlockBuild::new( db, constants, PecorinoCfg {}, - NoopBlock, + block, finish_by, self.config.concurrency_limit, sim_items, @@ -118,7 +140,7 @@ impl Simulator { /// /// A `JoinHandle` for the basefee updater and a `JoinHandle` for the /// cache handler. - pub fn spawn_cache_task( + pub fn spawn_cache_tasks( self: Arc, tx_receiver: mpsc::UnboundedReceiver, bundle_receiver: mpsc::UnboundedReceiver, @@ -153,6 +175,7 @@ impl Simulator { /// /// - `price`: A shared `Arc` used to store the updated basefee value. async fn basefee_updater(self: Arc, price: Arc) { + tracing::debug!("starting basefee updater"); loop { // calculate start of next slot plus a small buffer let time_remaining = self.slot_calculator.slot_duration() @@ -180,7 +203,6 @@ impl Simulator { /// /// - `price`: A shared `Arc` used to store the updated basefee. async fn check_basefee(&self, price: &Arc) { - tracing::debug!("checking latest basefee"); let resp = self.ru_provider.get_block_by_number(Latest).await; if let Err(e) = resp { tracing::debug!(err = %e, "basefee check failed with rpc error"); @@ -191,6 +213,7 @@ impl Simulator { match maybe_block { Some(block) => { let basefee = block.header.base_fee_per_gas.unwrap_or_default(); + println!("BASEFEE: {:?}", basefee); price.store(basefee, Ordering::Relaxed); tracing::debug!(basefee = %basefee, "basefee updated"); } @@ -234,6 +257,7 @@ impl Simulator { /// This function runs indefinitely and never returns. /// /// # Arguments + /// /// - `constants`: The system constants for the rollup. /// - `cache`: The simulation cache containing transactions and bundles. /// - `submit_sender`: A channel sender used to submit built blocks. @@ -247,7 +271,16 @@ impl Simulator { let sim_cache = cache.clone(); let finish_by = self.calculate_deadline(); - match self.handle_build(constants, sim_cache, finish_by).await { + let block_env = match self.next_block_env(finish_by).await { + Ok(block) => block, + Err(err) => { + tracing::error!(err = %err, "failed to configure next block"); + break; + } + }; + tracing::info!(block_env = ?block_env, "created block"); + + match self.handle_build(constants, sim_cache, finish_by, block_env).await { Ok(block) => { tracing::debug!(block = ?block, "built block"); let _ = submit_sender.send(block); @@ -264,12 +297,16 @@ impl Simulator { /// /// # Returns /// - /// An `Instant` representing the deadline. + /// An `Instant` representing the deadline, as calculated by determining the time left in + /// the current slot and adding that to the current timestamp in UNIX seconds. pub fn calculate_deadline(&self) -> Instant { + // Calculate the current timestamp in seconds since the UNIX epoch let now = SystemTime::now(); let unix_seconds = now.duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs(); - Instant::now().checked_add(Duration::from_secs(unix_seconds)).unwrap() + // Deadline is equal to the start of the next slot plus the time remaining in this slot + let remaining = self.slot_calculator.calculate_timepoint_within_slot(unix_seconds); + Instant::now() + Duration::from_secs(remaining) } /// Creates an `AlloyDB` instance from the rollup provider. @@ -293,6 +330,72 @@ impl Simulator { }); Some(wrapped_db) } + + /// Prepares the next block environment. + /// + /// Prepares the next block environment to load into the simulator by fetching the latest block number, + /// assigning the correct next block number, checking the basefee, and setting the timestamp, + /// reward address, and gas configuration for the block environment based on builder configuration. + /// + /// # Arguments + /// + /// - finish_by: The deadline at which block simulation will end. + async fn next_block_env(&self, finish_by: Instant) -> Result { + let remaining = finish_by.duration_since(Instant::now()); + let finish_time = SystemTime::now() + remaining; + let deadline: DateTime = finish_time.into(); + tracing::debug!(deadline = %deadline, "preparing block env"); + + // Fetch the latest block number and increment it by 1 + let latest_block_number = match self.ru_provider.get_block_number().await { + Ok(num) => num, + Err(err) => { + tracing::error!(error = %err, "RPC error during block build"); + return Err(SimulatorError::Rpc(Report::new(err))); + } + }; + tracing::debug!(next_block_num = latest_block_number + 1, "preparing block env"); + + // Fetch the basefee from previous block to calculate gas for this block + let basefee = match self.get_basefee().await? { + Some(basefee) => basefee, + None => { + tracing::warn!("get basefee failed - RPC error likely occurred"); + BASEFEE_DEFAULT + } + }; + tracing::debug!(basefee = basefee, "setting basefee"); + + // Craft the Block environment to pass to the simulator + let block_env = PecorinoBlockEnv::new( + self.config.clone(), + latest_block_number + 1, + deadline.timestamp() as u64, + basefee, + ); + tracing::debug!(block_env = ?block_env, "prepared block env"); + + Ok(block_env) + } + + /// Returns the basefee of the latest block. + /// + /// # Returns + /// + /// The basefee of the previous (latest) block if the request was successful, + /// or a sane default if the RPC failed. + async fn get_basefee(&self) -> Result, SimulatorError> { + match self.ru_provider.get_block_by_number(Latest).await { + Ok(maybe_block) => match maybe_block { + Some(block) => { + tracing::debug!(basefee = ?block.header.base_fee_per_gas, "basefee found"); + Ok(block.header.base_fee_per_gas) + } + None => Ok(None), + }, + Err(err) => Err(SimulatorError::Rpc(err.into())), + } + } } /// Continuously updates the simulation cache with incoming transactions and bundles. @@ -301,6 +404,7 @@ impl Simulator { /// channels and adds them to the simulation cache using the latest observed basefee. /// /// # Arguments +/// /// - `tx_receiver`: A receiver channel for incoming Ethereum transactions. /// - `bundle_receiver`: A receiver channel for incoming transaction bundles. /// - `cache`: The simulation cache used to store transactions and bundles. @@ -332,16 +436,15 @@ async fn cache_updater( } } -/// Configuration struct for Pecorino network values. -#[derive(Debug, Clone)] +/// PecorinoCfg holds network-level configuration values. +#[derive(Debug, Clone, Copy)] pub struct PecorinoCfg {} -impl Copy for PecorinoCfg {} - impl trevm::Cfg for PecorinoCfg { /// Fills the configuration environment with Pecorino-specific values. /// /// # Arguments + /// /// - `cfg_env`: The configuration environment to be filled. fn fill_cfg_env(&self, cfg_env: &mut CfgEnv) { let CfgEnv { chain_id, spec, .. } = cfg_env; @@ -350,3 +453,72 @@ impl trevm::Cfg for PecorinoCfg { *spec = SpecId::default(); } } + +/// PecorinoBlockEnv holds block-level configurations for Pecorino blocks. +#[derive(Debug, Clone, Copy)] +pub struct PecorinoBlockEnv { + /// The block number for this block. + pub number: u64, + /// The address the block reward should be sent to. + pub beneficiary: Address, + /// Timestamp for the block. + pub timestamp: u64, + /// The gas limit for this block environment. + pub gas_limit: u64, + /// The basefee to use for calculating gas usage. + pub basefee: u64, + /// The prevrandao to use for this block. + pub prevrandao: Option>, +} + +/// Implements [`trevm::Block`] for the Pecorino block. +impl Block for PecorinoBlockEnv { + /// Fills the block environment with the Pecorino specific values + fn fill_block_env(&self, block_env: &mut trevm::revm::context::BlockEnv) { + // Destructure the fields off of the block_env and modify them + let BlockEnv { + number, + beneficiary, + timestamp, + gas_limit, + basefee, + difficulty, + prevrandao, + blob_excess_gas_and_price, + } = block_env; + *number = self.number; + *beneficiary = self.beneficiary; + *timestamp = self.timestamp; + *gas_limit = self.gas_limit; + *basefee = self.basefee; + *prevrandao = self.prevrandao; + + // NB: The following fields are set to sane defaults because they + // are not supported by the rollup + *difficulty = U256::ZERO; + *blob_excess_gas_and_price = + Some(BlobExcessGasAndPrice { excess_blob_gas: 0, blob_gasprice: 0 }); + } +} + +impl PecorinoBlockEnv { + /// Returns a new PecorinoBlockEnv with the specified values. + /// + /// # Arguments + /// + /// - config: The BuilderConfig for the builder. + /// - number: The block number of this block, usually the latest block number plus 1, + /// unless simulating blocks in the past. + /// - timestamp: The timestamp of the block, typically set to the deadline of the + /// block building task. + fn new(config: BuilderConfig, number: u64, timestamp: u64, basefee: u64) -> Self { + PecorinoBlockEnv { + number, + beneficiary: config.builder_rewards_address, + timestamp, + gas_limit: config.rollup_block_gas_limit, + basefee, + prevrandao: Some(B256::random()), + } + } +} diff --git a/src/test_utils.rs b/src/test_utils.rs index 77f7169..6ff6fae 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -1,12 +1,16 @@ //! Test utilities for testing builder tasks -use crate::{config::BuilderConfig, constants::PECORINO_CHAIN_ID}; +use crate::{config::BuilderConfig, constants::PECORINO_CHAIN_ID, tasks::block::PecorinoBlockEnv}; use alloy::{ consensus::{SignableTransaction, TxEip1559, TxEnvelope}, - primitives::{Address, TxKind, U256}, + primitives::{Address, FixedBytes, TxKind, U256}, signers::{SignerSync, local::PrivateKeySigner}, }; +use chrono::{DateTime, Utc}; use eyre::Result; -use std::str::FromStr; +use std::{ + str::FromStr, + time::{Instant, SystemTime}, +}; use tracing_subscriber::{EnvFilter, Layer, layer::SubscriberExt, util::SubscriberInitExt}; /// Sets up a block builder with test values @@ -26,7 +30,7 @@ pub fn setup_test_config() -> Result { chain_offset: 0, target_slot_time: 1, builder_rewards_address: Address::default(), - rollup_block_gas_limit: 100_000, + rollup_block_gas_limit: 3_000_000_000, tx_pool_url: "http://localhost:9000/".into(), tx_pool_cache_duration: 5, oauth_client_id: "some_client_id".into(), @@ -70,3 +74,25 @@ pub fn setup_logging() { let registry = tracing_subscriber::registry().with(fmt); let _ = registry.try_init(); } + +/// Returns a Pecorino block environment for simulation with the timestamp set to `finish_by`, +/// the block number set to latest + 1, system gas configs, and a beneficiary address. +pub fn test_block_env( + config: BuilderConfig, + number: u64, + basefee: u64, + finish_by: Instant, +) -> PecorinoBlockEnv { + let remaining = finish_by.duration_since(Instant::now()); + let finish_time = SystemTime::now() + remaining; + let deadline: DateTime = finish_time.into(); + + PecorinoBlockEnv { + number, + beneficiary: Address::repeat_byte(0), + timestamp: deadline.timestamp() as u64, + gas_limit: config.rollup_block_gas_limit, + basefee, + prevrandao: Some(FixedBytes::random()), + } +} diff --git a/tests/block_builder_test.rs b/tests/block_builder_test.rs index c04f977..f1126f9 100644 --- a/tests/block_builder_test.rs +++ b/tests/block_builder_test.rs @@ -2,15 +2,17 @@ #[cfg(test)] mod tests { use alloy::{ - network::Ethereum, node_bindings::Anvil, primitives::U256, providers::RootProvider, + network::Ethereum, + node_bindings::Anvil, + primitives::U256, + providers::{Provider, RootProvider}, signers::local::PrivateKeySigner, }; use builder::{ constants::PECORINO_CHAIN_ID, tasks::block::Simulator, - test_utils::{new_signed_tx, setup_logging, setup_test_config}, + test_utils::{new_signed_tx, setup_logging, setup_test_config, test_block_env}, }; - use signet_sim::{SimCache, SimItem}; use signet_types::SlotCalculator; use std::{ @@ -49,7 +51,7 @@ mod tests { .duration_since(UNIX_EPOCH) .expect("Clock may have gone backwards") .as_secs(); - dbg!(now); + let slot_calculator = SlotCalculator::new(now, 0, 12); let block_builder = Simulator::new(&config, ru_provider.clone(), slot_calculator); @@ -63,10 +65,13 @@ mod tests { let tx_2 = new_signed_tx(&test_key_1, 0, U256::from(2_f64), 10_000).unwrap(); sim_items.add_item(SimItem::Tx(tx_2), 0); + // Setup the block env let finish_by = Instant::now() + Duration::from_secs(2); + let block_number = ru_provider.get_block_number().await.unwrap(); + let block_env = test_block_env(config, block_number, 7, finish_by); // Spawn the block builder task - let got = block_builder.handle_build(constants, sim_items, finish_by).await; + let got = block_builder.handle_build(constants, sim_items, finish_by, block_env).await; // Assert on the built block assert!(got.is_ok()); @@ -115,7 +120,7 @@ mod tests { let sim_cache = SimCache::new(); // Create a sim cache and start filling it with items - sim.clone().spawn_cache_task(tx_receiver, bundle_receiver, sim_cache.clone()); + sim.clone().spawn_cache_tasks(tx_receiver, bundle_receiver, sim_cache.clone()); // Finally, Kick off the block builder task. sim.clone().spawn_simulator_task(constants, sim_cache.clone(), block_sender); From 2d6495e38af889c3b270b7a3ec62a56c3e475caa Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 5 May 2025 14:20:48 -0600 Subject: [PATCH 46/53] updates test config values with chain id constants --- src/constants.rs | 16 ++-------------- src/test_utils.rs | 10 +++++----- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 4917d80..9a55d30 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -4,45 +4,33 @@ use alloy::primitives::{Address, address}; /// The default basefee to use for simulation if RPC fails. pub const BASEFEE_DEFAULT: u64 = 7; - +/// Pecorino Host Chain ID used for the Pecorino network. +pub const PECORINO_HOST_CHAIN_ID: u64 = 3151908; /// Pecorino Chain ID used for the Pecorino network. pub const PECORINO_CHAIN_ID: u64 = 14174; - /// Block number at which the Pecorino rollup contract is deployed. pub const PECORINO_DEPLOY_HEIGHT: u64 = 149984; - /// Address of the orders contract on the host. pub const HOST_ORDERS: Address = address!("0x4E8cC181805aFC307C83298242271142b8e2f249"); - /// Address of the passage contract on the host. pub const HOST_PASSAGE: Address = address!("0xd553C4CA4792Af71F4B61231409eaB321c1Dd2Ce"); - /// Address of the transactor contract on the host. pub const HOST_TRANSACTOR: Address = address!("0x1af3A16857C28917Ab2C4c78Be099fF251669200"); - /// Address of the USDC token contract on the host. pub const HOST_USDC: Address = address!("0x885F8DB528dC8a38aA3DDad9D3F619746B4a6A81"); - /// Address of the USDT token contract on the host. pub const HOST_USDT: Address = address!("0x7970D259D4a96764Fa9B23FF0715A35f06f52D1A"); - /// Address of the WBTC token contract on the host. pub const HOST_WBTC: Address = address!("0x7970D259D4a96764Fa9B23FF0715A35f06f52D1A"); - /// Address of the orders contract on the rollup. pub const ROLLUP_ORDERS: Address = address!("0x4E8cC181805aFC307C83298242271142b8e2f249"); - /// Address of the passage contract on the rollup. pub const ROLLUP_PASSAGE: Address = address!("0xd553C4CA4792Af71F4B61231409eaB321c1Dd2Ce"); - /// Base fee recipient address. pub const BASE_FEE_RECIPIENT: Address = address!("0xe0eDA3701D44511ce419344A4CeD30B52c9Ba231"); - /// Address of the USDC token contract on the rollup. pub const ROLLUP_USDC: Address = address!("0x0B8BC5e60EE10957E0d1A0d95598fA63E65605e2"); - /// Address of the USDT token contract on the rollup. pub const ROLLUP_USDT: Address = address!("0xF34326d3521F1b07d1aa63729cB14A372f8A737C"); - /// Address of the WBTC token contract on the rollup. pub const ROLLUP_WBTC: Address = address!("0xE3d7066115f7d6b65F88Dff86288dB4756a7D733"); diff --git a/src/test_utils.rs b/src/test_utils.rs index 6ff6fae..550ba8a 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -1,5 +1,5 @@ //! Test utilities for testing builder tasks -use crate::{config::BuilderConfig, constants::PECORINO_CHAIN_ID, tasks::block::PecorinoBlockEnv}; +use crate::{config::BuilderConfig, constants::{PECORINO_CHAIN_ID, PECORINO_HOST_CHAIN_ID}, tasks::block::PecorinoBlockEnv}; use alloy::{ consensus::{SignableTransaction, TxEip1559, TxEnvelope}, primitives::{Address, FixedBytes, TxKind, U256}, @@ -16,10 +16,10 @@ use tracing_subscriber::{EnvFilter, Layer, layer::SubscriberExt, util::Subscribe /// Sets up a block builder with test values pub fn setup_test_config() -> Result { let config = BuilderConfig { - host_chain_id: 17000, - ru_chain_id: 17001, - host_rpc_url: "host-rpc.example.com".into(), - ru_rpc_url: "ru-rpc.example.com".into(), + host_chain_id: PECORINO_HOST_CHAIN_ID, + ru_chain_id: PECORINO_CHAIN_ID, + host_rpc_url: "https://host-rpc.pecorino.signet.sh".into(), + ru_rpc_url: "https://rpc.pecorino.signet.sh".into(), tx_broadcast_urls: vec!["http://localhost:9000".into()], zenith_address: Address::default(), quincey_url: "http://localhost:8080".into(), From 4de13224bc7a033c0caaaaa1a3e74b084f7a3867 Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 5 May 2025 14:20:55 -0600 Subject: [PATCH 47/53] fmt --- src/test_utils.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/test_utils.rs b/src/test_utils.rs index 550ba8a..2e6fc8d 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -1,5 +1,9 @@ //! Test utilities for testing builder tasks -use crate::{config::BuilderConfig, constants::{PECORINO_CHAIN_ID, PECORINO_HOST_CHAIN_ID}, tasks::block::PecorinoBlockEnv}; +use crate::{ + config::BuilderConfig, + constants::{PECORINO_CHAIN_ID, PECORINO_HOST_CHAIN_ID}, + tasks::block::PecorinoBlockEnv, +}; use alloy::{ consensus::{SignableTransaction, TxEip1559, TxEnvelope}, primitives::{Address, FixedBytes, TxKind, U256}, From 9a87b88e17f6d45022a15e4949533ffb06b08e3b Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 5 May 2025 14:42:34 -0600 Subject: [PATCH 48/53] refactors check basefee func --- src/tasks/block.rs | 41 ++++++++++++++--------------------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/src/tasks/block.rs b/src/tasks/block.rs index 2f1641f..f626dbc 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -1,11 +1,6 @@ //! `block.rs` contains the Simulator and everything that wires it into an //! actor that handles the simulation of a stream of bundles and transactions //! and turns them into valid Pecorino blocks for network submission. -//! -//! # Architecture -//! -//! -//! use crate::{ config::{BuilderConfig, RuProvider}, constants::{BASEFEE_DEFAULT, PECORINO_CHAIN_ID}, @@ -203,24 +198,16 @@ impl Simulator { /// /// - `price`: A shared `Arc` used to store the updated basefee. async fn check_basefee(&self, price: &Arc) { - let resp = self.ru_provider.get_block_by_number(Latest).await; - if let Err(e) = resp { - tracing::debug!(err = %e, "basefee check failed with rpc error"); - return; - } + let resp = self.ru_provider.get_block_by_number(Latest).await.inspect_err(|e| { + tracing::error!(error = %e, "RPC error during basefee update"); + }); - if let Ok(maybe_block) = resp { - match maybe_block { - Some(block) => { - let basefee = block.header.base_fee_per_gas.unwrap_or_default(); - println!("BASEFEE: {:?}", basefee); - price.store(basefee, Ordering::Relaxed); - tracing::debug!(basefee = %basefee, "basefee updated"); - } - None => { - tracing::debug!("no block found; basefee not updated.") - } - } + if let Ok(Some(block)) = resp { + let basefee = block.header.base_fee_per_gas.unwrap_or(0); + price.store(basefee, Ordering::Relaxed); + tracing::debug!(basefee = basefee, "basefee updated"); + } else { + tracing::warn!("get basefee failed - an error likely occurred"); } } @@ -286,7 +273,7 @@ impl Simulator { let _ = submit_sender.send(block); } Err(e) => { - tracing::error!(err = %e, "failed to send block"); + tracing::error!(err = %e, "failed to build block"); continue; } } @@ -297,15 +284,15 @@ impl Simulator { /// /// # Returns /// - /// An `Instant` representing the deadline, as calculated by determining the time left in - /// the current slot and adding that to the current timestamp in UNIX seconds. + /// An `Instant` representing the simulation deadline, as calculated by determining + /// the time left in the current slot and adding that to the current timestamp in UNIX seconds. pub fn calculate_deadline(&self) -> Instant { // Calculate the current timestamp in seconds since the UNIX epoch let now = SystemTime::now(); let unix_seconds = now.duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs(); - - // Deadline is equal to the start of the next slot plus the time remaining in this slot + // Calculate the time remaining in the current slot let remaining = self.slot_calculator.calculate_timepoint_within_slot(unix_seconds); + // Deadline is equal to the start of the next slot plus the time remaining in this slot Instant::now() + Duration::from_secs(remaining) } From 5b7d565a3addc91d1827a28187ecb2a83112e8a1 Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 5 May 2025 15:37:06 -0600 Subject: [PATCH 49/53] remove an unnecessary clone --- src/tasks/block.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tasks/block.rs b/src/tasks/block.rs index f626dbc..318c1f8 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -147,8 +147,7 @@ impl Simulator { let basefee_reader = Arc::clone(&basefee_price); // Update the basefee on a per-block cadence - let basefee_jh = - tokio::spawn(async move { self.basefee_updater(Arc::clone(&basefee_price)).await }); + let basefee_jh = tokio::spawn(async move { self.basefee_updater(basefee_price).await }); // Update the sim cache whenever a transaction or bundle is received with respect to the basefee let cache_jh = tokio::spawn(async move { From f76387962f49db768e40e38211abf76a3acff3f5 Mon Sep 17 00:00:00 2001 From: dylan Date: Mon, 5 May 2025 15:43:04 -0600 Subject: [PATCH 50/53] explicitly sets the builder main function to be multi_thread - sets the builder main function to use multi-threaded runtime - this allows us to avoid a potential panic and enables a safe `unwrap` --- bin/builder.rs | 4 +++- src/tasks/block.rs | 13 +++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/bin/builder.rs b/bin/builder.rs index ed74d82..8a7cc0e 100644 --- a/bin/builder.rs +++ b/bin/builder.rs @@ -11,7 +11,9 @@ use signet_types::SlotCalculator; use std::sync::Arc; use tokio::select; -#[tokio::main] +// Note: Must be set to `multi_thread` to support async tasks. +// See: https://docs.rs/tokio/latest/tokio/attr.main.html +#[tokio::main(flavor = "multi_thread")] async fn main() -> eyre::Result<()> { let _guard = init4_bin_base::init4(); diff --git a/src/tasks/block.rs b/src/tasks/block.rs index 318c1f8..68a401c 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -301,6 +301,7 @@ impl Simulator { /// /// An `Option` containing the wrapped database or `None` if an error occurs. async fn create_db(&self) -> Option { + // Fetch latest block number let latest = match self.ru_provider.get_block_number().await { Ok(block_number) => block_number, Err(e) => { @@ -308,12 +309,16 @@ impl Simulator { return None; } }; + + // Make an AlloyDB instance from the rollup provider with that latest block number let alloy_db: AlloyDB = AlloyDB::new(self.ru_provider.clone(), BlockId::from(latest)); - let wrapped_db: WrapDatabaseAsync> = - WrapDatabaseAsync::new(alloy_db).unwrap_or_else(|| { - panic!("failed to acquire async alloy_db; check which runtime you're using") - }); + + // Wrap the AlloyDB instance in a WrapDatabaseAsync and return it. + // This is safe to unwrap because the main function sets the proper runtime settings. + // + // See: https://docs.rs/tokio/latest/tokio/attr.main.html + let wrapped_db: AlloyDatabaseProvider = WrapDatabaseAsync::new(alloy_db).unwrap(); Some(wrapped_db) } From 2b7d36fe25728a60a3840e1929c9d83e7d549dd6 Mon Sep 17 00:00:00 2001 From: dylan Date: Tue, 6 May 2025 13:46:46 -0600 Subject: [PATCH 51/53] add debug launcher to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index efe3eb1..acf25a3 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ Cargo.lock # Added by cargo /target + +# VSCode debug launcher +.vscode/launch.json \ No newline at end of file From 0643397cefc61f9c42a916685c9e53881d33ff19 Mon Sep 17 00:00:00 2001 From: dylan Date: Tue, 6 May 2025 13:52:05 -0600 Subject: [PATCH 52/53] pin bin-base to 0.3 instead of main --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6c05fd0..df68f0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ name = "transaction-submitter" path = "bin/submit_transaction.rs" [dependencies] -init4-bin-base = { git = "https://github.com/init4tech/bin-base.git" } +init4-bin-base = "0.3" signet-zenith = { git = "https://github.com/init4tech/signet-sdk", branch = "main" } signet-types = { git = "https://github.com/init4tech/signet-sdk", branch = "main" } From 31103796d6806a15aa948a55b426a47e4d81ba35 Mon Sep 17 00:00:00 2001 From: dylan Date: Tue, 6 May 2025 15:04:52 -0600 Subject: [PATCH 53/53] clippy --- src/tasks/block.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tasks/block.rs b/src/tasks/block.rs index 68a401c..345dd68 100644 --- a/src/tasks/block.rs +++ b/src/tasks/block.rs @@ -499,9 +499,9 @@ impl PecorinoBlockEnv { /// /// - config: The BuilderConfig for the builder. /// - number: The block number of this block, usually the latest block number plus 1, - /// unless simulating blocks in the past. + /// unless simulating blocks in the past. /// - timestamp: The timestamp of the block, typically set to the deadline of the - /// block building task. + /// block building task. fn new(config: BuilderConfig, number: u64, timestamp: u64, basefee: u64) -> Self { PecorinoBlockEnv { number,