signet_evm/
driver.rs

1use crate::{
2    orders::SignetInspector, BlockResult, EvmNeedsTx, EvmTransacted, ExecutionOutcome, RunTxResult,
3    SignetLayered,
4};
5use alloy::{
6    consensus::{
7        transaction::SignerRecoverable, BlockHeader, Header, ReceiptEnvelope, Transaction as _,
8        TxType,
9    },
10    eips::eip1559::{BaseFeeParams, INITIAL_BASE_FEE as EIP1559_INITIAL_BASE_FEE},
11    primitives::{map::HashSet, Address, Bloom, U256},
12};
13use signet_extract::{Extractable, Extracts};
14use signet_types::{
15    constants::SignetSystemConstants,
16    primitives::{BlockBody, RecoveredBlock, SealedBlock, SealedHeader, TransactionSigned},
17    AggregateFills, MarketError,
18};
19use std::collections::VecDeque;
20use tracing::{debug, debug_span, info_span, warn};
21use trevm::{
22    helpers::Ctx,
23    revm::{
24        context::{
25            result::{EVMError, ExecutionResult},
26            BlockEnv, CfgEnv, ContextTr, TransactTo, TxEnv,
27        },
28        context_interface::block::BlobExcessGasAndPrice,
29        database::State,
30        Database, DatabaseCommit, Inspector,
31    },
32    trevm_try, Block, BlockDriver, BlockOutput, Cfg, Tx,
33};
34
35/// Used internally to signal that the transaction should be discarded.
36pub(crate) enum ControlFlow<Db, Insp>
37where
38    Db: Database + DatabaseCommit,
39    Insp: Inspector<Ctx<Db>>,
40{
41    Discard(EvmNeedsTx<Db, Insp>),
42    Keep(EvmTransacted<Db, Insp>),
43}
44
45#[derive(thiserror::Error)]
46pub enum SignetDriverError<Db>
47where
48    Db: Database,
49{
50    /// A market error occurred.
51    #[error("Market error: {0}")]
52    MarketError(#[from] MarketError),
53    /// An EVM error occurred.
54    #[error("EVM error")]
55    EVMError(EVMError<Db::Error>),
56}
57
58impl<Db: Database> std::fmt::Debug for SignetDriverError<Db> {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        match self {
61            Self::MarketError(arg0) => f.debug_tuple("MarketError").field(arg0).finish(),
62            Self::EVMError(_) => f.debug_tuple("EVMError").finish(),
63        }
64    }
65}
66
67impl<Db> From<EVMError<Db::Error>> for SignetDriverError<Db>
68where
69    Db: Database,
70{
71    fn from(e: EVMError<Db::Error>) -> Self {
72        Self::EVMError(e)
73    }
74}
75
76/// Shim to impl [`Tx`] for [`TransactionSigned`]
77#[derive(Debug)]
78struct FillShim<'a>(&'a TransactionSigned, Address);
79
80impl Tx for FillShim<'_> {
81    fn fill_tx_env(&self, tx_env: &mut TxEnv) {
82        let TxEnv {
83            tx_type,
84            caller,
85            gas_limit,
86            gas_price,
87            kind,
88            value,
89            data,
90            nonce,
91            chain_id,
92            access_list,
93            gas_priority_fee,
94            blob_hashes,
95            max_fee_per_blob_gas,
96            authorization_list,
97        } = tx_env;
98
99        *caller = self.1;
100
101        match self.0 {
102            TransactionSigned::Legacy(tx) => {
103                let tx = tx.tx();
104                *tx_type = TxType::Legacy as u8;
105                *gas_limit = tx.gas_limit;
106                *gas_price = tx.gas_price;
107                *gas_priority_fee = None;
108                *kind = tx.to;
109                *value = tx.value;
110                *data = tx.input.clone();
111                *chain_id = tx.chain_id;
112                *nonce = tx.nonce;
113                access_list.0.clear();
114                blob_hashes.clear();
115                *max_fee_per_blob_gas = 0;
116                authorization_list.clear();
117            }
118            TransactionSigned::Eip2930(tx) => {
119                let tx = tx.tx();
120                *tx_type = TxType::Eip2930 as u8;
121                *gas_limit = tx.gas_limit;
122                *gas_price = tx.gas_price;
123                *gas_priority_fee = None;
124                *kind = tx.to;
125                *value = tx.value;
126                *data = tx.input.clone();
127                *chain_id = Some(tx.chain_id);
128                *nonce = tx.nonce;
129                access_list.clone_from(&tx.access_list);
130                blob_hashes.clear();
131                *max_fee_per_blob_gas = 0;
132                authorization_list.clear();
133            }
134            TransactionSigned::Eip1559(tx) => {
135                let tx = tx.tx();
136                *tx_type = TxType::Eip1559 as u8;
137                *gas_limit = tx.gas_limit;
138                *gas_price = tx.max_fee_per_gas;
139                *gas_priority_fee = Some(tx.max_priority_fee_per_gas);
140                *kind = tx.to;
141                *value = tx.value;
142                *data = tx.input.clone();
143                *chain_id = Some(tx.chain_id);
144                *nonce = tx.nonce;
145                access_list.clone_from(&tx.access_list);
146                blob_hashes.clear();
147                *max_fee_per_blob_gas = 0;
148                authorization_list.clear();
149            }
150            TransactionSigned::Eip4844(tx) => {
151                let tx = tx.tx();
152                *tx_type = TxType::Eip4844 as u8;
153                *gas_limit = tx.gas_limit;
154                *gas_price = tx.max_fee_per_gas;
155                *gas_priority_fee = Some(tx.max_priority_fee_per_gas);
156                *kind = TransactTo::Call(tx.to);
157                *value = tx.value;
158                *data = tx.input.clone();
159                *chain_id = Some(tx.chain_id);
160                *nonce = tx.nonce;
161                access_list.clone_from(&tx.access_list);
162                blob_hashes.clone_from(&tx.blob_versioned_hashes);
163                *max_fee_per_blob_gas = tx.max_fee_per_blob_gas;
164                authorization_list.clear();
165            }
166            TransactionSigned::Eip7702(tx) => {
167                let tx = tx.tx();
168                *tx_type = TxType::Eip7702 as u8;
169                *gas_limit = tx.gas_limit;
170                *gas_price = tx.max_fee_per_gas;
171                *gas_priority_fee = Some(tx.max_priority_fee_per_gas);
172                *kind = tx.to.into();
173                *value = tx.value;
174                *data = tx.input.clone();
175                *chain_id = Some(tx.chain_id);
176                *nonce = tx.nonce;
177                access_list.clone_from(&tx.access_list);
178                blob_hashes.clear();
179                *max_fee_per_blob_gas = 0;
180                authorization_list.clone_from(
181                    &tx.authorization_list
182                        .iter()
183                        .cloned()
184                        .map(alloy::signers::Either::Left)
185                        .collect(),
186                );
187            }
188        }
189    }
190}
191
192/// A driver for the Signet EVM
193#[derive(Debug)]
194pub struct SignetDriver<'a, 'b, C: Extractable> {
195    /// The block extracts.
196    pub(crate) extracts: &'a Extracts<'b, C>,
197
198    /// Set of addresses that generated transact events and should be aliased
199    /// because they contain code.
200    pub(crate) to_alias: HashSet<Address>,
201
202    /// Parent rollup block.
203    parent: SealedHeader,
204
205    /// Rollup constants, including pre-deploys
206    pub(crate) constants: SignetSystemConstants,
207
208    /// The working context is a clone of the block's [`AggregateFills`] that
209    /// is updated progessively as the block is evaluated.
210    working_context: AggregateFills,
211
212    /// Transactions in the RU block (if any)
213    to_process: VecDeque<TransactionSigned>,
214
215    /// Transactions that have been processed.
216    pub(crate) processed: Vec<TransactionSigned>,
217
218    /// Receipts and senders.
219    pub(crate) output: BlockOutput,
220
221    /// Payable gas used in the block.
222    payable_gas_used: u64,
223}
224
225impl<'a, 'b, C: Extractable> SignetDriver<'a, 'b, C> {
226    /// Create a new driver.
227    pub fn new(
228        extracts: &'a Extracts<'b, C>,
229        to_alias: HashSet<Address>,
230        to_process: VecDeque<TransactionSigned>,
231        parent: SealedHeader,
232        constants: SignetSystemConstants,
233    ) -> Self {
234        let cap = to_process.len()
235            + extracts.transacts.len()
236            + extracts.enters.len()
237            + extracts.enter_tokens.len();
238        Self {
239            extracts,
240            to_alias,
241            parent,
242            constants,
243            working_context: extracts.aggregate_fills(),
244            to_process,
245            processed: Vec::with_capacity(cap),
246            output: BlockOutput::with_capacity(cap),
247            payable_gas_used: 0,
248        }
249    }
250
251    /// Get the extracts being executed by the driver.
252    pub const fn extracts(&self) -> &Extracts<'b, C> {
253        self.extracts
254    }
255
256    /// Get the parent header.
257    pub const fn parent(&self) -> &SealedHeader {
258        &self.parent
259    }
260
261    /// Get the system constants.
262    pub const fn constants(&self) -> &SignetSystemConstants {
263        &self.constants
264    }
265
266    /// Get the rollup height of the current block.
267    pub const fn ru_height(&self) -> u64 {
268        self.extracts.ru_height
269    }
270
271    /// beneficiary of the current block.
272    pub fn beneficiary(&self) -> Address {
273        self.extracts.ru_header().map(|h| h.rewardAddress).unwrap_or(self.parent.beneficiary())
274    }
275
276    /// Base fee beneficiary of the current block.
277    pub const fn base_fee_recipient(&self) -> Address {
278        self.constants.base_fee_recipient()
279    }
280
281    /// Gas limit of the current block.
282    pub fn gas_limit(&self) -> u64 {
283        self.extracts.ru_header().map(|h| h.gas_limit()).unwrap_or(self.parent.gas_limit())
284    }
285
286    /// Base fee of the current block.
287    pub fn base_fee(&self) -> u64 {
288        if self.ru_height() == 0 {
289            // case should never occur.
290            EIP1559_INITIAL_BASE_FEE
291        } else {
292            self.parent
293                .next_block_base_fee(BaseFeeParams::ethereum())
294                .unwrap_or(EIP1559_INITIAL_BASE_FEE)
295        }
296    }
297
298    /// Get the cumulative gas used in the block (so far). This excludes gas
299    /// used by enters and enter token events.
300    pub const fn payable_gas_used(&self) -> u64 {
301        self.payable_gas_used
302    }
303
304    /// Get the cumulative gas used in the block, including the gas used by
305    /// enters and enter token events.
306    pub fn cumulative_gas_used(&self) -> u64 {
307        self.output.cumulative_gas_used()
308    }
309
310    /// Consume the driver, producing the sealed block and receipts.
311    pub fn finish(self) -> (RecoveredBlock, Vec<ReceiptEnvelope>) {
312        let header = self.construct_sealed_header();
313        let (receipts, senders, _) = self.output.into_parts();
314
315        let body = BlockBody { transactions: self.processed, ommers: vec![], withdrawals: None };
316        let block = SealedBlock { header, body };
317        let block = RecoveredBlock::new(block, senders);
318
319        (block, receipts)
320    }
321
322    /// Consume the driver and trevm, producing a [`BlockResult`].
323    pub fn finish_trevm<Db, Insp>(self, trevm: crate::EvmNeedsBlock<State<Db>, Insp>) -> BlockResult
324    where
325        Db: Database,
326        Insp: Inspector<Ctx<State<Db>>>,
327    {
328        let ru_height = self.extracts.ru_height;
329        let host_height = self.extracts.host_block.number();
330        let (sealed_block, receipts) = self.finish();
331        BlockResult {
332            host_height,
333            sealed_block,
334            execution_outcome: ExecutionOutcome::new(trevm.finish(), vec![receipts], ru_height),
335        }
336    }
337
338    /// Get the logs bloom of the block.
339    fn logs_bloom(&self) -> Bloom {
340        self.output.logs_bloom()
341    }
342
343    /// Make a receipt from the execution result.
344    fn make_receipt(&self, result: ExecutionResult) -> alloy::consensus::Receipt {
345        let cumulative_gas_used = self.cumulative_gas_used().saturating_add(result.gas_used());
346        alloy::consensus::Receipt {
347            status: result.is_success().into(),
348            cumulative_gas_used,
349            logs: result.into_logs(),
350        }
351    }
352
353    /// Construct a block header for DB and evm execution.
354    fn construct_header(&self) -> Header {
355        Header {
356            parent_hash: self.parent.hash(),
357            number: self.ru_height(),
358            gas_limit: self.gas_limit(),
359            timestamp: self.extracts.host_block.timestamp(),
360            base_fee_per_gas: Some(self.base_fee()),
361            beneficiary: self.beneficiary(),
362
363            logs_bloom: self.logs_bloom(),
364            gas_used: self.cumulative_gas_used(),
365
366            difficulty: self.extracts.host_block.difficulty(),
367
368            mix_hash: self.extracts.host_block.mix_hash().unwrap_or_default(),
369            nonce: self.extracts.host_block.nonce().unwrap_or_default(),
370            parent_beacon_block_root: self.extracts.host_block.parent_beacon_block_root(),
371
372            ..Default::default()
373        }
374    }
375
376    /// Construct a sealed header for DB and evm execution.
377    fn construct_sealed_header(&self) -> SealedHeader {
378        let header = self.construct_header();
379        SealedHeader::new(header)
380    }
381
382    /// Check the [`AggregateFills`], discard if invalid, otherwise accumulate
383    /// payable gas and call [`Self::accept_tx`].
384    ///
385    /// This path is used by
386    /// - [`TransactionSigned`] objects
387    /// - [`Transactor::Transact`] events
388    pub(crate) fn check_fills_and_accept<Db, Insp>(
389        &mut self,
390        mut trevm: EvmTransacted<Db, Insp>,
391        tx: TransactionSigned,
392    ) -> RunTxResult<Self, Db, Insp>
393    where
394        Db: Database + DatabaseCommit,
395        Insp: Inspector<Ctx<Db>>,
396    {
397        // Taking these clears the context for reuse.
398        let (agg_fills, agg_orders) =
399            trevm.inner_mut_unchecked().inspector.as_mut_detector().take_aggregates();
400
401        // We check the AggregateFills here, and if it fails, we discard the
402        // transaction outcome and push a failure receipt.
403        if let Err(err) = self.working_context.checked_remove_ru_tx_events(&agg_fills, &agg_orders)
404        {
405            debug!(%err, "Discarding transaction outcome due to market error");
406            return Ok(trevm.reject());
407        }
408
409        // We track this separately from the cumulative gas used. Enters and
410        // EnterTokens are not payable, so we don't want to include their gas
411        // usage in the payable gas used.
412        self.payable_gas_used += trevm.result().gas_used();
413
414        Ok(self.accept_tx(trevm, tx))
415    }
416
417    /// Accept the state changes and produce a receipt. Push the receipt to the
418    /// block.
419    ///
420    /// This path is used by
421    /// - [`TransactionSigned`] objects
422    /// - [`Transactor::Transact`] events
423    /// - [`Passage::Enter`] events
424    ///
425    /// [`Passage::Enter`]: signet_zenith::Passage::Enter
426    pub(crate) fn accept_tx<Db, Insp>(
427        &mut self,
428        trevm: EvmTransacted<Db, Insp>,
429        tx: TransactionSigned,
430    ) -> EvmNeedsTx<Db, Insp>
431    where
432        Db: Database + DatabaseCommit,
433        Insp: Inspector<Ctx<Db>>,
434    {
435        // Push the transaction to the block.
436        self.processed.push(tx);
437        // Accept the result.
438        let (result, trevm) = trevm.accept();
439
440        // Create a receipt for the transaction.
441        let tx_env = trevm.inner().ctx.tx();
442        let sender = tx_env.caller;
443        // 4844 transactions are not allowed
444        let receipt = self.make_receipt(result).into();
445        let receipt = if tx_env.gas_priority_fee.is_some() {
446            ReceiptEnvelope::Eip1559(receipt)
447        } else if !tx_env.access_list.is_empty() {
448            ReceiptEnvelope::Eip2930(receipt)
449        } else {
450            ReceiptEnvelope::Legacy(receipt)
451        };
452
453        self.output.push_result(receipt, sender);
454        trevm
455    }
456
457    /// Execute a transaction.
458    ///
459    /// This function does the following:
460    /// - Recover the signer of the transaction.
461    /// - Run the transaction.
462    /// - Check the [`AggregateFills`].
463    /// - Create a receipt.
464    fn execute_transaction<Db, Insp>(
465        &mut self,
466        mut trevm: EvmNeedsTx<Db, Insp>,
467        tx: TransactionSigned,
468    ) -> RunTxResult<Self, Db, Insp>
469    where
470        Db: Database + DatabaseCommit,
471        Insp: Inspector<Ctx<Db>>,
472    {
473        // We set up the span here so that tx details are captured in the event
474        // of signature recovery failure.
475        let s =
476        debug_span!("signet::evm::execute_transaction", tx_hash = %tx.hash(), sender = tracing::field::Empty, nonce = tx.nonce())
477                .entered();
478
479        if let Ok(sender) = tx.recover_signer() {
480            s.record("sender", sender.to_string());
481            // Run the tx, returning from this function if there is a tx error
482            let t = run_tx_early_return!(self, trevm, &FillShim(&tx, sender), sender);
483            trevm = self.check_fills_and_accept(t, tx)?;
484        } else {
485            warn!("Failed to recover signer for transaction");
486        }
487        Ok(trevm)
488    }
489
490    /// Execute all transactions. This is run before enters and transacts
491    fn execute_all_transactions<Db, Insp>(
492        &mut self,
493        mut trevm: EvmNeedsTx<Db, Insp>,
494    ) -> RunTxResult<Self, Db, Insp>
495    where
496        Db: Database + DatabaseCommit,
497        Insp: Inspector<Ctx<Db>>,
498    {
499        while let Some(tx) = self.to_process.pop_front() {
500            if tx.is_eip4844() {
501                warn!("EIP-4844 transactions are not allowed in Signet blocks");
502                continue;
503            }
504            trevm = self.execute_transaction(trevm, tx)?;
505        }
506
507        Ok(trevm)
508    }
509
510    /// Clear the balance of the rollup passage. This is run at the end of the
511    /// block, after all transactions, enters, and transact events have been
512    /// processed. It ensures that ETH sent to the rollup passage is burned,
513    /// and before the base fee is credited.
514    fn clear_ru_passage_balance<Db, Insp>(
515        &self,
516        mut trevm: EvmNeedsTx<Db, Insp>,
517    ) -> RunTxResult<Self, Db, Insp>
518    where
519        Db: Database + DatabaseCommit,
520        Insp: Inspector<Ctx<Db>>,
521    {
522        // Zero the balance of the rollup passage (deleting any exited ETH).
523        match trevm.try_set_balance_unchecked(self.constants.rollup().passage(), U256::ZERO) {
524            Ok(eth_burned) => debug!(%eth_burned, "Zeroed rollup passage balance"),
525            Err(e) => return Err(trevm.errored(EVMError::Database(e).into())),
526        }
527        Ok(trevm)
528    }
529
530    /// Credit the base fee to the base fee beneficiary. This is run at the end
531    /// of the block, after all transactions, enters, and transact events have
532    /// been processed, and after the rollup passage balance has been cleared.
533    fn credit_base_fee<Db, Insp>(
534        &mut self,
535        mut trevm: EvmNeedsTx<Db, Insp>,
536        gas_used: u64,
537    ) -> RunTxResult<Self, Db, Insp>
538    where
539        Db: Database + DatabaseCommit,
540        Insp: Inspector<Ctx<Db>>,
541    {
542        // We subtract the fake gas used for enters here. This
543        // gives us the gas used for transactions and transact events.
544        let base_fee = self.base_fee();
545        let amount = U256::from(gas_used) * U256::from(base_fee);
546
547        if amount.is_zero() {
548            debug!(%amount, gas_used, base_fee, recipient = %self.base_fee_recipient(), "No base fee to credit");
549            return Ok(trevm);
550        }
551
552        debug!(%amount, gas_used, base_fee, recipient = %self.base_fee_recipient(), "Crediting base fee");
553
554        trevm_try!(
555            trevm
556                .try_increase_balance_unchecked(self.base_fee_recipient(), amount)
557                .map_err(EVMError::Database),
558            trevm
559        );
560
561        Ok(trevm)
562    }
563}
564
565impl<C: Extractable> Cfg for SignetDriver<'_, '_, C> {
566    fn fill_cfg_env(&self, cfg_env: &mut CfgEnv) {
567        cfg_env.chain_id = self.extracts.chain_id;
568    }
569}
570
571impl<Db, Insp, C: Extractable> BlockDriver<Db, SignetLayered<Insp>> for SignetDriver<'_, '_, C>
572where
573    Db: Database + DatabaseCommit,
574    Insp: Inspector<Ctx<Db>>,
575{
576    type Block = Self;
577
578    type Error = SignetDriverError<Db>;
579
580    fn block(&self) -> &Self::Block {
581        self
582    }
583
584    fn run_txns(&mut self, mut trevm: EvmNeedsTx<Db, Insp>) -> RunTxResult<Self, Db, Insp> {
585        let _span = info_span!(
586            "SignetDriver::run_txns",
587            txn_count = self.to_process.len(),
588            enter_count = self.extracts.enters.len(),
589            enter_token_count = self.extracts.enter_tokens.len(),
590            transact_count = self.extracts.transacts.len(),
591            base_fee_beneficiary = %self.constants.base_fee_recipient(),
592            rollup_passage = %self.constants.rollup().passage(),
593            parent_hash = %self.parent.hash(),
594        )
595        .entered();
596
597        // NB:
598        // The signet block lifecycle is roughly as follows:
599        // - Execute the builder-created block by executing each transaction in
600        //   order.
601        // - Process each enter event in order.
602        // - Process each enter token event in order.
603        // - Run system built-in application logic
604        // - Process each transact event in order.
605        // - Set the balance of the rollup passage to zero.
606        // - Credit the basefee to the basefee beneficiary.
607
608        // Run the transactions.
609        // Transaction gas is metered, and pays basefee
610        trevm = self.execute_all_transactions(trevm)?;
611
612        // Run all Enter and EnterToken events
613        // Gas is unmetered, and does not pay basefee
614        trevm = self.run_all_mints(trevm)?;
615
616        // Run the transact events.
617        // Transact gas is metered, and pays basefee
618        trevm = self.execute_all_transacts(trevm)?;
619
620        // Clear the balance of the rollup passage.
621        trevm = self.clear_ru_passage_balance(trevm)?;
622
623        // Credit the basefee to the basefee beneficiary. This is the sum
624        // of the basefee for the transactions and transact events.
625        self.credit_base_fee(trevm, self.payable_gas_used())
626    }
627
628    fn post_block(&mut self, _trevm: &crate::EvmNeedsBlock<Db, Insp>) -> Result<(), Self::Error> {
629        Ok(())
630    }
631}
632
633impl<C: Extractable> Block for SignetDriver<'_, '_, C> {
634    fn fill_block_env(&self, block_env: &mut BlockEnv) {
635        let BlockEnv {
636            number,
637            beneficiary,
638            timestamp,
639            gas_limit,
640            basefee,
641            difficulty,
642            prevrandao,
643            blob_excess_gas_and_price,
644        } = block_env;
645        *number = U256::from(self.ru_height());
646        *beneficiary = self.beneficiary();
647        *timestamp = U256::from(self.extracts.host_block.timestamp());
648        *gas_limit = self.gas_limit();
649        *basefee = self.base_fee();
650        *difficulty = self.extracts.host_block.difficulty();
651        *prevrandao = self.extracts.host_block.mix_hash();
652        *blob_excess_gas_and_price =
653            Some(BlobExcessGasAndPrice { excess_blob_gas: 0, blob_gasprice: 0 });
654    }
655}