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
9pub type ZenithTransaction = TxEnvelope;
11
12pub trait Coder {
14 type Tx: std::fmt::Debug + Clone + PartialEq + Eq;
16
17 fn encode(t: &Self::Tx) -> Vec<u8>;
19
20 fn decode(buf: &mut &[u8]) -> Option<Self::Tx>
22 where
23 Self: Sized;
24}
25
26#[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#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct ZenithBlock<C: Coder = Alloy2718Coder> {
49 header: ZenithHeader,
52 transactions: Vec<<C as Coder>::Tx>,
55
56 encoded: OnceLock<Vec<u8>>,
58 block_data_hash: OnceLock<B256>,
59
60 _pd: std::marker::PhantomData<C>,
62}
63
64impl<C> ZenithBlock<C>
65where
66 C: Coder,
67{
68 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 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 pub fn into_parts(self) -> (ZenithHeader, Vec<C::Tx>) {
102 (self.header, self.transactions)
103 }
104
105 pub fn encoded_txns(&self) -> &[u8] {
107 self.seal();
108 self.encoded.get().unwrap().as_slice()
109 }
110
111 pub fn block_data_hash(&self) -> B256 {
113 self.seal();
114 *self.block_data_hash.get().unwrap()
115 }
116
117 pub fn push_transaction(&mut self, tx: C::Tx) {
119 self.unseal();
120 self.transactions.push(tx);
121 }
122
123 pub fn transactions(&self) -> &[C::Tx] {
125 &self.transactions
126 }
127
128 pub fn transactions_mut(&mut self) -> &mut Vec<C::Tx> {
130 self.unseal();
131 &mut self.transactions
132 }
133
134 pub fn transactions_iter(&self) -> std::slice::Iter<'_, C::Tx> {
136 self.transactions.iter()
137 }
138
139 pub fn transactions_iter_mut(&mut self) -> std::slice::IterMut<'_, C::Tx> {
141 self.unseal();
142 self.transactions.iter_mut()
143 }
144
145 pub const fn header(&self) -> &ZenithHeader {
147 &self.header
148 }
149
150 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 pub const fn chain_id(&self) -> u64 {
167 self.header.chain_id()
168 }
169
170 pub const fn block_height(&self) -> u64 {
172 self.header.host_block_number()
173 }
174
175 pub const fn gas_limit(&self) -> u64 {
177 self.header.gas_limit()
178 }
179
180 pub const fn reward_address(&self) -> Address {
182 self.header.reward_address()
183 }
184}
185
186pub 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
206pub 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}