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#[derive(Clone, Default)]
14pub struct BuiltBlock {
15 pub(crate) host_txns: Vec<Recovered<TxEnvelope>>,
17
18 pub(crate) transactions: Vec<Recovered<TxEnvelope>>,
20
21 pub(crate) block_number: u64,
23
24 pub(crate) gas_used: u64,
26
27 pub(crate) host_gas_used: u64,
29
30 pub(crate) raw_encoding: OnceLock<Bytes>,
33 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 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 pub const fn block_number(&self) -> u64 {
65 self.block_number
66 }
67
68 pub const fn gas_used(&self) -> u64 {
70 self.gas_used
71 }
72
73 pub const fn host_gas_used(&self) -> u64 {
75 self.host_gas_used
76 }
77
78 pub const fn tx_count(&self) -> usize {
80 self.transactions.len()
81 }
82
83 pub const fn is_empty(&self) -> bool {
85 self.transactions.is_empty()
86 }
87
88 #[allow(clippy::missing_const_for_fn)] pub fn transactions(&self) -> &[Recovered<TxEnvelope>] {
91 &self.transactions
92 }
93
94 pub const fn host_transactions(&self) -> &[Recovered<TxEnvelope>] {
96 self.host_txns.as_slice()
97 }
98
99 pub(crate) fn unseal(&mut self) {
101 self.raw_encoding.take();
102 self.hash.take();
103 }
104
105 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 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 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 self.transactions.extend(bundle.drain_txns());
131 self.host_txns.extend(bundle.drain_host_txns());
132 }
133
134 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 pub(crate) fn encode_raw(&self) -> &Bytes {
147 self.seal();
148 self.raw_encoding.get().unwrap()
149 }
150
151 pub fn contents_hash(&self) -> &B256 {
153 self.seal();
154 self.hash.get().unwrap()
155 }
156
157 pub fn encode_calldata(&self) -> &Bytes {
159 self.encode_raw()
160 }
161
162 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}