Skip to main content

signet_sim/
built.rs

1use crate::{outcome::SimulatedItem, SimItem};
2use alloy::{
3    consensus::{transaction::Recovered, SidecarBuilder, SidecarCoder, TxEnvelope},
4    primitives::{keccak256, Bytes, B256},
5};
6use core::fmt;
7use signet_bundle::RecoveredBundle;
8use signet_zenith::{encode_txns, Alloy2718Coder};
9use std::sync::{Arc, OnceLock};
10use tracing::trace;
11
12/// A block that has been built by the simulator.
13#[derive(Clone, Default)]
14pub struct BuiltBlock {
15    /// The host transactions to be included in a resulting bundle.
16    pub(crate) host_txns: Vec<Recovered<TxEnvelope>>,
17
18    /// Transactions in the block.
19    pub(crate) transactions: Vec<Recovered<TxEnvelope>>,
20
21    /// The block number for the Signet block.
22    pub(crate) block_number: u64,
23
24    /// The amount of gas used by the block so far
25    pub(crate) gas_used: u64,
26
27    /// The amount of host gas used by the block so far
28    pub(crate) host_gas_used: u64,
29
30    // -- Memoization fields --
31    /// Memoized raw encoding of the block.
32    pub(crate) raw_encoding: OnceLock<Bytes>,
33    /// Memoized hash of the block.
34    pub(crate) hash: OnceLock<B256>,
35}
36
37impl fmt::Debug for BuiltBlock {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        f.debug_struct("BuiltBlock")
40            .field("transactions", &self.transactions.len())
41            .field("host_txns", &self.host_txns.len())
42            .field("gas_used", &self.gas_used)
43            .field("host_gas_used", &self.host_gas_used)
44            .field("block_number", &self.block_number)
45            .finish_non_exhaustive()
46    }
47}
48
49impl BuiltBlock {
50    /// Create a new `BuiltBlock`
51    pub const fn new(block_number: u64) -> Self {
52        Self {
53            host_txns: Vec::new(),
54            transactions: Vec::new(),
55            block_number,
56            gas_used: 0,
57            host_gas_used: 0,
58            raw_encoding: OnceLock::new(),
59            hash: OnceLock::new(),
60        }
61    }
62
63    /// Gets the block number for the block.
64    pub const fn block_number(&self) -> u64 {
65        self.block_number
66    }
67
68    /// Get the amount of gas used by the block.
69    pub const fn gas_used(&self) -> u64 {
70        self.gas_used
71    }
72
73    /// Get the amount of host gas used by the block.
74    pub const fn host_gas_used(&self) -> u64 {
75        self.host_gas_used
76    }
77
78    /// Get the number of transactions in the block.
79    pub const fn tx_count(&self) -> usize {
80        self.transactions.len()
81    }
82
83    /// Check if the block is empty.
84    pub const fn is_empty(&self) -> bool {
85        self.transactions.is_empty()
86    }
87
88    /// Get the current list of transactions included in this block.
89    #[allow(clippy::missing_const_for_fn)] // false positive, const deref
90    pub fn transactions(&self) -> &[Recovered<TxEnvelope>] {
91        &self.transactions
92    }
93
94    /// Get the current list of host transactions included in this block.
95    pub const fn host_transactions(&self) -> &[Recovered<TxEnvelope>] {
96        self.host_txns.as_slice()
97    }
98
99    /// Unseal the block
100    pub(crate) fn unseal(&mut self) {
101        self.raw_encoding.take();
102        self.hash.take();
103    }
104
105    /// Seal the block by encoding the transactions and calculating the hash of
106    /// the block contents.
107    pub(crate) fn seal(&self) {
108        self.raw_encoding.get_or_init(|| {
109            let iter = self.transactions.iter().map(Recovered::inner);
110            encode_txns::<Alloy2718Coder>(iter).into()
111        });
112        self.hash.get_or_init(|| keccak256(self.raw_encoding.get().unwrap().as_ref()));
113    }
114
115    /// Ingest a transaction into the in-progress block.
116    pub fn ingest_tx(&mut self, tx: Recovered<TxEnvelope>) {
117        trace!(hash = %tx.tx_hash(), "ingesting tx");
118        self.unseal();
119        self.transactions.push(tx);
120    }
121
122    /// Ingest a bundle into the in-progress block.
123    /// Ignores Signed Orders for now.
124    pub fn ingest_bundle(&mut self, mut bundle: RecoveredBundle) {
125        trace!(replacement_uuid = bundle.replacement_uuid(), "adding bundle to block");
126
127        self.unseal();
128        // extend the transactions with the decoded transactions.
129        // As this builder does not provide bundles landing "top of block", its fine to just extend.
130        self.transactions.extend(bundle.drain_txns());
131        self.host_txns.extend(bundle.drain_host_txns());
132    }
133
134    /// Ingest a simulated item, extending the block.
135    pub fn ingest(&mut self, item: SimulatedItem) {
136        self.gas_used += item.gas_used;
137        self.host_gas_used += item.host_gas_used;
138
139        match item.item {
140            SimItem::Bundle(bundle) => self.ingest_bundle(Arc::unwrap_or_clone(bundle)),
141            SimItem::Tx(tx) => self.ingest_tx(Arc::unwrap_or_clone(tx)),
142        }
143    }
144
145    /// Encode the in-progress block.
146    pub(crate) fn encode_raw(&self) -> &Bytes {
147        self.seal();
148        self.raw_encoding.get().unwrap()
149    }
150
151    /// Calculate the hash of the in-progress block, finishing the block.
152    pub fn contents_hash(&self) -> &B256 {
153        self.seal();
154        self.hash.get().unwrap()
155    }
156
157    /// Convert the in-progress block to sign request contents.
158    pub fn encode_calldata(&self) -> &Bytes {
159        self.encode_raw()
160    }
161
162    /// Convert the in-progress block to a blob transaction sidecar.
163    pub fn encode_blob<T: SidecarCoder + Default>(&self) -> SidecarBuilder<T> {
164        let mut coder = SidecarBuilder::<T>::default();
165        coder.ingest(self.encode_raw());
166        coder
167    }
168}