Skip to main content

signet_test_utils/
chain.rs

1use alloy::consensus::{BlockHeader, Header, ReceiptEnvelope};
2pub use signet_constants::test_utils::*;
3use signet_evm::ExecutionOutcome;
4use signet_extract::{BlockAndReceipts, Extractable};
5use signet_types::primitives::{RecoveredBlock, SealedBlock, SignetHeaderV1};
6
7/// A simple, non-empty chain of blocks with receipts.
8#[derive(Clone, PartialEq, Eq)]
9pub struct Chain {
10    /// The blocks. Invariant: always non-empty.
11    blocks: Vec<RecoveredBlock>,
12    execution_outcome: ExecutionOutcome,
13}
14
15impl core::fmt::Debug for Chain {
16    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
17        f.debug_struct("Chain").field("blocks", &self.blocks.len()).finish_non_exhaustive()
18    }
19}
20
21impl Chain {
22    /// Create a new chain from a single block.
23    pub fn from_block(block: RecoveredBlock, execution_outcome: ExecutionOutcome) -> Self {
24        Self { blocks: vec![block], execution_outcome }
25    }
26
27    /// Append a block to the chain.
28    pub fn append_block(&mut self, block: RecoveredBlock, outcome: ExecutionOutcome) {
29        self.blocks.push(block);
30        self.execution_outcome.append(outcome);
31    }
32
33    /// Get the blocks in the chain.
34    pub fn blocks(&self) -> &[RecoveredBlock] {
35        &self.blocks
36    }
37
38    /// Get the execution outcome.
39    pub fn execution_outcome(&self) -> &ExecutionOutcome {
40        &self.execution_outcome
41    }
42
43    /// Decompose the chain into its constituent parts.
44    pub fn into_parts(self) -> (Vec<RecoveredBlock>, ExecutionOutcome) {
45        (self.blocks, self.execution_outcome)
46    }
47}
48
49impl Extractable for Chain {
50    type Block = RecoveredBlock;
51    type Receipt = ReceiptEnvelope;
52
53    fn blocks_and_receipts(
54        &self,
55    ) -> impl Iterator<Item = BlockAndReceipts<'_, Self::Block, Self::Receipt>> {
56        self.blocks
57            .iter()
58            .zip(self.execution_outcome.receipts().iter())
59            .map(|(block, receipts)| BlockAndReceipts { block, receipts })
60    }
61
62    fn first_number(&self) -> u64 {
63        self.blocks.first().expect("Chain must be non-empty").number()
64    }
65
66    fn tip_number(&self) -> u64 {
67        self.blocks.last().expect("Chain must be non-empty").number()
68    }
69
70    fn len(&self) -> usize {
71        self.blocks.len()
72    }
73}
74
75/// Make a chain with `count` fake blocks numbered `0..count`.
76///
77/// # Panics
78///
79/// Panics if `count` is 0, as an empty chain is not valid.
80pub fn fake_chain(count: u64) -> Chain {
81    assert!(count > 0, "fake_chain requires at least one block");
82    let blocks: Vec<_> = (0..count).map(fake_block).collect();
83    let receipts = vec![vec![]; count as usize];
84    let execution_outcome = ExecutionOutcome::new(Default::default(), receipts, 0);
85    Chain { blocks, execution_outcome }
86}
87
88/// Make a fake block with a specific number.
89pub fn fake_block(number: u64) -> RecoveredBlock {
90    let header = Header {
91        number,
92        timestamp: 1716555576, // no particular significance other than divisible by 12
93        ..Default::default()
94    };
95    let v1 = SignetHeaderV1::try_from(header).expect("fake header is valid V1");
96    SealedBlock::new(v1, vec![]).recover_unchecked(vec![])
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    #[should_panic(expected = "fake_chain requires at least one block")]
105    fn fake_chain_rejects_zero() {
106        fake_chain(0);
107    }
108
109    #[test]
110    fn single_block_metadata() {
111        let chain = fake_chain(1);
112        assert_eq!(chain.len(), 1);
113        assert_eq!(chain.first_number(), 0);
114        assert_eq!(chain.tip_number(), 0);
115    }
116
117    #[test]
118    fn multi_block_metadata() {
119        let chain = fake_chain(5);
120        assert_eq!(chain.len(), 5);
121        assert_eq!(chain.first_number(), 0);
122        assert_eq!(chain.tip_number(), 4);
123    }
124}