1use crate::{
2 cache::StateSource, env::SimEnv, BuiltBlock, HostEnv, RollupEnv, SharedSimEnv, SimCache, SimDb,
3};
4use std::time::Duration;
5use tokio::select;
6use tracing::{debug, trace};
7use trevm::{
8 helpers::Ctx,
9 revm::{inspector::NoOpInspector, DatabaseRef, Inspector},
10};
11
12pub(crate) const SIM_SLEEP_MS: u64 = 50;
14
15#[derive(Debug)]
17pub struct BlockBuild<
18 RuDb,
19 HostDb,
20 RuAsync,
21 HostAsync,
22 RuInsp = NoOpInspector,
23 HostInsp = NoOpInspector,
24> {
25 env: SharedSimEnv<RuDb, HostDb, RuInsp, HostInsp>,
27
28 ru_async_source: RuAsync,
30
31 host_async_source: HostAsync,
33
34 block: BuiltBlock,
36
37 finish_by: tokio::time::Instant,
39
40 max_gas: u64,
42
43 max_host_gas: u64,
46}
47
48impl<RuDb, HostDb, RuAsync, HostAsync, RuInsp, HostInsp>
49 BlockBuild<RuDb, HostDb, RuAsync, HostAsync, RuInsp, HostInsp>
50where
51 RuDb: DatabaseRef + Send + Sync + 'static,
52 RuInsp: Inspector<Ctx<SimDb<RuDb>>> + Default + Sync + 'static,
53 HostDb: DatabaseRef + Send + Sync + 'static,
54 HostInsp: Inspector<Ctx<SimDb<HostDb>>> + Default + Sync + 'static,
55 RuAsync: StateSource,
56 HostAsync: StateSource,
57{
58 #[expect(clippy::too_many_arguments, reason = "should be refactored to avoid this warning")]
60 pub fn new(
61 rollup: RollupEnv<RuDb, RuInsp>,
62 host: HostEnv<HostDb, HostInsp>,
63 finish_by: tokio::time::Instant,
64 concurrency_limit: usize,
65 sim_items: SimCache,
66 max_gas: u64,
67 max_host_gas: u64,
68 ru_async_source: RuAsync,
69 host_async_source: HostAsync,
70 ) -> Self {
71 let number = rollup.block().number;
72
73 let env = SimEnv::<RuDb, HostDb, RuInsp, HostInsp>::new(
74 rollup,
75 host,
76 finish_by,
77 concurrency_limit,
78 sim_items,
79 );
80 let finish_by = env.finish_by();
81 Self {
82 env: env.into(),
83 ru_async_source,
84 host_async_source,
85 block: BuiltBlock::new(number.to()),
86 finish_by,
87 max_gas,
88 max_host_gas,
89 }
90 }
91
92 pub const fn max_gas(&self) -> u64 {
94 self.max_gas
95 }
96
97 pub const fn set_max_gas(&mut self, max_gas: u64) {
99 self.max_gas = max_gas;
100 }
101
102 pub const fn max_host_gas(&self) -> u64 {
104 self.max_host_gas
105 }
106
107 pub const fn set_max_host_gas(&mut self, max_host_gas: u64) {
109 self.max_host_gas = max_host_gas;
110 }
111
112 pub fn sim_items(&self) -> &SimCache {
114 self.env.sim_items()
115 }
116
117 pub fn rollup_env(&self) -> &RollupEnv<RuDb, RuInsp> {
119 self.env.rollup_env()
120 }
121
122 pub fn host_env(&self) -> &HostEnv<HostDb, HostInsp> {
124 self.env.host_env()
125 }
126
127 pub fn into_block(self) -> BuiltBlock {
132 self.block
133 }
134
135 async fn round(&mut self) {
137 let gas_allowed = self.max_gas - self.block.gas_used();
138 let host_gas_allowed = self.max_host_gas - self.block.host_gas_used();
139
140 if let Some(simulated) = self
141 .env
142 .sim_round(
143 gas_allowed,
144 host_gas_allowed,
145 &self.ru_async_source,
146 &self.host_async_source,
147 )
148 .await
149 {
150 debug!(
151 score = %simulated.score,
152 gas_used = simulated.gas_used,
153 host_gas_used = simulated.host_gas_used,
154 identifier = %simulated.item.identifier(),
155 "Adding item to block"
156 );
157 self.block.ingest(simulated);
158 }
159 }
160
161 pub async fn run_build(mut self) -> Self {
168 if !self.block.transactions.is_empty() {
169 debug!(
170 transactions = self.block.transactions.len(),
171 "Starting block build with pre-existing transactions",
172 );
173 return self;
174 }
175 let mut i = 1;
176 loop {
178 let finish_by = self.finish_by;
179
180 let next_round_time = tokio::time::Instant::now() + Duration::from_millis(SIM_SLEEP_MS);
181
182 if next_round_time >= finish_by {
186 debug!("Next round time is past the deadline, stopping sim loop");
187 break;
188 }
189
190 if self.env.sim_items().is_empty() {
193 tokio::time::sleep_until(next_round_time).await;
194 continue;
195 }
196
197 let prev_tx_count = self.block.transactions.len();
199 let fut = self.round();
200
201 select! {
202 biased;
203 _ = tokio::time::sleep_until(finish_by) => {
204 debug!("Deadline reached, stopping sim loop");
207 break;
208 },
209 _ = fut => {
210 i += 1;
211 let remaining_items = self.env.sim_items().len();
212 trace!(remaining_items, "Round completed");
213 }
214 }
215
216 if self.block.transactions.len() == prev_tx_count {
220 tokio::time::sleep_until(next_round_time).await;
221 }
222 }
223
224 debug!(
225 rounds = i,
226 transactions = self.block.transactions.len(),
227 remaining_cache_size = self.env.sim_items().len(),
228 "Building completed",
229 );
230 self
231 }
232
233 pub async fn build(self) -> BuiltBlock {
236 self.run_build().await.block
237 }
238}
239
240#[cfg(test)]
241mod test {
242 use super::*;
243 use crate::cache::StateSource;
244 use std::future::Future;
245
246 fn _build_fut_is_send<RuDb, HostDb, RuAsync, HostAsync, RuInsp, HostInsp>(
249 b: BlockBuild<RuDb, HostDb, RuAsync, HostAsync, RuInsp, HostInsp>,
250 ) where
251 RuDb: DatabaseRef + Send + Sync + 'static,
252 RuInsp: Inspector<Ctx<SimDb<RuDb>>> + Default + Sync + 'static,
253 HostDb: DatabaseRef + Send + Sync + 'static,
254 HostInsp: Inspector<Ctx<SimDb<HostDb>>> + Default + Sync + 'static,
255 RuAsync: StateSource,
256 HostAsync: StateSource,
257 {
258 let _: Box<dyn Future<Output = BuiltBlock> + Send> = Box::new(b.build());
259 }
260}