Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
16f8f33
feat: adds host simulation to block build
dylanlott Nov 5, 2025
38e1ab7
cleanup: pass proper max gas to block build
dylanlott Nov 5, 2025
0fd7b54
comment the patch deps for CI
dylanlott Nov 5, 2025
4608eb3
wip: building with bin-base @ 0.16.0
dylanlott Nov 5, 2025
fbecd4c
fix block number handling in sim
dylanlott Nov 5, 2025
2082350
bump bin-base to 0.16 and remove git patch
dylanlott Nov 5, 2025
71ff088
fix cfg env
anna-carroll Nov 6, 2025
ee3be91
host env and rollup env fixes
dylanlott Nov 6, 2025
0ebc530
fmt + clippy
dylanlott Nov 6, 2025
27c84d2
fix: make calldata empty bytes array
dylanlott Nov 7, 2025
6166fcb
fix: move block env creation for host to env task (#169)
prestwich Nov 7, 2025
633ed97
fix: use latest instead of specific block number for host sim db crea…
dylanlott Nov 7, 2025
7988775
logging for environment setup
dylanlott Nov 7, 2025
7894fbd
moar logging around db and block heights
dylanlott Nov 7, 2025
0085db9
debug loggin for header comparison
dylanlott Nov 7, 2025
28547d4
logging
dylanlott Nov 8, 2025
79ed6cd
fix: instantiate DB on previous block state
anna-carroll Nov 10, 2025
02c0065
refactor: DRY
prestwich Nov 10, 2025
5e15a79
refactor: remove chain ids from config, use constants
anna-carroll Nov 10, 2025
960dbe7
fix: bug: submit to Zenith instead of Builder Helper. also remove Bui…
anna-carroll Nov 10, 2025
8de551a
chore: remove some unnecessary arguments
prestwich Nov 10, 2025
356a4cb
refactor: remove zenith address from config, use constants
anna-carroll Nov 10, 2025
7d75424
lint: fmt
prestwich Nov 10, 2025
466d8d4
testing out host block number `n + 1`
dylanlott Nov 10, 2025
216efd4
refactor: DRY up the env construction function
dylanlott Nov 10, 2025
b6d52e8
put it back
dylanlott Nov 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
942 changes: 424 additions & 518 deletions Cargo.lock

Large diffs are not rendered by default.

31 changes: 22 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ name = "zenith-builder-example"
path = "bin/builder.rs"

[dependencies]
init4-bin-base = { version = "0.13.1", features = ["perms", "aws" ] }
init4-bin-base = { version = "0.16.0", features = ["perms", "aws"] }

signet-constants = { version = "0.11.2" }
signet-sim = { version = "0.11.2" }
signet-tx-cache = { version = "0.11.2" }
signet-types = { version = "0.11.2" }
signet-zenith = { version = "0.11.2" }
signet-constants = { version = "0.13.0" }
signet-sim = { version = "0.13.0" }
signet-tx-cache = { version = "0.13.0" }
signet-types = { version = "0.13.0" }
signet-zenith = { version = "0.13.0" }

trevm = { version = "0.29.0", features = ["concurrent-db", "test-utils"] }
trevm = { version = "0.29", features = ["concurrent-db", "test-utils"] }

alloy = { version = "1.0.37", features = [
alloy = { version = "1.0.35", features = [
"full",
"json-rpc",
"signer-aws",
Expand All @@ -39,7 +39,7 @@ alloy = { version = "1.0.37", features = [
"node-bindings",
"serde",
"getrandom",
"provider-mev-api"
"provider-mev-api",
] }

axum = "0.7.5"
Expand All @@ -52,3 +52,16 @@ tracing = "0.1.41"
tokio = { version = "1.36.0", features = ["full", "macros", "rt-multi-thread"] }
tokio-stream = "0.1.17"
url = "2.5.4"

# comment / uncomment for local dev
# [patch.crates-io]
# signet-constants = { path = "../signet-sdk/crates/constants" }
# signet-types = { path = "../signet-sdk/crates/types" }
# signet-zenith = { path = "../signet-sdk/crates/zenith" }
# signet-sim = { path = "../signet-sdk/crates/sim" }
# signet-evm = { path = "../signet-sdk/crates/evm" }
# signet-extract = { path = "../signet-sdk/crates/extract" }
# signet-journal = { path = "../signet-sdk/crates/journal" }
# signet-tx-cache = { path = "../signet-sdk/crates/tx-cache" }
# signet-bundle = { path = "../signet-sdk/crates/bundle" }
# init4-bin-base = { path = "../bin-base" }
7 changes: 3 additions & 4 deletions bin/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async fn main() -> eyre::Result<()> {
let cache_system = cache_tasks.spawn();

// Set up the metrics task
let metrics = MetricsTask { host_provider };
let metrics = MetricsTask { host_provider: host_provider.clone() };
let (tx_channel, metrics_jh) = metrics.spawn();

// Set up the submit task. This will be either a Flashbots task or a
Expand All @@ -42,9 +42,8 @@ async fn main() -> eyre::Result<()> {
let (submit_channel, submit_jh) = config.spawn_submit_task(tx_channel).await?;

// Set up the simulator
let sim = Simulator::new(&config, ru_provider.clone(), block_env);
let build_jh =
sim.spawn_simulator_task(config.constants.clone(), cache_system.sim_cache, submit_channel);
let sim = Simulator::new(&config, host_provider, ru_provider, block_env);
let build_jh = sim.spawn_simulator_task(cache_system.sim_cache, submit_channel);

// Start the healthcheck server
let server = serve_builder(([0, 0, 0, 0], config.builder_port));
Expand Down
52 changes: 20 additions & 32 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
use crate::{
quincey::Quincey,
tasks::{
block::{cfg::SignetCfgEnv, sim::SimResult},
submit::FlashbotsTask,
},
tasks::{block::sim::SimResult, submit::FlashbotsTask},
};
use alloy::{
network::{Ethereum, EthereumWallet},
Expand Down Expand Up @@ -73,14 +70,6 @@ pub const DEFAULT_CONCURRENCY_LIMIT: usize = 8;
/// chain.
#[derive(Debug, Clone, FromEnv)]
pub struct BuilderConfig {
/// The chain ID of the host chain.
#[from_env(var = "HOST_CHAIN_ID", desc = "The chain ID of the host chain")]
pub host_chain_id: u64,

/// The chain ID of the rollup chain.
#[from_env(var = "RU_CHAIN_ID", desc = "The chain ID of the rollup chain")]
pub ru_chain_id: u64,

/// URL for Host RPC node.
#[from_env(
var = "HOST_RPC_URL",
Expand Down Expand Up @@ -121,17 +110,6 @@ pub struct BuilderConfig {
)]
pub flashbots_endpoint: Option<url::Url>,

/// Address of the Zenith contract on Host.
#[from_env(var = "ZENITH_ADDRESS", desc = "address of the Zenith contract on Host")]
pub zenith_address: Address,

/// Address of the Builder Helper contract on Host.
#[from_env(
var = "BUILDER_HELPER_ADDRESS",
desc = "address of the Builder Helper contract on Host"
)]
pub builder_helper_address: Address,

/// URL for remote Quincey Sequencer server to sign blocks.
/// NB: Disregarded if a sequencer_signer is configured.
#[from_env(
Expand Down Expand Up @@ -185,10 +163,18 @@ pub struct BuilderConfig {
)]
pub concurrency_limit: Option<usize>,

/// Optional maximum host gas coefficient to use when building blocks.
/// Defaults to 80% (80) if not set.
#[from_env(
var = "MAX_HOST_GAS_COEFFICIENT",
desc = "Optional maximum host gas coefficient, as a percentage, to use when building blocks",
default = 80
)]
pub max_host_gas_coefficient: Option<u8>,

/// The slot calculator for the builder.
pub slot_calculator: SlotCalculator,

// TODO: Make this compatible with FromEnv again, somehow it broke
/// The signet system constants.
pub constants: SignetSystemConstants,
}
Expand All @@ -199,7 +185,7 @@ impl BuilderConfig {
static ONCE: tokio::sync::OnceCell<LocalOrAws> = tokio::sync::OnceCell::const_new();

ONCE.get_or_try_init(|| async {
LocalOrAws::load(&self.builder_key, Some(self.host_chain_id)).await
LocalOrAws::load(&self.builder_key, Some(self.constants.host_chain_id())).await
})
.await
.cloned()
Expand All @@ -209,7 +195,7 @@ impl BuilderConfig {
/// Connect to the Sequencer signer.
pub async fn connect_sequencer_signer(&self) -> eyre::Result<Option<LocalOrAws>> {
if let Some(sequencer_key) = &self.sequencer_key {
LocalOrAws::load(sequencer_key, Some(self.host_chain_id))
LocalOrAws::load(sequencer_key, Some(self.constants.host_chain_id()))
.await
.map_err(Into::into)
.map(Some)
Expand Down Expand Up @@ -271,7 +257,7 @@ impl BuilderConfig {

/// Connect to the Zenith instance, using the specified provider.
pub const fn connect_zenith(&self, provider: HostProvider) -> ZenithInstance {
Zenith::new(self.zenith_address, provider)
Zenith::new(self.constants.host_zenith(), provider)
}

/// Get an oauth2 token for the builder, starting the authenticator if it
Expand Down Expand Up @@ -301,11 +287,6 @@ impl BuilderConfig {
Ok(Quincey::new_remote(client, url, token))
}

/// Create a [`SignetCfgEnv`] using this config.
pub const fn cfg_env(&self) -> SignetCfgEnv {
SignetCfgEnv { chain_id: self.ru_chain_id }
}

/// Memoizes the concurrency limit for the current system. Uses [`std::thread::available_parallelism`] if no
/// value is set. If that for some reason fails, it returns the default concurrency limit.
pub fn concurrency_limit(&self) -> usize {
Expand All @@ -324,6 +305,13 @@ impl BuilderConfig {
})
}

/// Returns the maximum host gas to use for block building based on the configured max host gas coefficient.
pub fn max_host_gas(&self, gas_limit: u64) -> u64 {
// Set max host gas to a percentage of the host block gas limit
((gas_limit as u128 * (self.max_host_gas_coefficient.unwrap_or(80) as u128)) / 100u128)
as u64
}

/// Spawn a submit task, either Flashbots or BuilderHelper depending on
/// configuration.
pub async fn spawn_submit_task(
Expand Down
76 changes: 33 additions & 43 deletions src/tasks/block/sim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
//! actor that handles the simulation of a stream of bundles and transactions
//! and turns them into valid Pecorino blocks for network submission.
use crate::{
config::{BuilderConfig, RuProvider},
config::{BuilderConfig, HostProvider, RuProvider},
tasks::env::SimEnv,
};
use alloy::{eips::BlockId, network::Ethereum};
use alloy::consensus::Header;
use init4_bin_base::{
deps::metrics::{counter, histogram},
utils::calc::SlotCalculator,
Expand All @@ -21,13 +21,6 @@ use tokio::{
task::JoinHandle,
};
use tracing::{Instrument, Span, debug, instrument};
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
Expand All @@ -36,6 +29,8 @@ type AlloyDatabaseProvider = WrapDatabaseAsync<AlloyDB<Ethereum, RuProvider>>;
pub struct Simulator {
/// Configuration for the builder.
pub config: BuilderConfig,
/// Host Provider to interact with the host chain.
pub host_provider: HostProvider,
/// A provider that cannot sign transactions, used for interacting with the rollup.
pub ru_provider: RuProvider,
/// The block configuration environment on which to simulate
Expand All @@ -52,8 +47,18 @@ pub struct SimResult {
}

impl SimResult {
/// Get a reference to the previous host header.
pub const fn prev_host(&self) -> &Header {
self.sim_env.prev_host()
}

/// Get a reference to the previous rollup header.
pub const fn prev_rollup(&self) -> &Header {
self.sim_env.prev_rollup()
}

/// Returns the block number of the built block.
pub const fn block_number(&self) -> u64 {
pub const fn rollup_block_number(&self) -> u64 {
self.block.block_number()
}

Expand Down Expand Up @@ -88,17 +93,23 @@ impl Simulator {
/// A new `Simulator` instance.
pub fn new(
config: &BuilderConfig,
host_provider: HostProvider,
ru_provider: RuProvider,
sim_env: watch::Receiver<Option<SimEnv>>,
) -> Self {
Self { config: config.clone(), ru_provider, sim_env }
Self { config: config.clone(), host_provider, ru_provider, sim_env }
}

/// Get the slot calculator.
pub const fn slot_calculator(&self) -> &SlotCalculator {
&self.config.slot_calculator
}

/// Get the system constants.
pub const fn constants(&self) -> &SignetSystemConstants {
&self.config.constants
}

/// Handles building a single block.
///
/// Builds a block in the block environment with items from the simulation cache
Expand All @@ -121,25 +132,24 @@ impl Simulator {
))]
pub async fn handle_build(
&self,
constants: SignetSystemConstants,
sim_items: SimCache,
finish_by: Instant,
block_env: BlockEnv,
sim_env: &SimEnv,
) -> eyre::Result<BuiltBlock> {
let concurrency_limit = self.config.concurrency_limit();

// NB: Build AlloyDB from the previous block number's state, since block_env maps to the in-progress block
let db = self.create_db(block_env.number.to::<u64>() - 1).unwrap();
let rollup_env = sim_env.sim_rollup_env(self.constants(), self.ru_provider.clone());

let block_build: BlockBuild<_, NoOpInspector> = BlockBuild::new(
db,
constants,
self.config.cfg_env(),
block_env,
let host_env = sim_env.sim_host_env(self.constants(), self.host_provider.clone());

let block_build = BlockBuild::new(
rollup_env,
host_env,
finish_by,
concurrency_limit,
sim_items,
self.config.rollup_block_gas_limit,
self.config.max_host_gas(sim_env.prev_host().gas_limit),
);

let built_block = block_build.build().in_current_span().await;
Expand All @@ -149,7 +159,7 @@ impl Simulator {
"block simulation completed",
);
counter!("signet.builder.built_blocks").increment(1);
histogram!("signet.builder.built_blocks.tx_count").record(built_block.tx_count() as f64);
histogram!("signet.builder.built_blocks.tx_count").record(built_block.tx_count() as u32);

Ok(built_block)
}
Expand All @@ -168,13 +178,12 @@ impl Simulator {
/// A `JoinHandle` for the spawned task.
pub fn spawn_simulator_task(
self,
constants: SignetSystemConstants,
cache: SimCache,
submit_sender: mpsc::UnboundedSender<SimResult>,
) -> JoinHandle<()> {
debug!("starting simulator task");

tokio::spawn(async move { self.run_simulator(constants, cache, submit_sender).await })
tokio::spawn(async move { self.run_simulator(cache, submit_sender).await })
}

/// This function runs indefinitely, waiting for the block environment to be set and checking
Expand All @@ -195,7 +204,6 @@ impl Simulator {
/// - `submit_sender`: A channel sender used to submit built blocks.
async fn run_simulator(
mut self,
constants: SignetSystemConstants,
cache: SimCache,
submit_sender: mpsc::UnboundedSender<SimResult>,
) {
Expand All @@ -217,7 +225,7 @@ impl Simulator {
let sim_cache = cache.clone();

let Ok(block) = self
.handle_build(constants.clone(), sim_cache, finish_by, sim_env.block_env.clone())
.handle_build(sim_cache, finish_by, &sim_env)
.instrument(span.clone())
.await
.inspect_err(|err| span_error!(span, %err, "error during block build"))
Expand Down Expand Up @@ -250,22 +258,4 @@ impl Simulator {
let deadline = Instant::now() + Duration::from_secs(remaining);
deadline.max(Instant::now())
}

/// Creates an `AlloyDB` instance from the rollup provider.
///
/// # Returns
///
/// An `Option` containing the wrapped database or `None` if an error occurs.
fn create_db(&self, latest_block_number: u64) -> Option<AlloyDatabaseProvider> {
// Make an AlloyDB instance from the rollup provider with that latest block number
let alloy_db: AlloyDB<Ethereum, RuProvider> =
AlloyDB::new(self.ru_provider.clone(), BlockId::from(latest_block_number));

// 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)
}
}
Loading