1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
use crate::{env::SimEnv, BuiltBlock, HostEnv, RollupEnv, SharedSimEnv, SimCache, SimDb};
use std::time::Duration;
use tokio::{select, time::Instant};
use tracing::{debug, info_span, trace, Instrument};
use trevm::{
helpers::Ctx,
revm::{inspector::NoOpInspector, DatabaseRef, Inspector},
};
/// The amount of time to sleep between simulation rounds when there are no items to simulate.
pub(crate) const SIM_SLEEP_MS: u64 = 50;
/// Builds a single block by repeatedly invoking [`SimEnv`].
#[derive(Debug)]
pub struct BlockBuild<RuDb, HostDb, RuInsp = NoOpInspector, HostInsp = NoOpInspector> {
/// The simulation environment.
env: SharedSimEnv<RuDb, HostDb, RuInsp, HostInsp>,
/// The block being built.
block: BuiltBlock,
/// The deadline to produce a block by.
finish_by: std::time::Instant,
/// The maximum amount of gas to use in the built block
max_gas: u64,
/// The maximum amount of host gas to use in the user portion of the built
/// block, not including overhead for the signet RU block submission.
max_host_gas: u64,
}
impl<RuDb, HostDb, RuInsp, HostInsp> BlockBuild<RuDb, HostDb, RuInsp, HostInsp>
where
RuDb: DatabaseRef + Send + Sync + 'static,
RuInsp: Inspector<Ctx<SimDb<RuDb>>> + Default + Sync + 'static,
HostDb: DatabaseRef + Send + Sync + 'static,
HostInsp: Inspector<Ctx<SimDb<HostDb>>> + Default + Sync + 'static,
{
/// Create a new block building process.
pub fn new(
rollup: RollupEnv<RuDb, RuInsp>,
host: HostEnv<HostDb, HostInsp>,
finish_by: std::time::Instant,
concurrency_limit: usize,
sim_items: SimCache,
max_gas: u64,
max_host_gas: u64,
) -> Self {
let number = rollup.block().number;
let env = SimEnv::<RuDb, HostDb, RuInsp, HostInsp>::new(
rollup,
host,
finish_by,
concurrency_limit,
sim_items,
);
let finish_by = env.finish_by();
Self {
env: env.into(),
block: BuiltBlock::new(number.to()),
finish_by,
max_gas,
max_host_gas,
}
}
/// Get the maximum gas limit for the block being built.
pub const fn max_gas(&self) -> u64 {
self.max_gas
}
/// Set the maximum gas limit for the block being built.
pub const fn set_max_gas(&mut self, max_gas: u64) {
self.max_gas = max_gas;
}
/// Get the maximum host gas limit for the block being built.
pub const fn max_host_gas(&self) -> u64 {
self.max_host_gas
}
/// Set the maximum host gas limit for the block being built.
pub const fn set_max_host_gas(&mut self, max_host_gas: u64) {
self.max_host_gas = max_host_gas;
}
/// Get a reference the simulation cache used by this builder.
pub fn sim_items(&self) -> &SimCache {
self.env.sim_items()
}
/// Get a reference to the rollup environment.
pub fn rollup_env(&self) -> &RollupEnv<RuDb, RuInsp> {
self.env.rollup_env()
}
/// Get a reference to the host environment.
pub fn host_env(&self) -> &HostEnv<HostDb, HostInsp> {
self.env.host_env()
}
/// Consume the builder and return the built block.
///
/// This should generally not be called directly; use [`BlockBuild::build`]
/// instead.
pub fn into_block(self) -> BuiltBlock {
self.block
}
/// Run a simulation round, and accumulate the results into the block.
async fn round(&mut self) {
let gas_allowed = self.max_gas - self.block.gas_used();
let host_gas_allowed = self.max_host_gas - self.block.host_gas_used();
if let Some(simulated) = self.env.sim_round(gas_allowed, host_gas_allowed).await {
tracing::debug!(
score = %simulated.score,
gas_used = simulated.gas_used,
host_gas_used = simulated.host_gas_used,
identifier = %simulated.item.identifier(),
"Adding item to block"
);
self.block.ingest(simulated);
}
}
/// Run several rounds, building a block by iteratively adding simulated
/// items.
///
/// This version returns self to allow inspection of the building process.
/// It does nothing if the block already has transactions (i.e. this
/// function should be idempotent).
pub async fn run_build(mut self) -> Self {
if !self.block.transactions.is_empty() {
debug!(
transactions = self.block.transactions.len(),
"Starting block build with pre-existing transactions",
);
return self;
}
let mut i = 1;
// Run until the deadline is reached.
loop {
let span = info_span!("build", round = i);
let finish_by = self.finish_by.into();
let next_round_time = Instant::now() + Duration::from_millis(SIM_SLEEP_MS);
// If the next round time is past the deadline, we stop the simulation loop.
// This will stop the simulation even if there are items, but that is an acceptable tradeoff
// as we must ensure there's enough time to submit the blob to the host chain.
if next_round_time >= finish_by {
debug!("Next round time is past the deadline, stopping sim loop");
break;
}
// Only simulate if there are items to simulate.
// If there are not items, we sleep for [`SIM_SLEEP_MS`] and restart the loop.
if self.env.sim_items().is_empty() {
tokio::time::sleep_until(next_round_time).await;
continue;
}
// If there are items to simulate, we run a simulation round.
let fut = self.round().instrument(span);
select! {
biased;
_ = tokio::time::sleep_until(finish_by) => {
debug!("Deadline reached, stopping sim loop");
break;
},
_ = fut => {
i+= 1;
let remaining = self.env.sim_items().len();
trace!(%remaining, round = i, "Round completed");
}
}
}
debug!(rounds = i, transactions = self.block.transactions.len(), "Building completed",);
self
}
/// Run several rounds, building a block by iteratively adding simulated
/// items.
pub async fn build(self) -> BuiltBlock {
self.run_build().await.block
}
}
#[cfg(test)]
mod test {
use std::future::Future;
use super::*;
/// Compile-time check to ensure that the block building process is
/// `Send`.
fn _build_fut_is_send<RuDb, HostDb, RuInsp, HostInsp>(
b: BlockBuild<RuDb, HostDb, RuInsp, HostInsp>,
) where
RuDb: DatabaseRef + Send + Sync + 'static,
RuInsp: Inspector<Ctx<SimDb<RuDb>>> + Default + Sync + 'static,
HostDb: DatabaseRef + Send + Sync + 'static,
HostInsp: Inspector<Ctx<SimDb<HostDb>>> + Default + Sync + 'static,
{
let _: Box<dyn Future<Output = BuiltBlock> + Send> = Box::new(b.build());
}
}