1use crate::{outcome::SimulatedItem, SimItem};
2use alloy::{
3 consensus::{SidecarBuilder, SidecarCoder, TxEnvelope},
4 eips::Decodable2718,
5 primitives::{keccak256, Bytes, B256},
6 rlp::Buf,
7};
8use core::fmt;
9use signet_bundle::SignetEthBundle;
10use signet_types::SignedFill;
11use signet_zenith::{encode_txns, Alloy2718Coder};
12use std::sync::OnceLock;
13use tracing::{error, trace};
14
15#[derive(Clone, Default)]
17pub struct BuiltBlock {
18 pub(crate) host_fills: Vec<SignedFill>,
20 pub(crate) transactions: Vec<TxEnvelope>,
22 pub(crate) block_number: u64,
24
25 pub(crate) gas_used: u64,
27
28 pub(crate) raw_encoding: OnceLock<Bytes>,
30 pub(crate) hash: OnceLock<B256>,
32}
33
34impl fmt::Debug for BuiltBlock {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 f.debug_struct("BuiltBlock")
37 .field("host_fills", &self.host_fills.len())
38 .field("transactions", &self.transactions.len())
39 .field("gas_used", &self.gas_used)
40 .field("block_number", &self.block_number)
41 .finish_non_exhaustive()
42 }
43}
44
45impl BuiltBlock {
46 pub const fn new(block_number: u64) -> Self {
48 Self {
49 host_fills: Vec::new(),
50 transactions: Vec::new(),
51 block_number,
52 gas_used: 0,
53 raw_encoding: OnceLock::new(),
54 hash: OnceLock::new(),
55 }
56 }
57
58 pub const fn block_number(&self) -> u64 {
60 self.block_number
61 }
62
63 pub const fn gas_used(&self) -> u64 {
65 self.gas_used
66 }
67
68 pub fn tx_count(&self) -> usize {
70 self.transactions.len()
71 }
72
73 pub fn is_empty(&self) -> bool {
75 self.transactions.is_empty()
76 }
77
78 #[allow(clippy::missing_const_for_fn)] pub fn transactions(&self) -> &[TxEnvelope] {
81 &self.transactions
82 }
83
84 #[allow(clippy::missing_const_for_fn)] pub fn host_fills(&self) -> &[SignedFill] {
87 &self.host_fills
88 }
89
90 pub(crate) fn unseal(&mut self) {
92 self.raw_encoding.take();
93 self.hash.take();
94 }
95
96 pub(crate) fn seal(&self) {
99 self.raw_encoding.get_or_init(|| encode_txns::<Alloy2718Coder>(&self.transactions).into());
100 self.hash.get_or_init(|| keccak256(self.raw_encoding.get().unwrap().as_ref()));
101 }
102
103 pub fn ingest_tx(&mut self, tx: TxEnvelope) {
105 trace!(hash = %tx.tx_hash(), "ingesting tx");
106 self.unseal();
107 self.transactions.push(tx);
108 }
109
110 pub fn ingest_bundle(&mut self, bundle: SignetEthBundle) {
113 trace!(replacement_uuid = bundle.replacement_uuid(), "adding bundle to block");
114
115 let txs = bundle
116 .bundle
117 .txs
118 .into_iter()
119 .map(|tx| TxEnvelope::decode_2718(&mut tx.chunk()))
120 .collect::<Result<Vec<_>, _>>();
121
122 if let Ok(txs) = txs {
123 self.unseal();
124 self.transactions.extend(txs);
127
128 if let Some(host_fills) = bundle.host_fills {
129 self.host_fills.push(host_fills);
130 }
131 } else {
132 error!("failed to decode bundle. dropping");
133 }
134 }
135
136 pub fn ingest(&mut self, item: SimulatedItem) {
138 self.gas_used += item.gas_used;
139
140 match item.item {
141 SimItem::Bundle(bundle) => self.ingest_bundle(bundle),
142 SimItem::Tx(tx) => self.ingest_tx(tx),
143 }
144 }
145
146 pub(crate) fn encode_raw(&self) -> &Bytes {
148 self.seal();
149 self.raw_encoding.get().unwrap()
150 }
151
152 pub fn contents_hash(&self) -> &B256 {
154 self.seal();
155 self.hash.get().unwrap()
156 }
157
158 pub fn encode_calldata(&self) -> &Bytes {
160 self.encode_raw()
161 }
162
163 pub fn encode_blob<T: SidecarCoder + Default>(&self) -> SidecarBuilder<T> {
165 let mut coder = SidecarBuilder::<T>::default();
166 coder.ingest(self.encode_raw());
167 coder
168 }
169}