Properly define the core pallet instead of placing it within the runtime

This commit is contained in:
Luke Parker
2025-09-19 19:05:47 -04:00
parent d74b00b9e4
commit 3f5150b3fa
12 changed files with 260 additions and 122 deletions

View File

@@ -297,6 +297,8 @@ mod substrate {
/// Begin execution of a transaction.
fn start_transaction(&self);
/// Consume the next nonce for an account.
///
/// This MUST NOT be called if the next nonce is `u32::MAX`. The caller MAY panic in that case.
fn consume_next_nonce(&self, signer: &SeraiAddress);
/// Have the transaction pay its SRI fee.
fn pay_fee(&self, signer: &SeraiAddress, fee: Amount) -> Result<(), TransactionValidityError>;
@@ -425,13 +427,20 @@ mod substrate {
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))?;
}
}
match self.1.next_nonce(signer).cmp(nonce) {
core::cmp::Ordering::Less => {
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))?
{
let next_nonce = self.1.next_nonce(signer);
if next_nonce == u32::MAX {
Err(TransactionValidityError::Invalid(InvalidTransaction::BadSigner))?;
}
core::cmp::Ordering::Equal => {}
core::cmp::Ordering::Greater => {
Err(TransactionValidityError::Invalid(InvalidTransaction::Future))?
match next_nonce.cmp(nonce) {
core::cmp::Ordering::Less => {
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))?
}
core::cmp::Ordering::Equal => {}
core::cmp::Ordering::Greater => {
Err(TransactionValidityError::Invalid(InvalidTransaction::Future))?
}
}
}

56
substrate/core/Cargo.toml Normal file
View File

@@ -0,0 +1,56 @@
[package]
name = "serai-core-pallet"
version = "0.1.0"
description = "Core pallet"
license = "AGPL-3.0-only"
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/core"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
edition = "2021"
rust-version = "1.85"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.cargo-machete]
ignored = ["scale"]
[lints]
workspace = true
[dependencies]
borsh = { version = "1", default-features = false, features = ["derive", "de_strict_order"] }
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
sp-core = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543", default-features = false }
sp-io = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543", default-features = false }
frame-system = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543", default-features = false }
frame-support = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543", default-features = false }
serai-abi = { path = "../abi", default-features = false, features = ["substrate"] }
[features]
std = [
"borsh/std",
"scale/std",
"sp-core/std",
"sp-io/std",
"frame-system/std",
"frame-support/std",
"serai-abi/std",
]
runtime-benchmarks = [
"frame-system/runtime-benchmarks",
"frame-support/runtime-benchmarks",
]
# TODO
try-runtime = []
default = ["std"]

15
substrate/core/LICENSE Normal file
View File

@@ -0,0 +1,15 @@
AGPL-3.0-only license
Copyright (c) 2023-2025 Luke Parker
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License Version 3 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

1
substrate/core/README.md Normal file
View File

@@ -0,0 +1 @@
# Serai Core Pallet

View File

@@ -0,0 +1,52 @@
use core::marker::PhantomData;
use borsh::BorshSerialize;
use serai_abi::primitives::merkle::{UnbalancedMerkleTree, IncrementalUnbalancedMerkleTree as Iumt};
/// A wrapper around a `StorageValue` which offers a high-level API as an
/// `IncrementalUnbalancedMerkleTree`.
pub struct IncrementalUnbalancedMerkleTree<
T: frame_support::StorageValue<Iumt, Query = Option<Iumt>>,
const BRANCH_TAG: u8 = 1,
const LEAF_TAG: u8 = 0,
>(PhantomData<T>);
impl<
T: frame_support::StorageValue<Iumt, Query = Option<Iumt>>,
const BRANCH_TAG: u8,
const LEAF_TAG: u8,
> IncrementalUnbalancedMerkleTree<T, BRANCH_TAG, LEAF_TAG>
{
/// Create a new Merkle tree, expecting there to be none already present.
///
/// Panics if a Merkle tree was already present.
pub 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.
pub fn append<L: BorshSerialize>(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.
pub fn get() -> UnbalancedMerkleTree {
T::get().unwrap().calculate(BRANCH_TAG)
}
/// Take the Merkle tree.
///
/// Panics if no Merkle tree was present.
pub fn take() -> UnbalancedMerkleTree {
T::mutate(|value| value.take().unwrap().calculate(BRANCH_TAG))
}
}

View File

@@ -1,59 +1,19 @@
use core::marker::PhantomData;
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
use borsh::BorshSerialize;
use core::marker::PhantomData;
use frame_support::pallet_prelude::*;
use serai_abi::{
primitives::merkle::{UnbalancedMerkleTree, IncrementalUnbalancedMerkleTree as Iumt},
primitives::{prelude::*, merkle::IncrementalUnbalancedMerkleTree as Iumt},
*,
};
/// A wrapper around a `StorageValue` which offers a high-level API as an IUMT.
struct IncrementalUnbalancedMerkleTree<
T: frame_support::StorageValue<Iumt, Query = Option<Iumt>>,
const BRANCH_TAG: u8 = 1,
const LEAF_TAG: u8 = 0,
>(PhantomData<T>);
impl<
T: frame_support::StorageValue<Iumt, Query = Option<Iumt>>,
const BRANCH_TAG: u8,
const LEAF_TAG: u8,
> IncrementalUnbalancedMerkleTree<T, BRANCH_TAG, LEAF_TAG>
{
/// 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<L: BorshSerialize>(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))
}
}
mod iumt;
pub use iumt::*;
#[frame_support::pallet]
mod pallet {
@@ -61,7 +21,7 @@ mod pallet {
/// The set of all blocks prior added to the blockchain.
#[pallet::storage]
pub type Blocks<T: Config> = StorageMap<_, Identity, T::Hash, (), OptionQuery>;
pub(super) type Blocks<T: Config> = StorageMap<_, Identity, T::Hash, (), OptionQuery>;
/// The Merkle tree of all blocks added to the blockchain.
#[pallet::storage]
#[pallet::unbounded]
@@ -96,8 +56,7 @@ mod pallet {
/// A mapping from an account to its next nonce.
#[pallet::storage]
pub type NextNonce<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, T::Nonce, ValueQuery>;
type NextNonce<T: Config> = StorageMap<_, Blake2_128Concat, SeraiAddress, T::Nonce, ValueQuery>;
#[pallet::config]
pub trait Config: frame_system::Config<Hash: Into<[u8; 32]>> {}
@@ -106,15 +65,43 @@ mod pallet {
pub struct Pallet<T>(_);
impl<T: Config> Pallet<T> {
/// If a block exists on the current blockchain.
#[must_use]
pub fn block_exists(hash: impl scale::EncodeLike<T::Hash>) -> bool {
Blocks::<T>::contains_key(hash)
}
/// The next nonce for an account.
#[must_use]
pub fn next_nonce(account: &SeraiAddress) -> T::Nonce {
NextNonce::<T>::get(account)
}
/// Consume the next nonce for an account.
///
/// Panics if the current nonce is `<_>::MAX`.
pub fn consume_next_nonce(signer: &SeraiAddress) {
NextNonce::<T>::mutate(signer, |value| {
*value = value
.checked_add(&T::Nonce::one())
.expect("`consume_next_nonce` called when current nonce is <_>::MAX")
});
}
/// 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::<T>::new_expecting_none();
}
/// Emit an event.
// TODO: Have this called
pub fn on_event(event: impl TryInto<serai_abi::Event>) {
pub fn emit_event(event: impl TryInto<serai_abi::Event>) {
if let Ok(event) = event.try_into() {
TransactionEventsMerkle::<T>::append(&event);
}
}
/// End execution of a transaction.
pub fn end_transaction(transaction_hash: [u8; 32]) {
BlockTransactionsCommitmentMerkle::<T>::append(&transaction_hash);
@@ -126,8 +113,9 @@ mod pallet {
}
}
}
pub(super) use pallet::*;
pub use pallet::*;
/// The code to run at the start of a block for this pallet.
pub struct StartOfBlock<T: Config>(PhantomData<T>);
impl<T: Config> frame_support::traits::PreInherents for StartOfBlock<T> {
fn pre_inherents() {
@@ -145,17 +133,20 @@ impl<T: Config> frame_support::traits::PreInherents for StartOfBlock<T> {
}
}
/// The code to run at the end of a block for this pallet.
pub struct EndOfBlock<T: Config>(PhantomData<T>);
impl<T: Config> frame_support::traits::PostTransactions for EndOfBlock<T> {
fn post_transactions() {
frame_system::Pallet::<T>::deposit_log(sp_runtime::generic::DigestItem::Consensus(
SeraiExecutionDigest::CONSENSUS_ID,
borsh::to_vec(&SeraiExecutionDigest {
builds_upon: BlocksCommitmentMerkle::<T>::get(),
transactions_commitment: BlockTransactionsCommitmentMerkle::<T>::take(),
events_commitment: BlockEventsCommitmentMerkle::<T>::take(),
})
.unwrap(),
));
frame_system::Pallet::<T>::deposit_log(
frame_support::sp_runtime::generic::DigestItem::Consensus(
SeraiExecutionDigest::CONSENSUS_ID,
borsh::to_vec(&SeraiExecutionDigest {
builds_upon: BlocksCommitmentMerkle::<T>::get(),
transactions_commitment: BlockTransactionsCommitmentMerkle::<T>::take(),
events_commitment: BlockEventsCommitmentMerkle::<T>::take(),
})
.unwrap(),
),
);
}
}

View File

@@ -33,6 +33,7 @@ frame-system = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev =
frame-support = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543", default-features = false }
frame-executive = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543", default-features = false }
serai-core-pallet = { path = "../core", default-features = false }
serai-coins-pallet = { path = "../coins", default-features = false }
serai-validator-sets-pallet = { path = "../validator-sets", default-features = false }
serai-signals-pallet = { path = "../signals", default-features = false }
@@ -42,6 +43,7 @@ substrate-wasm-builder = { git = "https://github.com/serai-dex/patch-polkadot-sd
[features]
std = [
"borsh/std",
"scale/std",
"sp-core/std",
@@ -55,6 +57,7 @@ std = [
"frame-support/std",
"frame-executive/std",
"serai-core-pallet/std",
"serai-coins-pallet/std",
"serai-validator-sets-pallet/std",
"serai-signals-pallet/std",
@@ -69,6 +72,7 @@ try-runtime = [
"frame-support/try-runtime",
"frame-executive/try-runtime",
"serai-core-pallet/try-runtime",
"serai-coins-pallet/try-runtime",
"serai-validator-sets-pallet/try-runtime",
"serai-signals-pallet/try-runtime",
@@ -80,6 +84,8 @@ runtime-benchmarks = [
"frame-system/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"serai-core-pallet/runtime-benchmarks",
"serai-coins-pallet/runtime-benchmarks",
"serai-validator-sets-pallet/runtime-benchmarks",
"serai-signals-pallet/runtime-benchmarks",
]

View File

@@ -9,7 +9,7 @@ extern crate alloc;
use alloc::borrow::Cow;
use sp_core::sr25519::Public;
use sp_runtime::{Perbill, Weight, traits::Header as _};
use sp_runtime::{Perbill, Weight};
use sp_version::RuntimeVersion;
#[rustfmt::skip]
@@ -19,8 +19,6 @@ use serai_abi::{
use serai_coins_pallet::{CoinsInstance, LiquidityTokensInstance};
mod core_pallet;
type Block = SubstrateBlock;
/// The lookup for a SeraiAddress -> Public.
@@ -75,7 +73,7 @@ mod runtime {
pub type System = frame_system::Pallet<Runtime>;
#[runtime::pallet_index(1)]
pub type Core = core_pallet::Pallet<Runtime>;
pub type Core = serai_core_pallet::Pallet<Runtime>;
#[runtime::pallet_index(2)]
pub type Coins = serai_coins_pallet::Pallet<Runtime, CoinsInstance>;
@@ -125,12 +123,12 @@ impl frame_system::Config for Runtime {
type SingleBlockMigrations = ();
type MultiBlockMigrator = ();
type PreInherents = core_pallet::StartOfBlock<Runtime>;
type PreInherents = serai_core_pallet::StartOfBlock<Runtime>;
type PostInherents = ();
type PostTransactions = core_pallet::EndOfBlock<Runtime>;
type PostTransactions = serai_core_pallet::EndOfBlock<Runtime>;
}
impl core_pallet::Config for Runtime {}
impl serai_core_pallet::Config for Runtime {}
impl serai_coins_pallet::Config<CoinsInstance> for Runtime {
type AllowMint = serai_coins_pallet::AlwaysAllowMint; // TODO
@@ -230,7 +228,6 @@ sp_api::impl_runtime_apis! {
VERSION
}
fn initialize_block(header: &Header) -> sp_runtime::ExtrinsicInclusionMode {
core_pallet::Blocks::<Runtime>::set(header.parent_hash(), Some(()));
Executive::initialize_block(header)
}
fn execute_block(block: Block) {
@@ -257,7 +254,7 @@ impl serai_abi::TransactionContext for Context {
/// If a block is present in the blockchain.
fn block_is_present_in_blockchain(&self, hash: &serai_abi::primitives::BlockHash) -> bool {
core_pallet::Blocks::<Runtime>::get(hash).is_some()
serai_core_pallet::Pallet::<Runtime>::block_exists(hash)
}
/// The time embedded into the current block.
fn current_time(&self) -> Option<u64> {
@@ -265,7 +262,7 @@ impl serai_abi::TransactionContext for Context {
}
/// Get the next nonce for an account.
fn next_nonce(&self, signer: &SeraiAddress) -> u32 {
core_pallet::NextNonce::<Runtime>::get(signer)
serai_core_pallet::Pallet::<Runtime>::next_nonce(signer)
}
/// If the signer can pay the SRI fee.
fn can_pay_fee(
@@ -284,11 +281,10 @@ impl serai_abi::TransactionContext for Context {
}
fn start_transaction(&self) {
Core::start_transaction();
Core::start_transaction()
}
/// Consume the next nonce for an account.
fn consume_next_nonce(&self, signer: &SeraiAddress) {
core_pallet::NextNonce::<Runtime>::mutate(signer, |value| *value += 1);
serai_core_pallet::Pallet::<Runtime>::consume_next_nonce(signer)
}
/// Have the transaction pay its SRI fee.
fn pay_fee(