use crate::Zenith::BlockHeader as ZenithHeader;
use alloy::{
consensus::TxEnvelope,
eips::eip2718::{Decodable2718, Encodable2718},
primitives::{keccak256, Address, B256},
rlp::Decodable,
};
use std::{marker::PhantomData, sync::OnceLock};
pub type ZenithTransaction = TxEnvelope;
pub trait Coder {
type Tx: std::fmt::Debug + Clone + PartialEq + Eq;
fn encode(t: &Self::Tx) -> Vec<u8>;
fn decode(buf: &mut &[u8]) -> Option<Self::Tx>
where
Self: Sized;
}
#[derive(Copy, Clone, Debug)]
pub struct Alloy2718Coder;
impl Coder for Alloy2718Coder {
type Tx = ZenithTransaction;
fn encode(t: &ZenithTransaction) -> Vec<u8> {
t.encoded_2718()
}
fn decode(buf: &mut &[u8]) -> Option<ZenithTransaction>
where
Self: Sized,
{
ZenithTransaction::decode_2718(buf).ok().filter(|tx| !tx.is_eip4844())
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(bound = "C::Tx: serde::Serialize + serde::de::DeserializeOwned")]
pub struct ZenithBlock<C: Coder = Alloy2718Coder> {
header: ZenithHeader,
transactions: Vec<<C as Coder>::Tx>,
#[serde(skip)]
encoded: OnceLock<Vec<u8>>,
#[serde(with = "oncelock_as_opt")]
block_data_hash: OnceLock<B256>,
_pd: std::marker::PhantomData<C>,
}
mod oncelock_as_opt {
use serde::{Deserialize, Serialize};
use std::sync::OnceLock;
pub(crate) fn serialize<S, T>(value: &OnceLock<T>, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
T: serde::Serialize,
{
value.get().serialize(serializer)
}
pub(crate) fn deserialize<'de, D, T>(deserializer: D) -> Result<OnceLock<T>, D::Error>
where
D: serde::Deserializer<'de>,
T: serde::Deserialize<'de>,
{
if let Some(item) = Option::<T>::deserialize(deserializer)? {
Ok(OnceLock::from(item))
} else {
Ok(OnceLock::new())
}
}
}
impl<C> ZenithBlock<C>
where
C: Coder,
{
pub const fn new(header: ZenithHeader, transactions: Vec<<C as Coder>::Tx>) -> Self {
ZenithBlock {
header,
transactions,
encoded: OnceLock::new(),
block_data_hash: OnceLock::new(),
_pd: PhantomData,
}
}
pub fn from_header_and_data(header: ZenithHeader, buf: impl AsRef<[u8]>) -> Self {
let b = buf.as_ref();
let transactions = decode_txns::<C>(b);
let h = keccak256(b);
ZenithBlock {
header,
transactions,
encoded: b.to_owned().into(),
block_data_hash: h.into(),
_pd: PhantomData,
}
}
pub fn into_parts(self) -> (ZenithHeader, Vec<C::Tx>) {
(self.header, self.transactions)
}
pub fn encoded_txns(&self) -> &[u8] {
self.seal();
self.encoded.get().unwrap().as_slice()
}
pub fn block_data_hash(&self) -> B256 {
self.seal();
*self.block_data_hash.get().unwrap()
}
pub fn push_transaction(&mut self, tx: C::Tx) {
self.unseal();
self.transactions.push(tx);
}
#[allow(clippy::missing_const_for_fn)] pub fn transactions(&self) -> &[C::Tx] {
&self.transactions
}
pub fn transactions_mut(&mut self) -> &mut Vec<C::Tx> {
self.unseal();
&mut self.transactions
}
pub fn transactions_iter(&self) -> std::slice::Iter<'_, C::Tx> {
self.transactions.iter()
}
pub fn transactions_iter_mut(&mut self) -> std::slice::IterMut<'_, C::Tx> {
self.unseal();
self.transactions.iter_mut()
}
pub const fn header(&self) -> &ZenithHeader {
&self.header
}
pub const fn header_mut(&mut self) -> &mut ZenithHeader {
&mut self.header
}
fn seal(&self) {
let encoded = self.encoded.get_or_init(|| encode_txns::<C>(&self.transactions));
self.block_data_hash.get_or_init(|| keccak256(encoded));
}
fn unseal(&mut self) {
self.encoded.take();
self.block_data_hash.take();
}
pub const fn chain_id(&self) -> u64 {
self.header.chain_id()
}
pub const fn block_height(&self) -> u64 {
self.header.host_block_number()
}
pub const fn gas_limit(&self) -> u64 {
self.header.gas_limit()
}
pub const fn reward_address(&self) -> Address {
self.header.reward_address()
}
}
impl<C: Coder> PartialEq for ZenithBlock<C> {
fn eq(&self, other: &Self) -> bool {
match (self.block_data_hash.get(), other.block_data_hash.get()) {
(Some(lhs), Some(rhs)) => lhs == rhs,
_ => self.header == other.header && self.transactions == other.transactions,
}
}
}
impl<C: Coder> Eq for ZenithBlock<C> {}
pub fn decode_txns<C>(block_data: impl AsRef<[u8]>) -> Vec<C::Tx>
where
C: Coder,
{
let mut bd = block_data.as_ref();
Vec::<Vec<u8>>::decode(&mut bd)
.map(|rlp| rlp.into_iter().flat_map(|buf| C::decode(&mut buf.as_slice())).collect())
.ok()
.unwrap_or_default()
}
pub fn encode_txns<'a, C>(transactions: impl IntoIterator<Item = &'a C::Tx>) -> Vec<u8>
where
C: Coder,
C::Tx: 'a,
{
let encoded_txns = transactions.into_iter().map(|tx| C::encode(tx)).collect::<Vec<Vec<u8>>>();
let mut buf = Vec::new();
alloy::rlp::Encodable::encode(&encoded_txns, &mut buf);
buf
}
#[cfg(test)]
mod test {
use alloy::consensus::{Signed, TxEip1559};
use alloy::primitives::{b256, bytes, Address, Signature, U256};
use super::*;
#[test]
fn encode_decode() {
let sig = Signature::from_scalars_and_parity(
b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565"),
b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1"),
false,
);
let tx = ZenithTransaction::Eip1559(Signed::new_unchecked(
TxEip1559 {
chain_id: 1,
nonce: 2,
gas_limit: 3,
max_fee_per_gas: 4,
max_priority_fee_per_gas: 5,
to: Address::repeat_byte(6).into(),
value: U256::from(7),
access_list: Default::default(),
input: bytes!("08090a0b0c0d0e0f"),
},
sig,
b256!("87fdda4563f2f98ac9c3f076bca48a59309df94f13fb8abf8471b3b8b51a2816"),
));
let mut txs = vec![tx.clone()];
let encoded = encode_txns::<Alloy2718Coder>(&txs);
let decoded = decode_txns::<Alloy2718Coder>(encoded);
assert_eq!(txs, decoded);
txs.push(tx.clone());
let encoded = encode_txns::<Alloy2718Coder>(&txs);
let decoded = decode_txns::<Alloy2718Coder>(encoded);
assert_eq!(txs, decoded);
}
#[test]
fn graceful_junk() {
let junk = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let decoded = decode_txns::<Alloy2718Coder>(&junk);
assert!(decoded.is_empty());
}
#[test]
fn zenith_block_eq_with_one_populated_hash() {
let header = ZenithHeader {
rollupChainId: U256::from(1),
hostBlockNumber: U256::from(100),
gasLimit: U256::from(30_000_000),
rewardAddress: Address::ZERO,
blockDataHash: [0u8; 32].into(),
};
let block_a = ZenithBlock::<Alloy2718Coder>::new(header, vec![]);
let block_b = ZenithBlock::<Alloy2718Coder>::new(header, vec![]);
block_a.block_data_hash();
assert!(block_a.block_data_hash.get().is_some());
assert!(block_b.block_data_hash.get().is_none());
assert_eq!(block_a, block_b);
}
}