1use crate::Zenith::BlockHeader as ZenithHeader;
2use alloy::{
3 consensus::TxEnvelope,
4 eips::eip2718::{Decodable2718, Encodable2718},
5 primitives::{keccak256, Address, B256},
6 rlp::Decodable,
7};
8use std::{marker::PhantomData, sync::OnceLock};
9
10pub type ZenithTransaction = TxEnvelope;
12
13pub trait Coder {
15 type Tx: std::fmt::Debug + Clone + PartialEq + Eq;
17
18 fn encode(t: &Self::Tx) -> Vec<u8>;
20
21 fn decode(buf: &mut &[u8]) -> Option<Self::Tx>
23 where
24 Self: Sized;
25}
26
27#[derive(Copy, Clone, Debug)]
30pub struct Alloy2718Coder;
31
32impl Coder for Alloy2718Coder {
33 type Tx = ZenithTransaction;
34
35 fn encode(t: &ZenithTransaction) -> Vec<u8> {
36 t.encoded_2718()
37 }
38
39 fn decode(buf: &mut &[u8]) -> Option<ZenithTransaction>
40 where
41 Self: Sized,
42 {
43 ZenithTransaction::decode_2718(buf).ok().filter(|tx| !tx.is_eip4844())
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
49#[serde(bound = "C::Tx: serde::Serialize + serde::de::DeserializeOwned")]
50pub struct ZenithBlock<C: Coder = Alloy2718Coder> {
51 header: ZenithHeader,
54 transactions: Vec<<C as Coder>::Tx>,
57
58 #[serde(skip)]
60 encoded: OnceLock<Vec<u8>>,
61
62 #[serde(with = "oncelock_as_opt")]
64 block_data_hash: OnceLock<B256>,
65
66 _pd: std::marker::PhantomData<C>,
68}
69
70mod oncelock_as_opt {
71 use serde::{Deserialize, Serialize};
72 use std::sync::OnceLock;
73
74 pub(crate) fn serialize<S, T>(value: &OnceLock<T>, serializer: S) -> Result<S::Ok, S::Error>
76 where
77 S: serde::Serializer,
78 T: serde::Serialize,
79 {
80 value.get().serialize(serializer)
81 }
82
83 pub(crate) fn deserialize<'de, D, T>(deserializer: D) -> Result<OnceLock<T>, D::Error>
85 where
86 D: serde::Deserializer<'de>,
87 T: serde::Deserialize<'de>,
88 {
89 if let Some(item) = Option::<T>::deserialize(deserializer)? {
90 Ok(OnceLock::from(item))
91 } else {
92 Ok(OnceLock::new())
93 }
94 }
95}
96
97impl<C> ZenithBlock<C>
98where
99 C: Coder,
100{
101 pub const fn new(header: ZenithHeader, transactions: Vec<<C as Coder>::Tx>) -> Self {
103 ZenithBlock {
104 header,
105 transactions,
106 encoded: OnceLock::new(),
107 block_data_hash: OnceLock::new(),
108 _pd: PhantomData,
109 }
110 }
111
112 pub fn from_header_and_data(header: ZenithHeader, buf: impl AsRef<[u8]>) -> Self {
121 let b = buf.as_ref();
122 let transactions = decode_txns::<C>(b);
123 let h = keccak256(b);
124 ZenithBlock {
125 header,
126 transactions,
127 encoded: b.to_owned().into(),
128 block_data_hash: h.into(),
129 _pd: PhantomData,
130 }
131 }
132
133 pub fn into_parts(self) -> (ZenithHeader, Vec<C::Tx>) {
135 (self.header, self.transactions)
136 }
137
138 pub fn encoded_txns(&self) -> &[u8] {
140 self.seal();
141 self.encoded.get().unwrap().as_slice()
142 }
143
144 pub fn block_data_hash(&self) -> B256 {
146 self.seal();
147 *self.block_data_hash.get().unwrap()
148 }
149
150 pub fn push_transaction(&mut self, tx: C::Tx) {
152 self.unseal();
153 self.transactions.push(tx);
154 }
155
156 #[allow(clippy::missing_const_for_fn)] pub fn transactions(&self) -> &[C::Tx] {
159 &self.transactions
160 }
161
162 pub fn transactions_mut(&mut self) -> &mut Vec<C::Tx> {
164 self.unseal();
165 &mut self.transactions
166 }
167
168 pub fn transactions_iter(&self) -> std::slice::Iter<'_, C::Tx> {
170 self.transactions.iter()
171 }
172
173 pub fn transactions_iter_mut(&mut self) -> std::slice::IterMut<'_, C::Tx> {
175 self.unseal();
176 self.transactions.iter_mut()
177 }
178
179 pub const fn header(&self) -> &ZenithHeader {
181 &self.header
182 }
183
184 pub const fn header_mut(&mut self) -> &mut ZenithHeader {
186 &mut self.header
187 }
188
189 fn seal(&self) {
190 let encoded = self.encoded.get_or_init(|| encode_txns::<C>(&self.transactions));
191 self.block_data_hash.get_or_init(|| keccak256(encoded));
192 }
193
194 fn unseal(&mut self) {
195 self.encoded.take();
196 self.block_data_hash.take();
197 }
198
199 pub const fn chain_id(&self) -> u64 {
201 self.header.chain_id()
202 }
203
204 pub const fn block_height(&self) -> u64 {
206 self.header.host_block_number()
207 }
208
209 pub const fn gas_limit(&self) -> u64 {
211 self.header.gas_limit()
212 }
213
214 pub const fn reward_address(&self) -> Address {
216 self.header.reward_address()
217 }
218}
219
220pub fn decode_txns<C>(block_data: impl AsRef<[u8]>) -> Vec<C::Tx>
229where
230 C: Coder,
231{
232 let mut bd = block_data.as_ref();
233
234 Vec::<Vec<u8>>::decode(&mut bd)
235 .map(|rlp| rlp.into_iter().flat_map(|buf| C::decode(&mut buf.as_slice())).collect())
236 .ok()
237 .unwrap_or_default()
238}
239
240pub fn encode_txns<'a, C>(transactions: impl IntoIterator<Item = &'a C::Tx>) -> Vec<u8>
246where
247 C: Coder,
248 C::Tx: 'a,
249{
250 let encoded_txns = transactions.into_iter().map(|tx| C::encode(tx)).collect::<Vec<Vec<u8>>>();
251
252 let mut buf = Vec::new();
253 alloy::rlp::Encodable::encode(&encoded_txns, &mut buf);
254 buf
255}
256
257#[cfg(test)]
258mod test {
259 use alloy::consensus::{Signed, TxEip1559};
260 use alloy::primitives::{b256, bytes, Address, Signature, U256};
261
262 use super::*;
263
264 #[test]
265 fn encode_decode() {
266 let sig = Signature::from_scalars_and_parity(
267 b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565"),
268 b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1"),
269 false,
270 );
271
272 let tx = ZenithTransaction::Eip1559(Signed::new_unchecked(
273 TxEip1559 {
274 chain_id: 1,
275 nonce: 2,
276 gas_limit: 3,
277 max_fee_per_gas: 4,
278 max_priority_fee_per_gas: 5,
279 to: Address::repeat_byte(6).into(),
280 value: U256::from(7),
281 access_list: Default::default(),
282 input: bytes!("08090a0b0c0d0e0f"),
283 },
284 sig,
285 b256!("87fdda4563f2f98ac9c3f076bca48a59309df94f13fb8abf8471b3b8b51a2816"),
286 ));
287
288 let mut txs = vec![tx.clone()];
289 let encoded = encode_txns::<Alloy2718Coder>(&txs);
290 let decoded = decode_txns::<Alloy2718Coder>(encoded);
291
292 assert_eq!(txs, decoded);
293
294 txs.push(tx.clone());
295 let encoded = encode_txns::<Alloy2718Coder>(&txs);
296 let decoded = decode_txns::<Alloy2718Coder>(encoded);
297
298 assert_eq!(txs, decoded);
299 }
300
301 #[test]
302 fn graceful_junk() {
303 let junk = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
304 let decoded = decode_txns::<Alloy2718Coder>(&junk);
305 assert!(decoded.is_empty());
306 }
307}