use core::marker::PhantomData; use borsh::BorshSerialize; use frame_support::pallet_prelude::*; use serai_abi::{ primitives::merkle::{UnbalancedMerkleTree, IncrementalUnbalancedMerkleTree as Iumt}, *, }; /// A wrapper around a `StorageValue` which offers a high-level API as an IUMT. struct IncrementalUnbalancedMerkleTree< T: frame_support::StorageValue>, const BRANCH_TAG: u8 = 1, const LEAF_TAG: u8 = 0, >(PhantomData); impl< T: frame_support::StorageValue>, const BRANCH_TAG: u8, const LEAF_TAG: u8, > IncrementalUnbalancedMerkleTree { /// Create a new Merkle tree, expecting there to be none already present. /// /// Panics if a Merkle tree was already present. fn new_expecting_none() { T::mutate(|value| { assert!(value.is_none()); *value = Some(Iumt::new()); }); } /// Append a leaf to the Merkle tree. /// /// Panics if no Merkle tree was present. fn append(leaf: &L) { let leaf = sp_core::blake2_256(&borsh::to_vec(&(LEAF_TAG, leaf)).unwrap()); T::mutate(|value| { let tree = value.as_mut().unwrap(); tree.append(BRANCH_TAG, leaf); }) } /// Get the unbalanced merkle tree. /// /// Panics if no Merkle tree was present. fn get() -> UnbalancedMerkleTree { T::get().unwrap().calculate(BRANCH_TAG) } /// Take the Merkle tree. /// /// Panics if no Merkle tree was present. fn take() -> UnbalancedMerkleTree { T::mutate(|value| value.take().unwrap().calculate(BRANCH_TAG)) } } #[frame_support::pallet] mod pallet { use super::*; /// The set of all blocks prior added to the blockchain. #[pallet::storage] pub type Blocks = StorageMap<_, Identity, T::Hash, (), OptionQuery>; /// The Merkle tree of all blocks added to the blockchain. #[pallet::storage] #[pallet::unbounded] pub(super) type BlocksCommitment = StorageValue<_, Iumt, OptionQuery>; pub(super) type BlocksCommitmentMerkle = IncrementalUnbalancedMerkleTree>; /// The Merkle tree of all transactions within the current block. #[pallet::storage] #[pallet::unbounded] pub(super) type BlockTransactionsCommitment = StorageValue<_, Iumt, OptionQuery>; pub(super) type BlockTransactionsCommitmentMerkle = IncrementalUnbalancedMerkleTree>; /// The hashes of events caused by the current transaction. #[pallet::storage] #[pallet::unbounded] pub(super) type TransactionEvents = StorageValue<_, Iumt, OptionQuery>; pub(super) type TransactionEventsMerkle = IncrementalUnbalancedMerkleTree< TransactionEvents, TRANSACTION_EVENTS_COMMITMENT_BRANCH_TAG, TRANSACTION_EVENTS_COMMITMENT_LEAF_TAG, >; /// The roots of the Merkle trees of each transaction's events. #[pallet::storage] #[pallet::unbounded] pub(super) type BlockEventsCommitment = StorageValue<_, Iumt, OptionQuery>; pub(super) type BlockEventsCommitmentMerkle = IncrementalUnbalancedMerkleTree< BlockEventsCommitment, EVENTS_COMMITMENT_BRANCH_TAG, EVENTS_COMMITMENT_LEAF_TAG, >; /// A mapping from an account to its next nonce. #[pallet::storage] pub type NextNonce = StorageMap<_, Blake2_128Concat, T::AccountId, T::Nonce, ValueQuery>; #[pallet::config] pub trait Config: frame_system::Config> {} #[pallet::pallet] pub struct Pallet(_); impl Pallet { pub fn start_transaction() { TransactionEventsMerkle::::new_expecting_none(); } // TODO: Have this called pub fn on_event(event: impl TryInto) { if let Ok(event) = event.try_into() { TransactionEventsMerkle::::append(&event); } } pub fn end_transaction(transaction_hash: [u8; 32]) { BlockTransactionsCommitmentMerkle::::append(&transaction_hash); let transaction_events_root = TransactionEventsMerkle::::take().root; // Append the leaf (the transaction's hash and its events' root) to the block's events' // commitment BlockEventsCommitmentMerkle::::append(&(&transaction_hash, &transaction_events_root)); } } } pub(super) use pallet::*; pub struct StartOfBlock(PhantomData); impl frame_support::traits::PreInherents for StartOfBlock { fn pre_inherents() { if frame_system::Pallet::::block_number().is_zero() { BlocksCommitmentMerkle::::new_expecting_none(); } else { let parent_hash = frame_system::Pallet::::parent_hash(); Blocks::::set(parent_hash, Some(())); let parent_hash: [u8; 32] = parent_hash.into(); BlocksCommitmentMerkle::::append(&parent_hash); } BlockTransactionsCommitmentMerkle::::new_expecting_none(); BlockEventsCommitmentMerkle::::new_expecting_none(); } } pub struct EndOfBlock(PhantomData); impl frame_support::traits::PostTransactions for EndOfBlock { fn post_transactions() { frame_system::Pallet::::deposit_log(sp_runtime::generic::DigestItem::Consensus( SeraiExecutionDigest::CONSENSUS_ID, borsh::to_vec(&SeraiExecutionDigest { builds_upon: BlocksCommitmentMerkle::::get(), transactions_commitment: BlockTransactionsCommitmentMerkle::::take(), events_commitment: BlockEventsCommitmentMerkle::::take(), }) .unwrap(), )); } }