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
35pub(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 #[error("Market error: {0}")]
52 MarketError(#[from] MarketError),
53 #[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#[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#[derive(Debug)]
194pub struct SignetDriver<'a, 'b, C: Extractable> {
195 pub(crate) extracts: &'a Extracts<'b, C>,
197
198 pub(crate) to_alias: HashSet<Address>,
201
202 parent: SealedHeader,
204
205 pub(crate) constants: SignetSystemConstants,
207
208 working_context: AggregateFills,
211
212 to_process: VecDeque<TransactionSigned>,
214
215 pub(crate) processed: Vec<TransactionSigned>,
217
218 pub(crate) output: BlockOutput,
220
221 payable_gas_used: u64,
223}
224
225impl<'a, 'b, C: Extractable> SignetDriver<'a, 'b, C> {
226 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 pub const fn extracts(&self) -> &Extracts<'b, C> {
253 self.extracts
254 }
255
256 pub const fn parent(&self) -> &SealedHeader {
258 &self.parent
259 }
260
261 pub const fn constants(&self) -> &SignetSystemConstants {
263 &self.constants
264 }
265
266 pub const fn ru_height(&self) -> u64 {
268 self.extracts.ru_height
269 }
270
271 pub fn beneficiary(&self) -> Address {
273 self.extracts.ru_header().map(|h| h.rewardAddress).unwrap_or(self.parent.beneficiary())
274 }
275
276 pub const fn base_fee_recipient(&self) -> Address {
278 self.constants.base_fee_recipient()
279 }
280
281 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 pub fn base_fee(&self) -> u64 {
288 if self.ru_height() == 0 {
289 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 pub const fn payable_gas_used(&self) -> u64 {
301 self.payable_gas_used
302 }
303
304 pub fn cumulative_gas_used(&self) -> u64 {
307 self.output.cumulative_gas_used()
308 }
309
310 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 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 fn logs_bloom(&self) -> Bloom {
340 self.output.logs_bloom()
341 }
342
343 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 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 fn construct_sealed_header(&self) -> SealedHeader {
378 let header = self.construct_header();
379 SealedHeader::new(header)
380 }
381
382 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 let (agg_fills, agg_orders) =
399 trevm.inner_mut_unchecked().inspector.as_mut_detector().take_aggregates();
400
401 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 self.payable_gas_used += trevm.result().gas_used();
413
414 Ok(self.accept_tx(trevm, tx))
415 }
416
417 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 self.processed.push(tx);
437 let (result, trevm) = trevm.accept();
439
440 let tx_env = trevm.inner().ctx.tx();
442 let sender = tx_env.caller;
443 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 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 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 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 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 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 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 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 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 trevm = self.execute_all_transactions(trevm)?;
611
612 trevm = self.run_all_mints(trevm)?;
615
616 trevm = self.execute_all_transacts(trevm)?;
619
620 trevm = self.clear_ru_passage_balance(trevm)?;
622
623 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}