Add a non-canonical SCALE derivations feature

Enables representing IUMT within `StorageValues`. Applied to a variety of
values.

Fixes a bug where `Some([0; 32])` would be considered a valid block anchor.
This commit is contained in:
Luke Parker
2025-03-06 03:19:29 -05:00
parent 35db2924b4
commit b08ae8e6a7
6 changed files with 40 additions and 41 deletions

View File

@@ -47,6 +47,6 @@ std = [
"serai-primitives/std", "serai-primitives/std",
] ]
substrate = ["serde", "scale", "scale-info", "sp-runtime", "frame-support"] substrate = ["serde", "scale", "scale-info", "sp-runtime", "frame-support", "serai-primitives/non_canonical_scale_derivations"]
try-runtime = ["sp-runtime/try-runtime"] try-runtime = ["sp-runtime/try-runtime"]
default = ["std"] default = ["std"]

View File

@@ -32,5 +32,6 @@ bech32 = { version = "0.11", default-features = false }
rand_core = { version = "0.6", default-features = false, features = ["std"] } rand_core = { version = "0.6", default-features = false, features = ["std"] }
[features] [features]
non_canonical_scale_derivations = []
std = ["zeroize/std", "borsh/std", "ciphersuite/std", "dkg/std", "sp-core/std", "bech32/std"] std = ["zeroize/std", "borsh/std", "ciphersuite/std", "dkg/std", "sp-core/std", "bech32/std"]
default = ["std"] default = ["std"]

View File

@@ -10,18 +10,10 @@ use crate::coin::{ExternalCoin, Coin};
pub type AmountRepr = u64; pub type AmountRepr = u64;
/// A wrapper used to represent amounts. /// A wrapper used to represent amounts.
#[derive( #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
Clone, #[cfg_attr(
Copy, feature = "non_canonical_scale_derivations",
PartialEq, derive(scale::Encode, scale::Decode, scale::MaxEncodedLen)
Eq,
PartialOrd,
Ord,
Hash,
Debug,
Zeroize,
BorshSerialize,
BorshDeserialize,
)] )]
pub struct Amount(pub AmountRepr); pub struct Amount(pub AmountRepr);
@@ -48,6 +40,10 @@ impl Mul for Amount {
/// An ExternalCoin and an Amount, forming a balance for an external coin. /// An ExternalCoin and an Amount, forming a balance for an external coin.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
#[cfg_attr(
feature = "non_canonical_scale_derivations",
derive(scale::Encode, scale::Decode, scale::MaxEncodedLen)
)]
pub struct ExternalBalance { pub struct ExternalBalance {
/// The coin this is a balance for. /// The coin this is a balance for.
pub coin: ExternalCoin, pub coin: ExternalCoin,
@@ -78,6 +74,10 @@ impl Mul<Amount> for ExternalBalance {
/// A Coin and an Amount, forming a balance for a coin. /// A Coin and an Amount, forming a balance for a coin.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
#[cfg_attr(
feature = "non_canonical_scale_derivations",
derive(scale::Encode, scale::Decode, scale::MaxEncodedLen)
)]
pub struct Balance { pub struct Balance {
/// The coin this is a balance for. /// The coin this is a balance for.
pub coin: Coin, pub coin: Coin,

View File

@@ -9,6 +9,10 @@ use crate::network_id::{ExternalNetworkId, NetworkId};
/// This type serializes to a subset of `Coin`. /// This type serializes to a subset of `Coin`.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Zeroize, BorshSerialize, BorshDeserialize)] #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
#[borsh(use_discriminant = true)] #[borsh(use_discriminant = true)]
#[cfg_attr(
feature = "non_canonical_scale_derivations",
derive(scale::Encode, scale::Decode, scale::MaxEncodedLen)
)]
#[non_exhaustive] #[non_exhaustive]
pub enum ExternalCoin { pub enum ExternalCoin {
/// Bitcoin, from the Bitcoin network. /// Bitcoin, from the Bitcoin network.
@@ -31,6 +35,10 @@ impl ExternalCoin {
/// The type used to identify coins. /// The type used to identify coins.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Zeroize)] #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Zeroize)]
#[cfg_attr(
feature = "non_canonical_scale_derivations",
derive(scale::Encode, scale::Decode, scale::MaxEncodedLen)
)]
pub enum Coin { pub enum Coin {
/// The Serai coin. /// The Serai coin.
Serai, Serai,

View File

@@ -67,7 +67,8 @@ impl UnbalancedMerkleTree {
} }
/// An unbalanced Merkle tree which is incrementally created. /// An unbalanced Merkle tree which is incrementally created.
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)] #[derive(Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "non_canonical_scale_derivations", derive(scale::Encode, scale::Decode))]
pub struct IncrementalUnbalancedMerkleTree { pub struct IncrementalUnbalancedMerkleTree {
/// (number of children under branch, branch hash) /// (number of children under branch, branch hash)
branches: Vec<(u64, [u8; 32])>, branches: Vec<(u64, [u8; 32])>,

View File

@@ -1,7 +1,6 @@
use core::marker::PhantomData; use core::marker::PhantomData;
use alloc::{vec, vec::Vec};
use borsh::{BorshSerialize, BorshDeserialize}; use borsh::BorshSerialize;
use frame_support::pallet_prelude::*; use frame_support::pallet_prelude::*;
@@ -10,13 +9,14 @@ use serai_abi::{
*, *,
}; };
/// A wrapper around a `StorageValue` which offers a high-level API as an IUMT.
struct IncrementalUnbalancedMerkleTree< struct IncrementalUnbalancedMerkleTree<
T: frame_support::StorageValue<Vec<u8>, Query = Option<Vec<u8>>>, T: frame_support::StorageValue<Iumt, Query = Option<Iumt>>,
const BRANCH_TAG: u8 = 1, const BRANCH_TAG: u8 = 1,
const LEAF_TAG: u8 = 0, const LEAF_TAG: u8 = 0,
>(PhantomData<T>); >(PhantomData<T>);
impl< impl<
T: frame_support::StorageValue<Vec<u8>, Query = Option<Vec<u8>>>, T: frame_support::StorageValue<Iumt, Query = Option<Iumt>>,
const BRANCH_TAG: u8, const BRANCH_TAG: u8,
const LEAF_TAG: u8, const LEAF_TAG: u8,
> IncrementalUnbalancedMerkleTree<T, BRANCH_TAG, LEAF_TAG> > IncrementalUnbalancedMerkleTree<T, BRANCH_TAG, LEAF_TAG>
@@ -27,7 +27,7 @@ impl<
fn new_expecting_none() { fn new_expecting_none() {
T::mutate(|value| { T::mutate(|value| {
assert!(value.is_none()); assert!(value.is_none());
*value = Some(borsh::to_vec(&Iumt::new()).unwrap()); *value = Some(Iumt::new());
}); });
} }
/// Append a leaf to the Merkle tree. /// Append a leaf to the Merkle tree.
@@ -37,26 +37,21 @@ impl<
let leaf = sp_core::blake2_256(&borsh::to_vec(&(LEAF_TAG, leaf)).unwrap()); let leaf = sp_core::blake2_256(&borsh::to_vec(&(LEAF_TAG, leaf)).unwrap());
T::mutate(|value| { T::mutate(|value| {
let mut tree = Iumt::deserialize_reader(&mut value.as_ref().unwrap().as_slice()).unwrap(); let tree = value.as_mut().unwrap();
tree.append(BRANCH_TAG, leaf); tree.append(BRANCH_TAG, leaf);
*value = Some(borsh::to_vec(&tree).unwrap());
}) })
} }
/// Get the unbalanced merkle tree. /// Get the unbalanced merkle tree.
/// ///
/// Panics if no Merkle tree was present. /// Panics if no Merkle tree was present.
fn get() -> UnbalancedMerkleTree { fn get() -> UnbalancedMerkleTree {
Iumt::deserialize_reader(&mut T::get().unwrap().as_slice()).unwrap().calculate(BRANCH_TAG) T::get().unwrap().calculate(BRANCH_TAG)
} }
/// Take the Merkle tree. /// Take the Merkle tree.
/// ///
/// Panics if no Merkle tree was present. /// Panics if no Merkle tree was present.
fn take() -> UnbalancedMerkleTree { fn take() -> UnbalancedMerkleTree {
T::mutate(|value| { T::mutate(|value| value.take().unwrap().calculate(BRANCH_TAG))
let tree = Iumt::deserialize_reader(&mut value.as_ref().unwrap().as_slice()).unwrap();
*value = None;
tree.calculate(BRANCH_TAG)
})
} }
} }
@@ -70,20 +65,20 @@ mod pallet {
/// The Merkle tree of all blocks added to the blockchain. /// The Merkle tree of all blocks added to the blockchain.
#[pallet::storage] #[pallet::storage]
#[pallet::unbounded] #[pallet::unbounded]
pub(super) type BlocksCommitment<T: Config> = StorageValue<_, Vec<u8>, OptionQuery>; pub(super) type BlocksCommitment<T: Config> = StorageValue<_, Iumt, OptionQuery>;
pub(super) type BlocksCommitmentMerkle<T> = IncrementalUnbalancedMerkleTree<BlocksCommitment<T>>; pub(super) type BlocksCommitmentMerkle<T> = IncrementalUnbalancedMerkleTree<BlocksCommitment<T>>;
/// The Merkle tree of all transactions within the current block. /// The Merkle tree of all transactions within the current block.
#[pallet::storage] #[pallet::storage]
#[pallet::unbounded] #[pallet::unbounded]
pub(super) type BlockTransactionsCommitment<T: Config> = StorageValue<_, Vec<u8>, OptionQuery>; pub(super) type BlockTransactionsCommitment<T: Config> = StorageValue<_, Iumt, OptionQuery>;
pub(super) type BlockTransactionsCommitmentMerkle<T> = pub(super) type BlockTransactionsCommitmentMerkle<T> =
IncrementalUnbalancedMerkleTree<BlockTransactionsCommitment<T>>; IncrementalUnbalancedMerkleTree<BlockTransactionsCommitment<T>>;
/// The hashes of events caused by the current transaction. /// The hashes of events caused by the current transaction.
#[pallet::storage] #[pallet::storage]
#[pallet::unbounded] #[pallet::unbounded]
pub(super) type TransactionEvents<T: Config> = StorageValue<_, Vec<u8>, OptionQuery>; pub(super) type TransactionEvents<T: Config> = StorageValue<_, Iumt, OptionQuery>;
pub(super) type TransactionEventsMerkle<T> = IncrementalUnbalancedMerkleTree< pub(super) type TransactionEventsMerkle<T> = IncrementalUnbalancedMerkleTree<
TransactionEvents<T>, TransactionEvents<T>,
TRANSACTION_EVENTS_COMMITMENT_BRANCH_TAG, TRANSACTION_EVENTS_COMMITMENT_BRANCH_TAG,
@@ -92,7 +87,7 @@ mod pallet {
/// The roots of the Merkle trees of each transaction's events. /// The roots of the Merkle trees of each transaction's events.
#[pallet::storage] #[pallet::storage]
#[pallet::unbounded] #[pallet::unbounded]
pub(super) type BlockEventsCommitment<T: Config> = StorageValue<_, Vec<u8>, OptionQuery>; pub(super) type BlockEventsCommitment<T: Config> = StorageValue<_, Iumt, OptionQuery>;
pub(super) type BlockEventsCommitmentMerkle<T> = IncrementalUnbalancedMerkleTree< pub(super) type BlockEventsCommitmentMerkle<T> = IncrementalUnbalancedMerkleTree<
BlockEventsCommitment<T>, BlockEventsCommitment<T>,
EVENTS_COMMITMENT_BRANCH_TAG, EVENTS_COMMITMENT_BRANCH_TAG,
@@ -105,12 +100,7 @@ mod pallet {
StorageMap<_, Blake2_128Concat, T::AccountId, T::Nonce, ValueQuery>; StorageMap<_, Blake2_128Concat, T::AccountId, T::Nonce, ValueQuery>;
#[pallet::config] #[pallet::config]
pub trait Config: pub trait Config: frame_system::Config<Hash: Into<[u8; 32]>> {}
frame_system::Config<
Block: sp_runtime::traits::Block<Header: sp_runtime::traits::Header<Hash: Into<[u8; 32]>>>,
>
{
}
#[pallet::pallet] #[pallet::pallet]
pub struct Pallet<T>(_); pub struct Pallet<T>(_);
@@ -141,12 +131,11 @@ pub(super) use pallet::*;
pub struct StartOfBlock<T: Config>(PhantomData<T>); pub struct StartOfBlock<T: Config>(PhantomData<T>);
impl<T: Config> frame_support::traits::PreInherents for StartOfBlock<T> { impl<T: Config> frame_support::traits::PreInherents for StartOfBlock<T> {
fn pre_inherents() { fn pre_inherents() {
let parent_hash = frame_system::Pallet::<T>::parent_hash(); if frame_system::Pallet::<T>::block_number().is_zero() {
Blocks::<T>::set(parent_hash, Some(()));
// TODO: Better detection of genesis
if parent_hash == Default::default() {
BlocksCommitmentMerkle::<T>::new_expecting_none(); BlocksCommitmentMerkle::<T>::new_expecting_none();
} else { } else {
let parent_hash = frame_system::Pallet::<T>::parent_hash();
Blocks::<T>::set(parent_hash, Some(()));
let parent_hash: [u8; 32] = parent_hash.into(); let parent_hash: [u8; 32] = parent_hash.into();
BlocksCommitmentMerkle::<T>::append(&parent_hash); BlocksCommitmentMerkle::<T>::append(&parent_hash);
} }