use crate::{outcome::SimulatedItem, SimItem};
use alloy::{
consensus::{SidecarBuilder, SidecarCoder, TxEnvelope},
eips::Decodable2718,
primitives::{keccak256, Bytes, B256},
rlp::Buf,
};
use core::fmt;
use signet_bundle::SignetEthBundle;
use signet_zenith::{encode_txns, Alloy2718Coder};
use std::sync::OnceLock;
use tracing::{error, trace};
#[derive(Clone, Default)]
pub struct BuiltBlock {
pub(crate) host_txns: Vec<Bytes>,
pub(crate) transactions: Vec<TxEnvelope>,
pub(crate) block_number: u64,
pub(crate) gas_used: u64,
pub(crate) host_gas_used: u64,
pub(crate) raw_encoding: OnceLock<Bytes>,
pub(crate) hash: OnceLock<B256>,
}
impl fmt::Debug for BuiltBlock {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BuiltBlock")
.field("transactions", &self.transactions.len())
.field("host_txns", &self.host_txns.len())
.field("gas_used", &self.gas_used)
.field("host_gas_used", &self.host_gas_used)
.field("block_number", &self.block_number)
.finish_non_exhaustive()
}
}
impl BuiltBlock {
pub const fn new(block_number: u64) -> Self {
Self {
host_txns: Vec::new(),
transactions: Vec::new(),
block_number,
gas_used: 0,
host_gas_used: 0,
raw_encoding: OnceLock::new(),
hash: OnceLock::new(),
}
}
pub const fn block_number(&self) -> u64 {
self.block_number
}
pub const fn gas_used(&self) -> u64 {
self.gas_used
}
pub const fn host_gas_used(&self) -> u64 {
self.host_gas_used
}
pub fn tx_count(&self) -> usize {
self.transactions.len()
}
pub fn is_empty(&self) -> bool {
self.transactions.is_empty()
}
#[allow(clippy::missing_const_for_fn)] pub fn transactions(&self) -> &[TxEnvelope] {
&self.transactions
}
#[allow(clippy::missing_const_for_fn)] pub fn host_transactions(&self) -> &[Bytes] {
&self.host_txns
}
pub(crate) fn unseal(&mut self) {
self.raw_encoding.take();
self.hash.take();
}
pub(crate) fn seal(&self) {
self.raw_encoding.get_or_init(|| encode_txns::<Alloy2718Coder>(&self.transactions).into());
self.hash.get_or_init(|| keccak256(self.raw_encoding.get().unwrap().as_ref()));
}
pub fn ingest_tx(&mut self, tx: TxEnvelope) {
trace!(hash = %tx.tx_hash(), "ingesting tx");
self.unseal();
self.transactions.push(tx);
}
pub fn ingest_bundle(&mut self, bundle: SignetEthBundle) {
trace!(replacement_uuid = bundle.replacement_uuid(), "adding bundle to block");
let txs = bundle
.bundle
.txs
.into_iter()
.map(|tx| TxEnvelope::decode_2718(&mut tx.chunk()))
.collect::<Result<Vec<_>, _>>();
if let Ok(txs) = txs {
self.unseal();
self.transactions.extend(txs);
self.host_txns.extend(bundle.host_txs);
} else {
error!("failed to decode bundle. dropping");
}
}
pub fn ingest(&mut self, item: SimulatedItem) {
self.gas_used += item.gas_used;
self.host_gas_used += item.host_gas_used;
match item.item {
SimItem::Bundle(bundle) => self.ingest_bundle(bundle),
SimItem::Tx(tx) => self.ingest_tx(tx),
}
}
pub(crate) fn encode_raw(&self) -> &Bytes {
self.seal();
self.raw_encoding.get().unwrap()
}
pub fn contents_hash(&self) -> &B256 {
self.seal();
self.hash.get().unwrap()
}
pub fn encode_calldata(&self) -> &Bytes {
self.encode_raw()
}
pub fn encode_blob<T: SidecarCoder + Default>(&self) -> SidecarBuilder<T> {
let mut coder = SidecarBuilder::<T>::default();
coder.ingest(self.encode_raw());
coder
}
}