Skip to main content

signet_bundle/send/
driver.rs

1use crate::{RecoveredBundle, SignetEthBundleError};
2use alloy::{hex, primitives::U256};
3use signet_evm::{DriveBundleResult, EvmErrored, EvmNeedsTx, SignetInspector, SignetLayered};
4use signet_types::{AggregateFills, AggregateOrders};
5use std::borrow::Cow;
6use tracing::{debug, debug_span, enabled, error};
7use trevm::{
8    helpers::Ctx,
9    inspectors::{Layered, TimeLimit},
10    revm::{
11        context::result::EVMError, inspector::InspectorEvmTr, Database, DatabaseCommit, Inspector,
12    },
13    trevm_bail, trevm_ensure, trevm_try, BundleDriver, BundleError,
14};
15
16/// Inspector used in the impl of [`BundleDriver`] for
17/// [`SignetEthBundleDriver`].
18pub type SignetEthBundleInsp<I> = Layered<TimeLimit, I>;
19
20/// The output of the [`SignetEthBundleDriver`].
21#[derive(Debug)]
22pub struct DriverOutput<Db, Insp>
23where
24    Db: Database,
25    Insp: Inspector<Ctx<Db>>,
26{
27    /// The host evm used to run the bundle.
28    pub host_evm: Option<signet_evm::EvmNeedsTx<Db, Insp>>,
29
30    /// Total gas used by this bundle during execution.
31    pub total_gas_used: u64,
32
33    /// Total host gas used by this bundle during execution.
34    pub total_host_gas_used: u64,
35
36    /// Beneficiary balance increase during execution.
37    pub beneficiary_balance_increase: U256,
38
39    /// Running aggregate of fills during execution.
40    pub bundle_fills: AggregateFills,
41
42    /// Running aggregate of orders during execution.
43    pub bundle_orders: AggregateOrders,
44}
45
46impl<Db, Insp> DriverOutput<Db, Insp>
47where
48    Db: Database,
49    Insp: Inspector<Ctx<Db>>,
50{
51    /// Increase the total gas used by the given amount.
52    pub const fn use_gas(&mut self, gas: u64) {
53        self.total_gas_used = self.total_gas_used.saturating_add(gas);
54    }
55
56    /// Increase the total host gas used by the given amount.
57    pub const fn use_host_gas(&mut self, gas: u64) {
58        self.total_host_gas_used = self.total_host_gas_used.saturating_add(gas);
59    }
60
61    /// Absorb fills and orders into the running totals.
62    pub fn absorb(&mut self, fills: &AggregateFills, orders: &AggregateOrders) {
63        self.bundle_fills.absorb(fills);
64        self.bundle_orders.absorb(orders);
65    }
66
67    /// Record an increase in the beneficiary balance.
68    pub const fn record_beneficiary_increase(&mut self, increase: U256) {
69        self.beneficiary_balance_increase =
70            self.beneficiary_balance_increase.saturating_add(increase);
71    }
72}
73
74/// Driver for applying a Signet Ethereum bundle to an EVM.
75#[derive(Debug)]
76pub struct SignetEthBundleDriver<'a, 'b, Db, Insp>
77where
78    Db: Database,
79    Insp: Inspector<Ctx<Db>>,
80{
81    /// The bundle to apply.
82    bundle: &'a RecoveredBundle,
83
84    /// Reference to the fill state to check against.
85    pub fill_state: Cow<'b, AggregateFills>,
86
87    /// Execution deadline for this bundle. This limits the total WALLCLOCK
88    /// time spent simulating the bundle.
89    deadline: tokio::time::Instant,
90
91    // -- Accumulated outputs below here--
92    output: DriverOutput<Db, Insp>,
93}
94
95impl<'a, 'b, Db, Insp> SignetEthBundleDriver<'a, 'b, Db, Insp>
96where
97    Db: Database,
98    Insp: Inspector<Ctx<Db>>,
99{
100    /// Creates a new [`SignetEthBundleDriver`] with the given bundle and
101    /// response.
102    pub fn new(
103        bundle: &'a RecoveredBundle,
104        host_evm: signet_evm::EvmNeedsTx<Db, Insp>,
105        deadline: tokio::time::Instant,
106    ) -> Self {
107        Self::new_with_fill_state(bundle, host_evm, deadline, Default::default())
108    }
109
110    /// Creates a new [`SignetEthBundleDriver`] with the given bundle,
111    /// response, and aggregate fills.
112    ///
113    /// This is useful for testing, and for combined host-rollup simulation.
114    pub fn new_with_fill_state(
115        bundle: &'a RecoveredBundle,
116        host_evm: signet_evm::EvmNeedsTx<Db, Insp>,
117        deadline: tokio::time::Instant,
118        fill_state: Cow<'b, AggregateFills>,
119    ) -> Self {
120        Self {
121            bundle,
122            fill_state,
123            deadline,
124            output: DriverOutput {
125                host_evm: Some(host_evm),
126                total_gas_used: 0,
127                total_host_gas_used: 0,
128                beneficiary_balance_increase: U256::ZERO,
129                bundle_fills: AggregateFills::default(),
130                bundle_orders: AggregateOrders::default(),
131            },
132        }
133    }
134
135    /// Get a reference to the bundle.
136    pub const fn bundle(&self) -> &RecoveredBundle {
137        self.bundle
138    }
139
140    /// Get the deadline for this driver.
141    pub const fn deadline(&self) -> tokio::time::Instant {
142        self.deadline
143    }
144
145    /// Get the total gas used by this driver during tx execution.
146    pub const fn total_gas_used(&self) -> u64 {
147        self.output.total_gas_used
148    }
149
150    /// Get the beneficiary balance increase for this driver during tx execution.
151    pub const fn beneficiary_balance_increase(&self) -> U256 {
152        self.output.beneficiary_balance_increase
153    }
154
155    /// Take the aggregate orders and fills from this driver.
156    pub fn into_outputs(self) -> DriverOutput<Db, Insp> {
157        self.output
158    }
159}
160
161impl<RuDb, HostDb, RuInsp, HostInsp> BundleDriver<RuDb, SignetLayered<Layered<TimeLimit, RuInsp>>>
162    for SignetEthBundleDriver<'_, '_, HostDb, HostInsp>
163where
164    RuDb: Database + DatabaseCommit,
165    RuInsp: Inspector<Ctx<RuDb>>,
166    HostDb: Database + DatabaseCommit,
167    HostInsp: Inspector<Ctx<HostDb>>,
168{
169    type Error = SignetEthBundleError<RuDb>;
170
171    fn run_bundle(
172        &mut self,
173        mut trevm: EvmNeedsTx<RuDb, SignetEthBundleInsp<RuInsp>>,
174    ) -> DriveBundleResult<Self, RuDb, SignetEthBundleInsp<RuInsp>> {
175        // -- STATELESS CHECKS --
176
177        // Ensure that the bundle has transactions
178        trevm_ensure!(!self.bundle.txs.is_empty(), trevm, BundleError::BundleEmpty.into());
179
180        // Check if the block we're in is valid for this bundle. Both must match
181        trevm_ensure!(
182            trevm.block_number().to::<u64>() == self.bundle.block_number,
183            trevm,
184            BundleError::BlockNumberMismatch.into()
185        );
186
187        // Check if the state block number is valid (not 0, and not a tag)
188        let timestamp = trevm.block_timestamp();
189        trevm_ensure!(
190            self.bundle.is_valid_at_timestamp(timestamp.to()),
191            trevm,
192            BundleError::TimestampOutOfRange.into()
193        );
194
195        // -- STATEFUL ACTIONS --
196
197        // Get the beneficiary address and its initial balance
198        let beneficiary = trevm.beneficiary();
199        let inital_beneficiary_balance =
200            trevm_try!(trevm.try_read_balance(beneficiary).map_err(EVMError::Database), trevm);
201
202        // -- HOST PORTION --
203
204        // We simply run all host transactions first, accumulating their state
205        // changes into the host_evm's state. If any reverts, we error out the
206        // simulation.
207        for tx in self.bundle.host_txs().iter() {
208            self.output.host_evm = Some(trevm_try!(
209                self.output
210                    .host_evm
211                    .take()
212                    .expect("host_evm missing")
213                    .run_tx(tx)
214                    .and_then(|mut htrevm| {
215                        let result = htrevm.result();
216                        if let Some(output) = result.output()  {
217                            if !result.is_success() {
218                                debug!(
219                                    tx_hash = %tx.hash(),
220                                    callee = ?htrevm.callee(),
221                                    sender = ?htrevm.caller(),
222                                    input = hex::encode(htrevm.input()),
223                                    output = hex::encode(output),
224                                    "host transaction reverted"
225                                );
226                            }
227                        }
228
229                        trevm_ensure!(
230                            result.is_success(),
231                            htrevm,
232                            EVMError::Custom("host transaction reverted".to_string())
233                        );
234
235                        // Accumulate gas used
236                        self.output.use_host_gas(result.gas_used());
237
238                        // The host fills go in the bundle fills.
239                        let host_fills = htrevm
240                            .inner_mut_unchecked()
241                            .inspector
242                            .as_mut_detector()
243                            .take_aggregates()
244                            .0;
245                        self.output.bundle_fills.absorb(&host_fills);
246
247                        Ok(htrevm.accept_state())
248                    })
249                    .map_err(|err| {
250                        debug!(err = %err.error(), err_dbg = ?err.error(), "error while running host transaction");
251                        SignetEthBundleError::HostSimulation("host simulation error")
252                    }),
253                trevm
254            ));
255        }
256
257        // -- ROLLUP PORTION --
258        for tx in self.bundle.txs().iter() {
259            let span = debug_span!(
260                "bundle_tx_loop",
261                tx_hash = %tx.hash(),
262                caller = tracing::field::Empty,
263                callee = tracing::field::Empty,
264                input = tracing::field::Empty,
265            );
266            let _guard = span.enter();
267
268            // Update the inner deadline.
269            let limit = trevm.inner_mut_unchecked().ctx_inspector().1.outer_mut().outer_mut();
270            *limit = TimeLimit::new(self.deadline - tokio::time::Instant::now());
271
272            let tx_hash = tx.hash();
273
274            // Temporary rebinding of trevm within each loop iteration.
275            // The type of t is `EvmTransacted`, while the type of trevm is
276            // `EvmNeedsTx`.
277            let mut t = trevm.run_tx(tx).map_err(EvmErrored::err_into).inspect_err(
278                |err| error!(err = %err.error(), "error while running rollup transaction"),
279            )?;
280
281            // Record tx details to the span for debugging.
282            if enabled!(tracing::Level::DEBUG) {
283                span.record("caller", t.caller().to_string());
284                span.record(
285                    "callee",
286                    t.callee().map(|c| c.to_string()).unwrap_or_else(|| "CREATE".to_string()),
287                );
288                span.record("input", hex::encode(t.input()));
289            }
290
291            // Check the result of the transaction.
292            let result = t.result();
293            let gas_used = result.gas_used();
294
295            // EVM Execution succeeded.
296            // We now check if the orders are valid with the bundle's fills
297            // state. If not, and the tx is not marked as revertible by the
298            // bundle, we error our simulation.
299            if result.is_success() {
300                let (tx_fills, tx_orders) =
301                    t.inner_mut_unchecked().inspector.as_mut_detector().take_aggregates();
302
303                // These clones are inefficient. We can optimize later if
304                // needed.
305                let mut candidate_fills = self.output.bundle_fills.clone();
306                let mut candidate_orders = self.output.bundle_orders.clone();
307
308                // The candidate is the updated
309                candidate_fills.absorb(&tx_fills);
310                candidate_orders.absorb(&tx_orders);
311
312                // Then we check that the fills are sufficient against the
313                // provided fill state. This does nothing on error.
314                if self.fill_state.check_ru_tx_events(&candidate_fills, &candidate_orders).is_err()
315                {
316                    if self.bundle.reverting_tx_hashes().contains(tx_hash) {
317                        debug!("transaction marked as revertible, reverting");
318                        trevm = t.reject();
319                        continue;
320                    } else {
321                        debug!("transaction dropped due to insufficient fills, not marked as revertible");
322                        return Err(t.errored(BundleError::BundleReverted.into()));
323                    }
324                }
325
326                // Now we accept the fills and order candidates
327                self.output.bundle_fills = candidate_fills;
328                self.output.bundle_orders = candidate_orders;
329            } else {
330                // EVM Execution did not succeed.
331                // If not success, we are in a revert or halt. If the tx is
332                // not marked as revertible by the bundle, we error our
333                // simulation.
334                if !self.bundle.reverting_tx_hashes().contains(tx_hash) {
335                    debug!(
336                        output = result.output().map(hex::encode),
337                        "transaction reverted, not marked as revertible"
338                    );
339                    return Err(t.errored(BundleError::BundleReverted.into()));
340                }
341            }
342
343            // If we did not shortcut return/continue, we accept the state
344            // changes from this transaction.
345            self.output.use_gas(gas_used);
346            trevm = t.accept_state()
347        }
348
349        // -- CLEANUP --
350
351        let beneficiary_balance =
352            trevm_try!(trevm.try_read_balance(beneficiary).map_err(EVMError::Database), trevm);
353
354        self.output.record_beneficiary_increase(
355            beneficiary_balance.saturating_sub(inital_beneficiary_balance),
356        );
357
358        Ok(trevm)
359    }
360
361    fn post_bundle(
362        &mut self,
363        _trevm: &EvmNeedsTx<RuDb, SignetEthBundleInsp<RuInsp>>,
364    ) -> Result<(), Self::Error> {
365        Ok(())
366    }
367}