#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc = include_str!("../README.md")] #![deny(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] use core::marker::PhantomData; extern crate alloc; mod iumt; pub use iumt::*; #[expect(clippy::cast_possible_truncation)] #[frame_support::pallet] pub mod pallet { use alloc::vec::Vec; use frame_support::pallet_prelude::*; use serai_abi::primitives::{prelude::*, merkle::IncrementalUnbalancedMerkleTree as Iumt}; use super::*; /// The set of all blocks prior added to the blockchain. #[pallet::storage] pub(super) 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, { serai_abi::TRANSACTION_EVENTS_COMMITMENT_BRANCH_TAG }, { serai_abi::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, { serai_abi::EVENTS_COMMITMENT_BRANCH_TAG }, { serai_abi::EVENTS_COMMITMENT_LEAF_TAG }, >; /// A mapping from an account to its next nonce. #[pallet::storage] type NextNonce = StorageMap<_, Blake2_128Concat, SeraiAddress, T::Nonce, ValueQuery>; /// Mapping from Serai's events to Substrate's. #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// An event from Serai. Event(Vec), } #[pallet::config] pub trait Config: frame_system::Config> {} #[pallet::pallet] pub struct Pallet(_); impl Pallet { /// If a block exists on the current blockchain. #[must_use] pub fn block_exists(hash: impl scale::EncodeLike) -> bool { Blocks::::contains_key(hash) } /// The next nonce for an account. #[must_use] pub fn next_nonce(account: &SeraiAddress) -> T::Nonce { NextNonce::::get(account) } /// Consume the next nonce for an account. /// /// Panics if the current nonce is `<_>::MAX`. pub fn consume_next_nonce(signer: &SeraiAddress) { NextNonce::::mutate(signer, |value| { *value = value .checked_add(&T::Nonce::one()) .expect("`consume_next_nonce` called when current nonce is <_>::MAX") }); } /// The code to run on genesis. pub fn genesis() { BlocksCommitmentMerkle::::new_expecting_none(); BlockTransactionsCommitmentMerkle::::new_expecting_none(); BlockEventsCommitmentMerkle::::new_expecting_none(); } /// The code to run when beginning execution of a transaction. /// /// The caller MUST ensure two transactions aren't simultaneously started. pub fn start_transaction() { TransactionEventsMerkle::::new_expecting_none(); } /// Emit an event. pub fn emit_event(event: impl Into) { let event = event.into(); TransactionEventsMerkle::::append(&event); Self::deposit_event(Event::Event(borsh::to_vec(&event).unwrap())); } /// End execution of a transaction. 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)); } /// Fetch all of Serai's events. /// /// This MUST only be used for testing purposes. #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] pub fn events() -> Vec where serai_abi::Event: TryFrom, { frame_system::Pallet::::events() .into_iter() .filter_map(|e| serai_abi::Event::try_from(e.event).ok()) .collect() } } } pub use pallet::*; /// The code to run at the start of a block for this pallet. pub struct StartOfBlock(PhantomData); impl frame_support::traits::PreInherents for StartOfBlock { fn pre_inherents() { use frame_support::pallet_prelude::Zero; // `Pallet::genesis` is expected to be used for the genesis block assert!(!frame_system::Pallet::::block_number().is_zero()); 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(); } } /// The code to run at the end of a block for this pallet. pub struct EndOfBlock(PhantomData); impl frame_support::traits::PostTransactions for EndOfBlock { fn post_transactions() { use serai_abi::SeraiExecutionDigest; frame_system::Pallet::::deposit_log( frame_support::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(), ), ); } }