Skip to content

Commit 14a7e35

Browse files
authored
feat: adds host simulation to block build (#167)
# feat: adds host simulation to block build This PR adds host simulation to the block build loop. > Note: Requires the signet-sdk release of `0.13.0`​ in order to pass CI. - bumps bin-base to `0.15.1`​ - deprecates the `BuilderHelper`​ wrapper contract submission in favor of the raw `Zenith`​ contract - wires up the `HostProvider`​ into the Simulator for host transaction simulation
1 parent f10ff92 commit 14a7e35

File tree

12 files changed

+704
-689
lines changed

12 files changed

+704
-689
lines changed

Cargo.lock

Lines changed: 395 additions & 514 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,17 @@ name = "zenith-builder-example"
1818
path = "bin/builder.rs"
1919

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

23-
signet-constants = { version = "0.11.2" }
24-
signet-sim = { version = "0.11.2" }
25-
signet-tx-cache = { version = "0.11.2" }
26-
signet-types = { version = "0.11.2" }
27-
signet-zenith = { version = "0.11.2" }
23+
signet-constants = { version = "0.13.0" }
24+
signet-sim = { version = "0.13.0" }
25+
signet-tx-cache = { version = "0.13.0" }
26+
signet-types = { version = "0.13.0" }
27+
signet-zenith = { version = "0.13.0" }
2828

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

31-
alloy = { version = "1.0.37", features = [
31+
alloy = { version = "1.0.35", features = [
3232
"full",
3333
"json-rpc",
3434
"signer-aws",
@@ -39,7 +39,7 @@ alloy = { version = "1.0.37", features = [
3939
"node-bindings",
4040
"serde",
4141
"getrandom",
42-
"provider-mev-api"
42+
"provider-mev-api",
4343
] }
4444

4545
axum = "0.7.5"
@@ -52,3 +52,16 @@ tracing = "0.1.41"
5252
tokio = { version = "1.36.0", features = ["full", "macros", "rt-multi-thread"] }
5353
tokio-stream = "0.1.17"
5454
url = "2.5.4"
55+
56+
# comment / uncomment for local dev
57+
# [patch.crates-io]
58+
# signet-constants = { path = "../signet-sdk/crates/constants" }
59+
# signet-types = { path = "../signet-sdk/crates/types" }
60+
# signet-zenith = { path = "../signet-sdk/crates/zenith" }
61+
# signet-sim = { path = "../signet-sdk/crates/sim" }
62+
# signet-evm = { path = "../signet-sdk/crates/evm" }
63+
# signet-extract = { path = "../signet-sdk/crates/extract" }
64+
# signet-journal = { path = "../signet-sdk/crates/journal" }
65+
# signet-tx-cache = { path = "../signet-sdk/crates/tx-cache" }
66+
# signet-bundle = { path = "../signet-sdk/crates/bundle" }
67+
# init4-bin-base = { path = "../bin-base" }

bin/builder.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,8 @@ async fn main() -> eyre::Result<()> {
4444
let (submit_channel, submit_jh) = submit.spawn();
4545

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

5150
// Start the healthcheck server
5251
let server = serve_builder(([0, 0, 0, 0], config.builder_port));

src/config.rs

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{quincey::Quincey, tasks::block::cfg::SignetCfgEnv};
1+
use crate::quincey::Quincey;
22
use alloy::{
33
network::{Ethereum, EthereumWallet},
44
primitives::Address,
@@ -66,14 +66,6 @@ pub const DEFAULT_CONCURRENCY_LIMIT: usize = 8;
6666
/// chain.
6767
#[derive(Debug, Clone, FromEnv)]
6868
pub struct BuilderConfig {
69-
/// The chain ID of the host chain.
70-
#[from_env(var = "HOST_CHAIN_ID", desc = "The chain ID of the host chain")]
71-
pub host_chain_id: u64,
72-
73-
/// The chain ID of the rollup chain.
74-
#[from_env(var = "RU_CHAIN_ID", desc = "The chain ID of the rollup chain")]
75-
pub ru_chain_id: u64,
76-
7769
/// URL for Host RPC node.
7870
#[from_env(
7971
var = "HOST_RPC_URL",
@@ -101,17 +93,6 @@ pub struct BuilderConfig {
10193
)]
10294
pub flashbots_endpoint: Option<url::Url>,
10395

104-
/// Address of the Zenith contract on Host.
105-
#[from_env(var = "ZENITH_ADDRESS", desc = "address of the Zenith contract on Host")]
106-
pub zenith_address: Address,
107-
108-
/// Address of the Builder Helper contract on Host.
109-
#[from_env(
110-
var = "BUILDER_HELPER_ADDRESS",
111-
desc = "address of the Builder Helper contract on Host"
112-
)]
113-
pub builder_helper_address: Address,
114-
11596
/// URL for remote Quincey Sequencer server to sign blocks.
11697
/// NB: Disregarded if a sequencer_signer is configured.
11798
#[from_env(
@@ -165,10 +146,18 @@ pub struct BuilderConfig {
165146
)]
166147
pub concurrency_limit: Option<usize>,
167148

149+
/// Optional maximum host gas coefficient to use when building blocks.
150+
/// Defaults to 80% (80) if not set.
151+
#[from_env(
152+
var = "MAX_HOST_GAS_COEFFICIENT",
153+
desc = "Optional maximum host gas coefficient, as a percentage, to use when building blocks",
154+
default = 80
155+
)]
156+
pub max_host_gas_coefficient: Option<u8>,
157+
168158
/// The slot calculator for the builder.
169159
pub slot_calculator: SlotCalculator,
170160

171-
// TODO: Make this compatible with FromEnv again, somehow it broke
172161
/// The signet system constants.
173162
pub constants: SignetSystemConstants,
174163
}
@@ -179,7 +168,7 @@ impl BuilderConfig {
179168
static ONCE: tokio::sync::OnceCell<LocalOrAws> = tokio::sync::OnceCell::const_new();
180169

181170
ONCE.get_or_try_init(|| async {
182-
LocalOrAws::load(&self.builder_key, Some(self.host_chain_id)).await
171+
LocalOrAws::load(&self.builder_key, Some(self.constants.host_chain_id())).await
183172
})
184173
.await
185174
.cloned()
@@ -189,7 +178,7 @@ impl BuilderConfig {
189178
/// Connect to the Sequencer signer.
190179
pub async fn connect_sequencer_signer(&self) -> eyre::Result<Option<LocalOrAws>> {
191180
if let Some(sequencer_key) = &self.sequencer_key {
192-
LocalOrAws::load(sequencer_key, Some(self.host_chain_id))
181+
LocalOrAws::load(sequencer_key, Some(self.constants.host_chain_id()))
193182
.await
194183
.map_err(Into::into)
195184
.map(Some)
@@ -240,7 +229,7 @@ impl BuilderConfig {
240229

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

246235
/// Get an oauth2 token for the builder, starting the authenticator if it
@@ -270,11 +259,6 @@ impl BuilderConfig {
270259
Ok(Quincey::new_remote(client, url, token))
271260
}
272261

273-
/// Create a [`SignetCfgEnv`] using this config.
274-
pub const fn cfg_env(&self) -> SignetCfgEnv {
275-
SignetCfgEnv { chain_id: self.ru_chain_id }
276-
}
277-
278262
/// Memoizes the concurrency limit for the current system. Uses [`std::thread::available_parallelism`] if no
279263
/// value is set. If that for some reason fails, it returns the default concurrency limit.
280264
pub fn concurrency_limit(&self) -> usize {
@@ -292,4 +276,11 @@ impl BuilderConfig {
292276
.unwrap_or(DEFAULT_CONCURRENCY_LIMIT)
293277
})
294278
}
279+
280+
/// Returns the maximum host gas to use for block building based on the configured max host gas coefficient.
281+
pub fn max_host_gas(&self, gas_limit: u64) -> u64 {
282+
// Set max host gas to a percentage of the host block gas limit
283+
((gas_limit as u128 * (self.max_host_gas_coefficient.unwrap_or(80) as u128)) / 100u128)
284+
as u64
285+
}
295286
}

src/tasks/block/sim.rs

Lines changed: 33 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
//! actor that handles the simulation of a stream of bundles and transactions
33
//! and turns them into valid Pecorino blocks for network submission.
44
use crate::{
5-
config::{BuilderConfig, RuProvider},
5+
config::{BuilderConfig, HostProvider, RuProvider},
66
tasks::env::SimEnv,
77
};
8-
use alloy::{eips::BlockId, network::Ethereum};
8+
use alloy::consensus::Header;
99
use init4_bin_base::{
1010
deps::metrics::{counter, histogram},
1111
utils::calc::SlotCalculator,
@@ -21,13 +21,6 @@ use tokio::{
2121
task::JoinHandle,
2222
};
2323
use tracing::{Instrument, Span, debug, instrument};
24-
use trevm::revm::{
25-
context::BlockEnv,
26-
database::{AlloyDB, WrapDatabaseAsync},
27-
inspector::NoOpInspector,
28-
};
29-
30-
type AlloyDatabaseProvider = WrapDatabaseAsync<AlloyDB<Ethereum, RuProvider>>;
3124

3225
/// `Simulator` is responsible for periodically building blocks and submitting them for
3326
/// signing and inclusion in the blockchain. It wraps a rollup provider and a slot
@@ -36,6 +29,8 @@ type AlloyDatabaseProvider = WrapDatabaseAsync<AlloyDB<Ethereum, RuProvider>>;
3629
pub struct Simulator {
3730
/// Configuration for the builder.
3831
pub config: BuilderConfig,
32+
/// Host Provider to interact with the host chain.
33+
pub host_provider: HostProvider,
3934
/// A provider that cannot sign transactions, used for interacting with the rollup.
4035
pub ru_provider: RuProvider,
4136
/// The block configuration environment on which to simulate
@@ -52,8 +47,18 @@ pub struct SimResult {
5247
}
5348

5449
impl SimResult {
50+
/// Get a reference to the previous host header.
51+
pub const fn prev_host(&self) -> &Header {
52+
self.sim_env.prev_host()
53+
}
54+
55+
/// Get a reference to the previous rollup header.
56+
pub const fn prev_rollup(&self) -> &Header {
57+
self.sim_env.prev_rollup()
58+
}
59+
5560
/// Returns the block number of the built block.
56-
pub const fn block_number(&self) -> u64 {
61+
pub const fn rollup_block_number(&self) -> u64 {
5762
self.block.block_number()
5863
}
5964

@@ -88,17 +93,23 @@ impl Simulator {
8893
/// A new `Simulator` instance.
8994
pub fn new(
9095
config: &BuilderConfig,
96+
host_provider: HostProvider,
9197
ru_provider: RuProvider,
9298
sim_env: watch::Receiver<Option<SimEnv>>,
9399
) -> Self {
94-
Self { config: config.clone(), ru_provider, sim_env }
100+
Self { config: config.clone(), host_provider, ru_provider, sim_env }
95101
}
96102

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

108+
/// Get the system constants.
109+
pub const fn constants(&self) -> &SignetSystemConstants {
110+
&self.config.constants
111+
}
112+
102113
/// Handles building a single block.
103114
///
104115
/// Builds a block in the block environment with items from the simulation cache
@@ -121,25 +132,24 @@ impl Simulator {
121132
))]
122133
pub async fn handle_build(
123134
&self,
124-
constants: SignetSystemConstants,
125135
sim_items: SimCache,
126136
finish_by: Instant,
127-
block_env: BlockEnv,
137+
sim_env: &SimEnv,
128138
) -> eyre::Result<BuiltBlock> {
129139
let concurrency_limit = self.config.concurrency_limit();
130140

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

134-
let block_build: BlockBuild<_, NoOpInspector> = BlockBuild::new(
135-
db,
136-
constants,
137-
self.config.cfg_env(),
138-
block_env,
143+
let host_env = sim_env.sim_host_env(self.constants(), self.host_provider.clone());
144+
145+
let block_build = BlockBuild::new(
146+
rollup_env,
147+
host_env,
139148
finish_by,
140149
concurrency_limit,
141150
sim_items,
142151
self.config.rollup_block_gas_limit,
152+
self.config.max_host_gas(sim_env.prev_host().gas_limit),
143153
);
144154

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

154164
Ok(built_block)
155165
}
@@ -168,13 +178,12 @@ impl Simulator {
168178
/// A `JoinHandle` for the spawned task.
169179
pub fn spawn_simulator_task(
170180
self,
171-
constants: SignetSystemConstants,
172181
cache: SimCache,
173182
submit_sender: mpsc::UnboundedSender<SimResult>,
174183
) -> JoinHandle<()> {
175184
debug!("starting simulator task");
176185

177-
tokio::spawn(async move { self.run_simulator(constants, cache, submit_sender).await })
186+
tokio::spawn(async move { self.run_simulator(cache, submit_sender).await })
178187
}
179188

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

219227
let Ok(block) = self
220-
.handle_build(constants.clone(), sim_cache, finish_by, sim_env.block_env.clone())
228+
.handle_build(sim_cache, finish_by, &sim_env)
221229
.instrument(span.clone())
222230
.await
223231
.inspect_err(|err| span_error!(span, %err, "error during block build"))
@@ -250,22 +258,4 @@ impl Simulator {
250258
let deadline = Instant::now() + Duration::from_secs(remaining);
251259
deadline.max(Instant::now())
252260
}
253-
254-
/// Creates an `AlloyDB` instance from the rollup provider.
255-
///
256-
/// # Returns
257-
///
258-
/// An `Option` containing the wrapped database or `None` if an error occurs.
259-
fn create_db(&self, latest_block_number: u64) -> Option<AlloyDatabaseProvider> {
260-
// Make an AlloyDB instance from the rollup provider with that latest block number
261-
let alloy_db: AlloyDB<Ethereum, RuProvider> =
262-
AlloyDB::new(self.ru_provider.clone(), BlockId::from(latest_block_number));
263-
264-
// Wrap the AlloyDB instance in a WrapDatabaseAsync and return it.
265-
// This is safe to unwrap because the main function sets the proper runtime settings.
266-
//
267-
// See: https://docs.rs/tokio/latest/tokio/attr.main.html
268-
let wrapped_db: AlloyDatabaseProvider = WrapDatabaseAsync::new(alloy_db).unwrap();
269-
Some(wrapped_db)
270-
}
271261
}

src/tasks/cache/task.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use tracing::{debug, info};
1616
#[derive(Debug)]
1717
pub struct CacheTask {
1818
/// The channel to receive the block environment.
19-
env: watch::Receiver<Option<SimEnv>>,
19+
envs: watch::Receiver<Option<SimEnv>>,
2020
/// The channel to receive the transaction bundles.
2121
bundles: mpsc::UnboundedReceiver<TxCacheBundle>,
2222
/// The channel to receive the transactions.
@@ -30,24 +30,27 @@ impl CacheTask {
3030
bundles: mpsc::UnboundedReceiver<TxCacheBundle>,
3131
txns: mpsc::UnboundedReceiver<TxEnvelope>,
3232
) -> Self {
33-
Self { env, bundles, txns }
33+
Self { envs: env, bundles, txns }
3434
}
3535

3636
async fn task_future(mut self, cache: SimCache) {
3737
loop {
3838
let mut basefee = 0;
3939
tokio::select! {
4040
biased;
41-
res = self.env.changed() => {
41+
res = self.envs.changed() => {
4242
if res.is_err() {
4343
debug!("Cache task: env channel closed, exiting");
4444
break;
4545
}
46-
if let Some(env) = self.env.borrow_and_update().as_ref() {
47-
basefee = env.block_env.basefee;
48-
info!(basefee, block_env_number = env.block_env.number.to::<u64>(), block_env_timestamp = env.block_env.timestamp.to::<u64>(), "rollup block env changed, clearing cache");
46+
47+
if let Some(env) = self.envs.borrow_and_update().as_ref() {
48+
let sim_env = env.rollup_env();
49+
50+
basefee = sim_env.basefee;
51+
info!(basefee, block_env_number = sim_env.number.to::<u64>(), block_env_timestamp = sim_env.timestamp.to::<u64>(), "rollup block env changed, clearing cache");
4952
cache.clean(
50-
env.block_env.number.to(), env.block_env.timestamp.to()
53+
sim_env.number.to(), sim_env.timestamp.to()
5154
);
5255
}
5356
}

0 commit comments

Comments
 (0)