-
Notifications
You must be signed in to change notification settings - Fork 0
refactor: integrate env and cache tasks #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+419
−596
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,30 @@ | ||
//! `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. | ||
use super::cfg::PecorinoBlockEnv; | ||
use crate::{ | ||
config::{BuilderConfig, RuProvider}, | ||
tasks::{block::cfg::PecorinoCfg, cache::Bundle}, | ||
}; | ||
use alloy::{ | ||
consensus::TxEnvelope, | ||
eips::{BlockId, BlockNumberOrTag::Latest}, | ||
network::Ethereum, | ||
providers::Provider, | ||
}; | ||
use chrono::{DateTime, Utc}; | ||
use eyre::{Context, bail}; | ||
use crate::config::{BuilderConfig, RuProvider}; | ||
use alloy::{eips::BlockId, network::Ethereum, providers::Provider}; | ||
use init4_bin_base::{ | ||
deps::tracing::{debug, error, info, warn}, | ||
deps::tracing::{debug, error}, | ||
utils::calc::SlotCalculator, | ||
}; | ||
use signet_sim::{BlockBuild, BuiltBlock, SimCache}; | ||
use signet_types::constants::SignetSystemConstants; | ||
use std::{ | ||
use std::time::{Duration, Instant}; | ||
use tokio::{ | ||
sync::{ | ||
Arc, | ||
atomic::{AtomicU64, Ordering}, | ||
mpsc::{self}, | ||
watch, | ||
}, | ||
time::{Duration, Instant, SystemTime, UNIX_EPOCH}, | ||
}; | ||
use tokio::{ | ||
select, | ||
sync::mpsc::{self}, | ||
task::JoinHandle, | ||
time::sleep, | ||
}; | ||
use trevm::revm::{ | ||
context::BlockEnv, | ||
database::{AlloyDB, WrapDatabaseAsync}, | ||
inspector::NoOpInspector, | ||
}; | ||
|
||
type AlloyDatabaseProvider = WrapDatabaseAsync<AlloyDB<Ethereum, RuProvider>>; | ||
|
||
/// `Simulator` is responsible for periodically building blocks and submitting them for | ||
/// signing and inclusion in the blockchain. It wraps a rollup provider and a slot | ||
/// calculator with a builder configuration. | ||
|
@@ -47,11 +34,10 @@ pub struct Simulator { | |
pub config: BuilderConfig, | ||
/// A provider that cannot sign transactions, used for interacting with the rollup. | ||
pub ru_provider: RuProvider, | ||
/// The slot calculator for determining when to wake up and build blocks. | ||
pub slot_calculator: SlotCalculator, | ||
} | ||
|
||
type AlloyDatabaseProvider = WrapDatabaseAsync<AlloyDB<Ethereum, RuProvider>>; | ||
/// The block configuration environment on which to simulate | ||
pub block_env: watch::Receiver<Option<BlockEnv>>, | ||
} | ||
|
||
impl Simulator { | ||
/// Creates a new `Simulator` instance. | ||
|
@@ -60,17 +46,21 @@ impl Simulator { | |
/// | ||
/// - `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, | ||
ru_provider: RuProvider, | ||
slot_calculator: SlotCalculator, | ||
block_env: watch::Receiver<Option<BlockEnv>>, | ||
) -> Self { | ||
Self { config: config.clone(), ru_provider, slot_calculator } | ||
Self { config: config.clone(), ru_provider, block_env } | ||
} | ||
|
||
/// Get the slot calculator. | ||
pub const fn slot_calculator(&self) -> &SlotCalculator { | ||
&self.config.slot_calculator | ||
} | ||
|
||
/// Handles building a single block. | ||
|
@@ -89,14 +79,14 @@ impl Simulator { | |
constants: SignetSystemConstants, | ||
sim_items: SimCache, | ||
finish_by: Instant, | ||
block: PecorinoBlockEnv, | ||
block: BlockEnv, | ||
) -> eyre::Result<BuiltBlock> { | ||
let db = self.create_db().await.unwrap(); | ||
|
||
let block_build: BlockBuild<_, NoOpInspector> = BlockBuild::new( | ||
db, | ||
constants, | ||
PecorinoCfg {}, | ||
self.config.cfg_env(), | ||
block, | ||
finish_by, | ||
self.config.concurrency_limit, | ||
|
@@ -110,85 +100,6 @@ impl Simulator { | |
Ok(block) | ||
} | ||
|
||
/// 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. | ||
/// - `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_tasks( | ||
&self, | ||
tx_receiver: mpsc::UnboundedReceiver<TxEnvelope>, | ||
bundle_receiver: mpsc::UnboundedReceiver<Bundle>, | ||
cache: SimCache, | ||
) -> (JoinHandle<()>, JoinHandle<()>) { | ||
debug!("starting up cache handler"); | ||
|
||
let basefee_price = Arc::new(AtomicU64::new(0_u64)); | ||
let basefee_reader = Arc::clone(&basefee_price); | ||
let fut = self.basefee_updater_fut(basefee_price); | ||
|
||
// Update the basefee on a per-block cadence | ||
let basefee_jh = tokio::spawn(fut); | ||
|
||
// Update the sim cache whenever a transaction or bundle is received with respect to the basefee | ||
let cache_jh = tokio::spawn(async move { | ||
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<AtomicU64>` used to store the updated basefee value. | ||
fn basefee_updater_fut(&self, price: Arc<AtomicU64>) -> impl Future<Output = ()> + use<> { | ||
let slot_calculator = self.slot_calculator; | ||
let ru_provider = self.ru_provider.clone(); | ||
|
||
async move { | ||
debug!("starting basefee updater"); | ||
loop { | ||
// calculate start of next slot plus a small buffer | ||
let time_remaining = slot_calculator.slot_duration() | ||
- slot_calculator.current_timepoint_within_slot() | ||
+ 1; | ||
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 | ||
let resp = ru_provider.get_block_by_number(Latest).await.inspect_err(|e| { | ||
error!(error = %e, "RPC error during basefee update"); | ||
}); | ||
|
||
if let Ok(Some(block)) = resp { | ||
let basefee = block.header.base_fee_per_gas.unwrap_or(0); | ||
price.store(basefee, Ordering::Relaxed); | ||
debug!(basefee = basefee, "basefee updated"); | ||
} else { | ||
warn!("get basefee failed - an error likely occurred"); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// Spawns the simulator task, which handles the setup and sets the deadline | ||
/// for the each round of simulation. | ||
/// | ||
|
@@ -227,7 +138,7 @@ impl Simulator { | |
/// - `cache`: The simulation cache containing transactions and bundles. | ||
/// - `submit_sender`: A channel sender used to submit built blocks. | ||
async fn run_simulator( | ||
self, | ||
mut self, | ||
constants: SignetSystemConstants, | ||
cache: SimCache, | ||
submit_sender: mpsc::UnboundedSender<BuiltBlock>, | ||
|
@@ -236,14 +147,16 @@ impl Simulator { | |
let sim_cache = cache.clone(); | ||
let finish_by = self.calculate_deadline(); | ||
|
||
let block_env = match self.next_block_env(finish_by).await { | ||
Ok(block) => block, | ||
Err(err) => { | ||
error!(err = %err, "failed to configure next block"); | ||
break; | ||
} | ||
}; | ||
info!(block_env = ?block_env, "created block"); | ||
// Wait for the block environment to be set | ||
if self.block_env.changed().await.is_err() { | ||
error!("block_env channel closed"); | ||
return; | ||
} | ||
|
||
// If no env, skip this run | ||
let Some(block_env) = self.block_env.borrow_and_update().clone() else { return }; | ||
|
||
debug!(block_env = ?block_env, "building on block"); | ||
|
||
match self.handle_build(constants, sim_cache, finish_by, block_env).await { | ||
Ok(block) => { | ||
|
@@ -265,13 +178,19 @@ impl Simulator { | |
/// 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(); | ||
// 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) | ||
// Get the current timepoint within the slot. | ||
let timepoint = self.slot_calculator().current_timepoint_within_slot(); | ||
|
||
// We have the timepoint in seconds into the slot. To find out what's | ||
// remaining, we need to subtract it from the slot duration | ||
let remaining = self.slot_calculator().slot_duration() - timepoint; | ||
|
||
// We add a 1500 ms buffer to account for sequencer stopping signing. | ||
prestwich marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
let candidate = | ||
Instant::now() + Duration::from_secs(remaining) - Duration::from_millis(1500); | ||
|
||
candidate.max(Instant::now()) | ||
} | ||
|
||
/// Creates an `AlloyDB` instance from the rollup provider. | ||
|
@@ -300,106 +219,4 @@ impl Simulator { | |
let wrapped_db: AlloyDatabaseProvider = WrapDatabaseAsync::new(alloy_db).unwrap(); | ||
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) -> eyre::Result<PecorinoBlockEnv> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. replaced by EnvTask |
||
let remaining = finish_by.duration_since(Instant::now()); | ||
let finish_time = SystemTime::now() + remaining; | ||
let deadline: DateTime<Utc> = finish_time.into(); | ||
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) => { | ||
error!(%err, "RPC error during block build"); | ||
bail!(err) | ||
} | ||
}; | ||
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 => { | ||
warn!("get basefee failed - RPC error likely occurred"); | ||
todo!() | ||
} | ||
}; | ||
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, | ||
); | ||
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) -> eyre::Result<Option<u64>> { | ||
let Some(block) = | ||
self.ru_provider.get_block_by_number(Latest).await.wrap_err("basefee error")? | ||
else { | ||
return Ok(None); | ||
}; | ||
|
||
debug!(basefee = ?block.header.base_fee_per_gas, "basefee found"); | ||
Ok(block.header.base_fee_per_gas) | ||
} | ||
} | ||
|
||
/// 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<AtomicU64>` providing the latest basefee for simulation pricing. | ||
async fn cache_updater( | ||
mut tx_receiver: mpsc::UnboundedReceiver< | ||
alloy::consensus::EthereumTxEnvelope<alloy::consensus::TxEip4844Variant>, | ||
>, | ||
mut bundle_receiver: mpsc::UnboundedReceiver<Bundle>, | ||
cache: SimCache, | ||
price_reader: Arc<AtomicU64>, | ||
) -> ! { | ||
loop { | ||
let p = price_reader.load(Ordering::Relaxed); | ||
select! { | ||
maybe_tx = tx_receiver.recv() => { | ||
if let Some(tx) = maybe_tx { | ||
debug!(tx = ?tx.hash(), "received transaction"); | ||
cache.add_item(tx, p); | ||
} | ||
} | ||
maybe_bundle = bundle_receiver.recv() => { | ||
if let Some(bundle) = maybe_bundle { | ||
debug!(bundle = ?bundle.id, "received bundle"); | ||
cache.add_item(bundle.bundle, p); | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,30 +6,13 @@ use init4_bin_base::{ | |
}; | ||
use oauth2::TokenResponse; | ||
use reqwest::{Client, Url}; | ||
use serde::{Deserialize, Serialize}; | ||
use signet_bundle::SignetEthBundle; | ||
use signet_tx_cache::types::{TxCacheBundle, TxCacheBundlesResponse}; | ||
use tokio::{ | ||
sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel}, | ||
task::JoinHandle, | ||
time::{self, Duration}, | ||
}; | ||
|
||
/// Holds a bundle from the cache with a unique ID and a Zenith bundle. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. replaced with sdk types |
||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
pub struct Bundle { | ||
/// Cache identifier for the bundle. | ||
pub id: String, | ||
/// The corresponding Signet bundle. | ||
pub bundle: SignetEthBundle, | ||
} | ||
|
||
/// Response from the tx-pool containing a list of bundles. | ||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
struct TxPoolBundleResponse { | ||
/// Bundle responses are available on the bundles property. | ||
pub bundles: Vec<Bundle>, | ||
} | ||
|
||
/// The BundlePoller polls the tx-pool for bundles. | ||
#[derive(Debug)] | ||
pub struct BundlePoller { | ||
|
@@ -60,7 +43,7 @@ impl BundlePoller { | |
} | ||
|
||
/// Fetches bundles from the transaction cache and returns them. | ||
pub async fn check_bundle_cache(&mut self) -> eyre::Result<Vec<Bundle>> { | ||
pub async fn check_bundle_cache(&mut self) -> eyre::Result<Vec<TxCacheBundle>> { | ||
let bundle_url: Url = Url::parse(&self.config.tx_pool_url)?.join("bundles")?; | ||
let Some(token) = self.token.read() else { | ||
warn!("No token available, skipping bundle fetch"); | ||
|
@@ -75,7 +58,7 @@ impl BundlePoller { | |
.error_for_status()? | ||
.json() | ||
.await | ||
.map(|resp: TxPoolBundleResponse| resp.bundles) | ||
.map(|resp: TxCacheBundlesResponse| resp.bundles) | ||
Evalir marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.map_err(Into::into) | ||
} | ||
|
||
|
@@ -84,7 +67,7 @@ impl BundlePoller { | |
Duration::from_millis(self.poll_interval_ms) | ||
} | ||
|
||
async fn task_future(mut self, outbound: UnboundedSender<Bundle>) { | ||
async fn task_future(mut self, outbound: UnboundedSender<TxCacheBundle>) { | ||
loop { | ||
let span = debug_span!("BundlePoller::loop", url = %self.config.tx_pool_url); | ||
|
||
|
@@ -119,7 +102,7 @@ impl BundlePoller { | |
} | ||
|
||
/// Spawns a task that sends bundles it finds to its channel sender. | ||
pub fn spawn(self) -> (UnboundedReceiver<Bundle>, JoinHandle<()>) { | ||
pub fn spawn(self) -> (UnboundedReceiver<TxCacheBundle>, JoinHandle<()>) { | ||
let (outbound, inbound) = unbounded_channel(); | ||
|
||
let jh = tokio::spawn(self.task_future(outbound)); | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,134 +1,130 @@ | ||
//! Tests for the block building task. | ||
#[cfg(test)] | ||
mod tests { | ||
use alloy::{ | ||
network::Ethereum, | ||
node_bindings::Anvil, | ||
primitives::U256, | ||
providers::{Provider, RootProvider}, | ||
signers::local::PrivateKeySigner, | ||
}; | ||
use builder::{ | ||
tasks::block::sim::Simulator, | ||
test_utils::{new_signed_tx, setup_logging, setup_test_config, test_block_env}, | ||
}; | ||
use init4_bin_base::utils::calc::SlotCalculator; | ||
use signet_sim::{SimCache, SimItem}; | ||
use signet_types::constants::SignetSystemConstants; | ||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; | ||
use tokio::{sync::mpsc::unbounded_channel, time::timeout}; | ||
|
||
/// 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 | ||
/// transactions from multiple senders. | ||
#[cfg(feature = "integration")] | ||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)] | ||
async fn test_handle_build() { | ||
setup_logging(); | ||
|
||
// Make a test config | ||
let config = setup_test_config().unwrap(); | ||
let constants = SignetSystemConstants::pecorino(); | ||
|
||
// Create an anvil instance for testing | ||
let anvil_instance = Anvil::new().chain_id(signet_constants::pecorino::RU_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()); | ||
|
||
// Create a rollup provider | ||
let ru_provider = RootProvider::<Ethereum>::new_http(anvil_instance.endpoint_url()); | ||
|
||
// 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(); | ||
|
||
let slot_calculator = SlotCalculator::new(now, 0, 12); | ||
let block_builder = Simulator::new(&config, ru_provider.clone(), slot_calculator); | ||
|
||
// Setup a sim cache | ||
let sim_items = SimCache::new(); | ||
|
||
// 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), 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), 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, block_env).await; | ||
|
||
// Assert on the built block | ||
assert!(got.is_ok()); | ||
assert!(got.unwrap().tx_count() == 2); | ||
} | ||
|
||
/// 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. | ||
#[ignore = "integration test"] | ||
#[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 = SignetSystemConstants::pecorino(); | ||
|
||
// Create an anvil instance for testing | ||
let anvil_instance = Anvil::new().chain_id(signet_constants::pecorino::RU_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 = RootProvider::<Ethereum>::new_http(anvil_instance.endpoint_url()); | ||
|
||
let sim = Simulator::new(&config, ru_provider.clone(), config.slot_calculator); | ||
|
||
// Create a shared sim cache | ||
let sim_cache = SimCache::new(); | ||
|
||
// Create a sim cache and start filling it with items | ||
sim.spawn_cache_tasks(tx_receiver, bundle_receiver, sim_cache.clone()); | ||
|
||
// Finally, Kick off the block builder task. | ||
sim.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(); | ||
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(); | ||
|
||
// 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(); | ||
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. | ||
} | ||
use alloy::{ | ||
network::Ethereum, | ||
node_bindings::Anvil, | ||
primitives::U256, | ||
providers::{Provider, RootProvider}, | ||
signers::local::PrivateKeySigner, | ||
}; | ||
use builder::{ | ||
tasks::{block::sim::Simulator, cache::CacheTask}, | ||
test_utils::{new_signed_tx, setup_logging, setup_test_config, test_block_env}, | ||
}; | ||
use signet_sim::{SimCache, SimItem}; | ||
use signet_types::constants::SignetSystemConstants; | ||
use std::time::{Duration, Instant}; | ||
use tokio::{sync::mpsc::unbounded_channel, time::timeout}; | ||
|
||
/// 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 | ||
/// transactions from multiple senders. | ||
#[ignore = "integration test"] | ||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)] | ||
async fn test_handle_build() { | ||
use alloy::eips::BlockId; | ||
|
||
setup_logging(); | ||
|
||
// Make a test config | ||
let config = setup_test_config().unwrap(); | ||
let constants = SignetSystemConstants::pecorino(); | ||
|
||
// Create an anvil instance for testing | ||
let anvil_instance = Anvil::new().chain_id(signet_constants::pecorino::RU_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()); | ||
|
||
// Create a rollup provider | ||
let ru_provider = RootProvider::<Ethereum>::new_http(anvil_instance.endpoint_url()); | ||
|
||
let block_env = config.env_task().spawn().0; | ||
|
||
let block_builder = Simulator::new(&config, ru_provider.clone(), block_env); | ||
|
||
// Setup a sim cache | ||
let sim_items = SimCache::new(); | ||
|
||
// 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), 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), 0); | ||
|
||
// Setup the block env | ||
let finish_by = Instant::now() + Duration::from_secs(2); | ||
let header = ru_provider.get_block(BlockId::latest()).await.unwrap().unwrap().header.inner; | ||
let number = header.number + 1; | ||
let timestamp = header.timestamp + config.slot_calculator.slot_duration(); | ||
let block_env = test_block_env(config, number, 7, timestamp); | ||
|
||
// Spawn the block builder task | ||
let got = block_builder.handle_build(constants, sim_items, finish_by, block_env).await; | ||
|
||
// Assert on the built block | ||
assert!(got.is_ok()); | ||
assert!(got.unwrap().tx_count() == 2); | ||
} | ||
|
||
/// 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. | ||
#[ignore = "integration test"] | ||
#[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 = SignetSystemConstants::pecorino(); | ||
|
||
// Create an anvil instance for testing | ||
let anvil_instance = Anvil::new().chain_id(signet_constants::pecorino::RU_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(); | ||
|
||
let env_task = config.env_task(); | ||
let (block_env, _env_jh) = env_task.spawn(); | ||
|
||
let cache_task = CacheTask::new(block_env.clone(), bundle_receiver, tx_receiver); | ||
let (sim_cache, _cache_jh) = cache_task.spawn(); | ||
|
||
// Create a rollup provider | ||
let ru_provider = RootProvider::<Ethereum>::new_http(anvil_instance.endpoint_url()); | ||
|
||
let sim = Simulator::new(&config, ru_provider.clone(), block_env); | ||
|
||
// Finally, Kick off the block builder task. | ||
sim.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(); | ||
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(); | ||
|
||
// 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(); | ||
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. | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,15 @@ | ||
mod tests { | ||
use builder::test_utils; | ||
use eyre::Result; | ||
use builder::test_utils; | ||
use eyre::Result; | ||
|
||
#[ignore = "integration test"] | ||
#[tokio::test] | ||
async fn test_bundle_poller_roundtrip() -> Result<()> { | ||
let config = test_utils::setup_test_config().unwrap(); | ||
let token = config.oauth_token(); | ||
#[ignore = "integration test"] | ||
#[tokio::test] | ||
async fn test_bundle_poller_roundtrip() -> Result<()> { | ||
let config = test_utils::setup_test_config().unwrap(); | ||
let token = config.oauth_token(); | ||
|
||
let mut bundle_poller = builder::tasks::cache::BundlePoller::new(&config, token); | ||
let mut bundle_poller = builder::tasks::cache::BundlePoller::new(&config, token); | ||
|
||
let _ = bundle_poller.check_bundle_cache().await?; | ||
let _ = bundle_poller.check_bundle_cache().await?; | ||
|
||
Ok(()) | ||
} | ||
Ok(()) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
use builder::test_utils::{setup_logging, setup_test_config}; | ||
use init4_bin_base::deps::tracing::warn; | ||
use std::time::Duration; | ||
|
||
#[ignore = "integration test. This test will take >12 seconds to run, and requires Authz configuration env vars."] | ||
#[tokio::test] | ||
async fn test_bundle_poller_roundtrip() -> eyre::Result<()> { | ||
setup_logging(); | ||
|
||
let config = setup_test_config().unwrap(); | ||
|
||
let (block_env, _jh) = config.env_task().spawn(); | ||
let cache = config.spawn_cache_system(block_env); | ||
|
||
tokio::time::sleep(Duration::from_secs(12)).await; | ||
|
||
warn!(txns = ?cache.sim_cache.read_best(5)); | ||
|
||
Ok(()) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
use builder::test_utils::{setup_logging, setup_test_config}; | ||
|
||
#[ignore = "integration test. This test will take between 0 and 12 seconds to run."] | ||
#[tokio::test] | ||
async fn test_bundle_poller_roundtrip() { | ||
setup_logging(); | ||
|
||
let config = setup_test_config().unwrap(); | ||
let env_task = config.env_task(); | ||
let (mut env_watcher, _jh) = env_task.spawn(); | ||
|
||
env_watcher.changed().await.unwrap(); | ||
let env = env_watcher.borrow_and_update(); | ||
assert!(env.as_ref().is_some(), "Env should be Some"); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,50 +1,48 @@ | ||
mod tests { | ||
use alloy::{primitives::U256, signers::local::PrivateKeySigner}; | ||
use builder::{ | ||
config::BuilderConfig, | ||
tasks::cache::TxPoller, | ||
test_utils::{new_signed_tx, setup_logging, setup_test_config}, | ||
}; | ||
// Import the refactored function | ||
use eyre::{Ok, Result}; | ||
use alloy::{primitives::U256, signers::local::PrivateKeySigner}; | ||
use builder::{ | ||
config::BuilderConfig, | ||
tasks::cache::TxPoller, | ||
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(); | ||
#[ignore = "integration test"] | ||
#[tokio::test] | ||
async fn test_tx_roundtrip() -> Result<()> { | ||
setup_logging(); | ||
|
||
// Create a new test environment | ||
let config = setup_test_config()?; | ||
// Create a new test environment | ||
let config = setup_test_config()?; | ||
|
||
// Post a transaction to the cache | ||
post_tx(&config).await?; | ||
// Post a transaction to the cache | ||
post_tx(&config).await?; | ||
|
||
// Create a new poller | ||
let mut poller = TxPoller::new(&config); | ||
// Create a new poller | ||
let mut poller = TxPoller::new(&config); | ||
|
||
// Fetch transactions the pool | ||
let transactions = poller.check_tx_cache().await?; | ||
// Fetch transactions the pool | ||
let transactions = poller.check_tx_cache().await?; | ||
|
||
// Ensure at least one transaction exists | ||
assert!(!transactions.is_empty()); | ||
// Ensure at least one transaction exists | ||
assert!(!transactions.is_empty()); | ||
|
||
Ok(()) | ||
} | ||
|
||
async fn post_tx(config: &BuilderConfig) -> Result<()> { | ||
let client = reqwest::Client::new(); | ||
Ok(()) | ||
} | ||
|
||
let wallet = PrivateKeySigner::random(); | ||
let tx_envelope = new_signed_tx(&wallet, 1, U256::from(1), 10_000)?; | ||
async fn post_tx(config: &BuilderConfig) -> Result<()> { | ||
let client = reqwest::Client::new(); | ||
|
||
let url = format!("{}/transactions", config.tx_pool_url); | ||
let response = client.post(&url).json(&tx_envelope).send().await?; | ||
let wallet = PrivateKeySigner::random(); | ||
let tx_envelope = new_signed_tx(&wallet, 1, U256::from(1), 10_000)?; | ||
|
||
if !response.status().is_success() { | ||
let error_text = response.text().await?; | ||
eyre::bail!("Failed to post transaction: {}", error_text); | ||
} | ||
let url = format!("{}/transactions", config.tx_pool_url); | ||
let response = client.post(&url).json(&tx_envelope).send().await?; | ||
|
||
Ok(()) | ||
if !response.status().is_success() { | ||
let error_text = response.text().await?; | ||
eyre::bail!("Failed to post transaction: {}", error_text); | ||
} | ||
|
||
Ok(()) | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems to have been giving incorrect output. we need to sub remaining from duration, then add that to now