use alloy::{
consensus::{BlockHeader, Header, ReceiptEnvelope},
primitives::{B256, B64, U256},
};
pub use signet_constants::test_utils::*;
use signet_evm::ExecutionOutcome;
use signet_extract::{BlockAndReceipts, Extractable};
use signet_types::primitives::{RecoveredBlock, SealedBlock, SealedHeader};
#[derive(Clone, PartialEq, Eq)]
pub struct Chain {
blocks: Vec<RecoveredBlock>,
execution_outcome: ExecutionOutcome,
}
impl core::fmt::Debug for Chain {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Chain").field("blocks", &self.blocks.len()).finish_non_exhaustive()
}
}
impl Chain {
pub fn from_block(block: RecoveredBlock, execution_outcome: ExecutionOutcome) -> Self {
Self { blocks: vec![block], execution_outcome }
}
pub fn append_block(&mut self, block: RecoveredBlock, outcome: ExecutionOutcome) {
self.blocks.push(block);
self.execution_outcome.append(outcome);
}
pub fn blocks(&self) -> &[RecoveredBlock] {
&self.blocks
}
pub fn execution_outcome(&self) -> &ExecutionOutcome {
&self.execution_outcome
}
pub fn into_parts(self) -> (Vec<RecoveredBlock>, ExecutionOutcome) {
(self.blocks, self.execution_outcome)
}
}
impl Extractable for Chain {
type Block = RecoveredBlock;
type Receipt = ReceiptEnvelope;
fn blocks_and_receipts(
&self,
) -> impl Iterator<Item = BlockAndReceipts<'_, Self::Block, Self::Receipt>> {
self.blocks
.iter()
.zip(self.execution_outcome.receipts().iter())
.map(|(block, receipts)| BlockAndReceipts { block, receipts })
}
fn first_number(&self) -> u64 {
self.blocks.first().expect("Chain must be non-empty").number()
}
fn tip_number(&self) -> u64 {
self.blocks.last().expect("Chain must be non-empty").number()
}
fn len(&self) -> usize {
self.blocks.len()
}
}
pub fn fake_chain(count: u64) -> Chain {
assert!(count > 0, "fake_chain requires at least one block");
let blocks: Vec<_> = (0..count).map(fake_block).collect();
let receipts = vec![vec![]; count as usize];
let execution_outcome = ExecutionOutcome::new(Default::default(), receipts, 0);
Chain { blocks, execution_outcome }
}
pub fn fake_block(number: u64) -> RecoveredBlock {
let header = Header {
difficulty: U256::from(0x4000_0000),
number,
mix_hash: B256::repeat_byte(0xed),
nonce: B64::repeat_byte(0xbe),
timestamp: 1716555576, excess_blob_gas: Some(0),
..Default::default()
};
let sealed = SealedHeader::new(header);
SealedBlock::new(sealed, vec![]).recover_unchecked(vec![])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "fake_chain requires at least one block")]
fn fake_chain_rejects_zero() {
fake_chain(0);
}
#[test]
fn single_block_metadata() {
let chain = fake_chain(1);
assert_eq!(chain.len(), 1);
assert_eq!(chain.first_number(), 0);
assert_eq!(chain.tip_number(), 0);
}
#[test]
fn multi_block_metadata() {
let chain = fake_chain(5);
assert_eq!(chain.len(), 5);
assert_eq!(chain.first_number(), 0);
assert_eq!(chain.tip_number(), 4);
}
}