zenith_types/
block.rs

1use std::{marker::PhantomData, sync::OnceLock};
2
3use crate::Zenith::BlockHeader as ZenithHeader;
4use alloy::consensus::TxEnvelope;
5use alloy::eips::eip2718::{Decodable2718, Encodable2718};
6use alloy::primitives::{keccak256, Address, B256};
7use alloy::rlp::Decodable;
8
9/// Zenith processes normal Ethereum txns.
10pub type ZenithTransaction = TxEnvelope;
11
12/// Encode/Decode trait for inner tx type
13pub trait Coder {
14    /// The inner tx type.
15    type Tx: std::fmt::Debug + Clone + PartialEq + Eq;
16
17    /// Encode the tx.
18    fn encode(t: &Self::Tx) -> Vec<u8>;
19
20    /// Decode the tx.
21    fn decode(buf: &mut &[u8]) -> Option<Self::Tx>
22    where
23        Self: Sized;
24}
25
26/// Coder for [`encode_txns`] and [`decode_txns`] that operates on
27/// [`TxEnvelope`].
28#[derive(Copy, Clone, Debug)]
29pub struct Alloy2718Coder;
30
31impl Coder for Alloy2718Coder {
32    type Tx = ZenithTransaction;
33
34    fn encode(t: &ZenithTransaction) -> Vec<u8> {
35        t.encoded_2718()
36    }
37
38    fn decode(buf: &mut &[u8]) -> Option<ZenithTransaction>
39    where
40        Self: Sized,
41    {
42        ZenithTransaction::decode_2718(buf).ok()
43    }
44}
45
46/// A Zenith block is just a list of transactions.
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct ZenithBlock<C: Coder = Alloy2718Coder> {
49    /// The zenith block header, which may be extracted from a
50    /// [`crate::Zenith::BlockSubmitted`] event.
51    header: ZenithHeader,
52    /// The transactions in the block, which are extracted from the calldata or
53    /// blob data.
54    transactions: Vec<<C as Coder>::Tx>,
55
56    // memoization fields
57    encoded: OnceLock<Vec<u8>>,
58    block_data_hash: OnceLock<B256>,
59
60    /// The coder
61    _pd: std::marker::PhantomData<C>,
62}
63
64impl<C> ZenithBlock<C>
65where
66    C: Coder,
67{
68    /// Create a new zenith block.
69    pub const fn new(header: ZenithHeader, transactions: Vec<<C as Coder>::Tx>) -> Self {
70        ZenithBlock {
71            header,
72            transactions,
73            encoded: OnceLock::new(),
74            block_data_hash: OnceLock::new(),
75            _pd: PhantomData,
76        }
77    }
78
79    /// Decode tx data in the block.
80    ///
81    /// This will perform the following steps:
82    /// - Attempt to decode the data as an RLP list
83    ///     - On failure, discard all data, returning an empty tx list
84    /// - Attempt to decode each item in the list as a transaction
85    ///     - On failure, discard the item
86    /// - Return a list of succesfully decoded transactions
87    pub fn from_header_and_data(header: ZenithHeader, buf: impl AsRef<[u8]>) -> Self {
88        let b = buf.as_ref();
89        let transactions = decode_txns::<C>(b);
90        let h = keccak256(b);
91        ZenithBlock {
92            header,
93            transactions,
94            encoded: b.to_owned().into(),
95            block_data_hash: h.into(),
96            _pd: PhantomData,
97        }
98    }
99
100    /// Break the block into its parts.
101    pub fn into_parts(self) -> (ZenithHeader, Vec<C::Tx>) {
102        (self.header, self.transactions)
103    }
104
105    /// Encode the transactions in the block.
106    pub fn encoded_txns(&self) -> &[u8] {
107        self.seal();
108        self.encoded.get().unwrap().as_slice()
109    }
110
111    /// The hash of the encoded transactions.
112    pub fn block_data_hash(&self) -> B256 {
113        self.seal();
114        *self.block_data_hash.get().unwrap()
115    }
116
117    /// Push a transaction into the block.
118    pub fn push_transaction(&mut self, tx: C::Tx) {
119        self.unseal();
120        self.transactions.push(tx);
121    }
122
123    /// Access to the transactions.
124    pub fn transactions(&self) -> &[C::Tx] {
125        &self.transactions
126    }
127
128    /// Mutable access to the transactions.
129    pub fn transactions_mut(&mut self) -> &mut Vec<C::Tx> {
130        self.unseal();
131        &mut self.transactions
132    }
133
134    /// Iterate over the transactions.
135    pub fn transactions_iter(&self) -> std::slice::Iter<'_, C::Tx> {
136        self.transactions.iter()
137    }
138
139    /// Iterate over mut transactions.
140    pub fn transactions_iter_mut(&mut self) -> std::slice::IterMut<'_, C::Tx> {
141        self.unseal();
142        self.transactions.iter_mut()
143    }
144
145    /// Access to the header.
146    pub const fn header(&self) -> &ZenithHeader {
147        &self.header
148    }
149
150    /// Mutable access to the header.
151    pub fn header_mut(&mut self) -> &mut ZenithHeader {
152        &mut self.header
153    }
154
155    fn seal(&self) {
156        let encoded = self.encoded.get_or_init(|| encode_txns::<C>(&self.transactions));
157        self.block_data_hash.get_or_init(|| keccak256(encoded));
158    }
159
160    fn unseal(&mut self) {
161        self.encoded.take();
162        self.block_data_hash.take();
163    }
164
165    /// Get the chain ID of the block (discarding high bytes).
166    pub const fn chain_id(&self) -> u64 {
167        self.header.chain_id()
168    }
169
170    /// Gets the block height according to the header
171    pub const fn block_height(&self) -> u64 {
172        self.header.host_block_number()
173    }
174
175    /// Get the gas limit of the block (discarding high bytes).
176    pub const fn gas_limit(&self) -> u64 {
177        self.header.gas_limit()
178    }
179
180    /// Get the reward address of the block.
181    pub const fn reward_address(&self) -> Address {
182        self.header.reward_address()
183    }
184}
185
186/// Decode transactions.
187///
188/// A transaction is an RLP-encoded list of EIP-2718-encoded transaction
189/// envelopes.
190///
191/// The function is generic over the coder type, which is used to decode the
192/// transactions. This allows for different transaction types to be decoded
193/// using different coders.
194pub fn decode_txns<C>(block_data: impl AsRef<[u8]>) -> Vec<C::Tx>
195where
196    C: Coder,
197{
198    let mut bd = block_data.as_ref();
199
200    Vec::<Vec<u8>>::decode(&mut bd)
201        .map(|rlp| rlp.into_iter().flat_map(|buf| C::decode(&mut buf.as_slice())).collect())
202        .ok()
203        .unwrap_or_default()
204}
205
206/// Encode a set of transactions into a single RLP-encoded buffer.
207///
208/// The function is generic over the coder type, which is used to encode the
209/// transactions. This allows for different transaction types to be encoded
210/// using different encodings.
211pub fn encode_txns<'a, C>(transactions: impl IntoIterator<Item = &'a C::Tx>) -> Vec<u8>
212where
213    C: Coder,
214    C::Tx: 'a,
215{
216    let encoded_txns = transactions.into_iter().map(|tx| C::encode(tx)).collect::<Vec<Vec<u8>>>();
217
218    let mut buf = Vec::new();
219    alloy::rlp::Encodable::encode(&encoded_txns, &mut buf);
220    buf
221}
222
223#[cfg(test)]
224mod test {
225    use alloy::consensus::{Signed, TxEip1559};
226    use alloy::primitives::{b256, bytes, Address, PrimitiveSignature, U256};
227
228    use super::*;
229
230    #[test]
231    fn encode_decode() {
232        let sig = PrimitiveSignature::from_scalars_and_parity(
233            b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565"),
234            b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1"),
235            false,
236        );
237
238        let tx = ZenithTransaction::Eip1559(Signed::new_unchecked(
239            TxEip1559 {
240                chain_id: 1,
241                nonce: 2,
242                gas_limit: 3,
243                max_fee_per_gas: 4,
244                max_priority_fee_per_gas: 5,
245                to: Address::repeat_byte(6).into(),
246                value: U256::from(7),
247                access_list: Default::default(),
248                input: bytes!("08090a0b0c0d0e0f"),
249            },
250            sig,
251            b256!("87fdda4563f2f98ac9c3f076bca48a59309df94f13fb8abf8471b3b8b51a2816"),
252        ));
253
254        let mut txs = vec![tx.clone()];
255        let encoded = encode_txns::<Alloy2718Coder>(&txs);
256        let decoded = decode_txns::<Alloy2718Coder>(encoded);
257
258        assert_eq!(txs, decoded);
259
260        txs.push(tx.clone());
261        let encoded = encode_txns::<Alloy2718Coder>(&txs);
262        let decoded = decode_txns::<Alloy2718Coder>(encoded);
263
264        assert_eq!(txs, decoded);
265    }
266
267    #[test]
268    fn graceful_junk() {
269        let junk = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
270        let decoded = decode_txns::<Alloy2718Coder>(&junk);
271        assert!(decoded.is_empty());
272    }
273}