From ff95c5834134e26f9569e77e7482058999cf0673 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 2 Dec 2025 21:04:47 -0500 Subject: [PATCH] Round out the runtime Ensures the block's size limit is respected. Defines a policy for weights. While I'm unsure I want to commit to this forever, I do want to acknowledge it's valid and well-defined. Cleans up the `serai-runtime` crate a bit with further modules in the `wasm` folder. --- substrate/abi/src/block.rs | 21 +- substrate/abi/src/transaction.rs | 33 +-- substrate/coins/src/mock.rs | 2 + substrate/coins/src/tests.rs | 6 +- substrate/core/src/lib.rs | 38 +++- substrate/core/src/limits.rs | 34 +++ substrate/dex/src/mock.rs | 2 + substrate/runtime/src/wasm/map.rs | 127 +++++++++++ substrate/runtime/src/wasm/mod.rs | 314 ++------------------------- substrate/runtime/src/wasm/system.rs | 95 ++++++++ 10 files changed, 347 insertions(+), 325 deletions(-) create mode 100644 substrate/core/src/limits.rs create mode 100644 substrate/runtime/src/wasm/map.rs create mode 100644 substrate/runtime/src/wasm/system.rs diff --git a/substrate/abi/src/block.rs b/substrate/abi/src/block.rs index e364c16e..a2d38a74 100644 --- a/substrate/abi/src/block.rs +++ b/substrate/abi/src/block.rs @@ -63,6 +63,11 @@ pub struct HeaderV1 { pub consensus_commitment: [u8; 32], } +impl HeaderV1 { + /// The size of a serialized V1 header. + pub const SIZE: usize = 8 + 32 + 8 + 32 + 32 + 32; +} + /// A header for a block. #[derive(Clone, Copy, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)] pub enum Header { @@ -71,6 +76,9 @@ pub enum Header { } impl Header { + /// The size of a serialized header. + pub const SIZE: usize = 1 + HeaderV1::SIZE; + /// Get the hash of the header. pub fn number(&self) -> u64 { match self { @@ -109,8 +117,8 @@ impl Header { /// A block. /// -/// This does not guarantee consistency. The header's `transactions_root` may not match the -/// contained transactions. +/// This does not guarantee consistency nor validity. The header's `transactions_root` may not +/// match the contained transactions, among other ill effects. #[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)] pub struct Block { /// The block's header. @@ -119,6 +127,13 @@ pub struct Block { pub transactions: Vec, } +impl Block { + /// The size limit for a block. + /// + /// This is not enforced upon deserialization. Be careful accordingly. + pub const SIZE_LIMIT: usize = 1024 * 1024; +} + #[cfg(feature = "substrate")] mod substrate { use core::fmt::Debug; @@ -133,7 +148,7 @@ mod substrate { use super::*; - // Add `serde` implementations which treat self as a `Vec` + // Add `serde` implementations which treat `self` as a `Vec` impl sp_core::serde::Serialize for Transaction { fn serialize(&self, serializer: S) -> Result where diff --git a/substrate/abi/src/transaction.rs b/substrate/abi/src/transaction.rs index 112c87b5..84183db4 100644 --- a/substrate/abi/src/transaction.rs +++ b/substrate/abi/src/transaction.rs @@ -279,12 +279,13 @@ mod substrate { /// The implicit context to verify transactions with. fn implicit_context() -> ImplicitContext; + /// The size of the current block. + fn current_block_size(&self) -> usize; + /// If a block is present in the blockchain. fn block_is_present_in_blockchain(&self, hash: &BlockHash) -> bool; /// The time embedded into the current block. - /// - /// Returns `None` if the time has yet to be set. - fn current_time(&self) -> Option; + fn current_time(&self) -> u64; /// Get the next nonce for an account. fn next_nonce(&self, signer: &SeraiAddress) -> u32; /// If the signer can pay the SRI fee. @@ -295,7 +296,7 @@ mod substrate { ) -> Result<(), TransactionValidityError>; /// Begin execution of a transaction. - fn start_transaction(&self); + fn start_transaction(&self, len: usize); /// 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. @@ -390,9 +391,14 @@ mod substrate { impl TransactionWithContext { fn validate_except_fee>( &self, + len: usize, source: TransactionSource, mempool_priority_if_signed: u64, ) -> TransactionValidity { + if self.1.current_block_size().saturating_add(len) > crate::Block::SIZE_LIMIT { + Err(TransactionValidityError::Invalid(InvalidTransaction::ExhaustsResources))?; + } + match &self.0 { Transaction::Unsigned { call } => { let ValidTransaction { priority: _, requires, provides, longevity: _, propagate: _ } = @@ -417,13 +423,8 @@ mod substrate { Err(TransactionValidityError::Unknown(UnknownTransaction::CannotLookup))?; } if let Some(include_by) = *include_by { - if let Some(current_time) = self.1.current_time() { - if current_time >= u64::from(include_by) { - // Since this transaction has a time bound which has passed, error - Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))?; - } - } else { - // Since this transaction has a time bound, yet we don't know the time, error + if self.1.current_time() >= u64::from(include_by) { + // Since this transaction has a time bound which has passed, error Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))?; } } @@ -471,7 +472,7 @@ mod substrate { &self, source: TransactionSource, info: &DispatchInfo, - _len: usize, + len: usize, ) -> TransactionValidity { let mempool_priority_if_signed = match &self.0 { Transaction::Unsigned { .. } => { @@ -493,19 +494,19 @@ mod substrate { } } }; - self.validate_except_fee::(source, mempool_priority_if_signed) + self.validate_except_fee::(len, source, mempool_priority_if_signed) } fn apply>( self, _info: &DispatchInfo, - _len: usize, + len: usize, ) -> sp_runtime::ApplyExtrinsicResultWithInfo { // We use 0 for the mempool priority, as this is no longer in the mempool so it's irrelevant - self.validate_except_fee::(TransactionSource::InBlock, 0)?; + self.validate_except_fee::(len, TransactionSource::InBlock, 0)?; // Start the transaction - self.1.start_transaction(); + self.1.start_transaction(len); let transaction_hash = self.0.hash(); diff --git a/substrate/coins/src/mock.rs b/substrate/coins/src/mock.rs index fa8e35ec..4ebca7b5 100644 --- a/substrate/coins/src/mock.rs +++ b/substrate/coins/src/mock.rs @@ -21,6 +21,8 @@ impl frame_system::Config for Test { type AccountId = sp_core::sr25519::Public; type Lookup = frame_support::sp_runtime::traits::IdentityLookup; type Block = frame_system::mocking::MockBlock; + type BlockLength = serai_core_pallet::Limits; + type BlockWeights = serai_core_pallet::Limits; } #[derive_impl(pallet_timestamp::config_preludes::TestDefaultConfig)] diff --git a/substrate/coins/src/tests.rs b/substrate/coins/src/tests.rs index 35bd3628..daf2af97 100644 --- a/substrate/coins/src/tests.rs +++ b/substrate/coins/src/tests.rs @@ -10,7 +10,7 @@ pub type CoinsEvent = serai_abi::coins::Event; #[test] fn mint() { new_test_ext().execute_with(|| { - Core::start_transaction(); + Core::start_transaction(0); // minting u64::MAX should work let coin = Coin::Serai; @@ -51,7 +51,7 @@ fn mint() { #[test] fn burn_with_instruction() { new_test_ext().execute_with(|| { - Core::start_transaction(); + Core::start_transaction(0); // mint some coin let coin = Coin::External(ExternalCoin::Bitcoin); @@ -106,7 +106,7 @@ fn burn_with_instruction() { #[test] fn transfer() { new_test_ext().execute_with(|| { - Core::start_transaction(); + Core::start_transaction(0); // mint some coin let coin = Coin::External(ExternalCoin::Bitcoin); diff --git a/substrate/core/src/lib.rs b/substrate/core/src/lib.rs index f3f051dd..708f9641 100644 --- a/substrate/core/src/lib.rs +++ b/substrate/core/src/lib.rs @@ -8,6 +8,9 @@ extern crate alloc; use frame_support::traits::{PreInherents, PostTransactions}; +mod limits; +pub use limits::Limits; + mod iumt; pub use iumt::*; @@ -83,7 +86,8 @@ pub mod pallet { #[pallet::config] pub trait Config: - frame_system::Config> + pallet_timestamp::Config + frame_system::Config, BlockLength = Limits, BlockWeights = Limits> + + pallet_timestamp::Config { } @@ -120,7 +124,7 @@ pub mod pallet { BlockTransactionsCommitmentMerkle::::new_expecting_none(); BlockEventsCommitmentMerkle::::new_expecting_none(); - Self::start_transaction(); + Self::start_transaction(0); <_>::build(config); Self::end_transaction([0; 32]); @@ -130,7 +134,15 @@ pub mod pallet { /// The code to run when beginning execution of a transaction. /// /// The caller MUST ensure two transactions aren't simultaneously started. - pub fn start_transaction() { + pub fn start_transaction(len: usize) { + { + let existing_len = frame_system::AllExtrinsicsLen::::get().unwrap_or(0); + let new_len = existing_len.saturating_add(u32::try_from(len).unwrap_or(u32::MAX)); + // We panic here as this should've been caught earlier during validation + assert!(new_len <= u32::try_from(serai_abi::Block::SIZE_LIMIT).unwrap()); + frame_system::AllExtrinsicsLen::::set(Some(new_len)); + } + TransactionEventsMerkle::::new_expecting_none(); Self::deposit_event(Event::BeginTransaction); } @@ -192,7 +204,21 @@ impl PreInherents for StartOfBlock { BlockTransactionsCommitmentMerkle::::new_expecting_none(); BlockEventsCommitmentMerkle::::new_expecting_none(); - Pallet::::start_transaction(); + /* + We assign the implicit transaction with the block the length of the block itself: its + header's length and the length of the length-prefix for the list of transactions. + + The length-prefix will be a little-endian `u32`, as `Block` will be borsh-serialized + (https://borsh.io). + + The length of each actual transaction is expected to be accurate as the SCALE implementation + defers to the `borsh` serialization. + */ + assert!( + frame_system::AllExtrinsicsLen::::get().is_none(), + "AllExtrinsicsLen wasn't killed at the end of the last block" + ); + Pallet::::start_transaction(serai_abi::Header::SIZE + 4); // Handle the `SeraiPreExecutionDigest` /* @@ -220,7 +246,7 @@ impl PreInherents for StartOfBlock { pub struct EndOfBlock(PhantomData); impl PostTransactions for EndOfBlock { fn post_transactions() { - Pallet::::start_transaction(); + Pallet::::start_transaction(0); // Other modules' `PostTransactions` @@ -229,6 +255,8 @@ impl PostTransactions for EndOfBlock { end_of_block_transaction_hash[.. 16].copy_from_slice(&[0xff; 16]); Pallet::::end_transaction(end_of_block_transaction_hash); + frame_system::AllExtrinsicsLen::::kill(); + use serai_abi::SeraiExecutionDigest; frame_system::Pallet::::deposit_log( frame_support::sp_runtime::generic::DigestItem::Consensus( diff --git a/substrate/core/src/limits.rs b/substrate/core/src/limits.rs new file mode 100644 index 00000000..de5db67c --- /dev/null +++ b/substrate/core/src/limits.rs @@ -0,0 +1,34 @@ +use sp_core::Get; +use frame_support::weights::Weight; +use frame_system::limits::{BlockLength, BlockWeights}; + +/// The limits for the Serai protocol. +pub struct Limits; +impl Get for Limits { + fn get() -> BlockLength { + /* + We do not reserve an allocation for mandatory/operational transactions, assuming they'll be + prioritized in the mempool. This does technically give block producers an inventive to + misbehave by on-purposely favoring paying non-operational transactions over operational + transactions, but ensures the entire block is available to the transactions actually present + in the mempool. + */ + BlockLength::max(u32::try_from(serai_abi::Block::SIZE_LIMIT).unwrap()) + } +} +impl Get for Limits { + fn get() -> BlockWeights { + /* + While Serai does limit the size of a block, every transaction is expected to operate in + complexity constant to the current state size, regardless of what the state is. Accordingly, + the most efficient set of transactions (basic transfers?) is expected to be within an order + of magnitude of the most expensive transactions (multi-pool swaps?). + + Instead of engaging with the complexity within the consensus protocol of metering both + bandwidth and computation, we do not define limits for weights. We do, however, still use the + weight system in order to determine fee rates and ensure prioritization to + computationally-cheaper transactions. That solely serves as mempool policy however. + */ + BlockWeights::simple_max(Weight::MAX) + } +} diff --git a/substrate/dex/src/mock.rs b/substrate/dex/src/mock.rs index 03a76144..41a70142 100644 --- a/substrate/dex/src/mock.rs +++ b/substrate/dex/src/mock.rs @@ -25,6 +25,8 @@ impl frame_system::Config for Test { type AccountId = sp_core::sr25519::Public; type Lookup = frame_support::sp_runtime::traits::IdentityLookup; type Block = frame_system::mocking::MockBlock; + type BlockLength = serai_core_pallet::Limits; + type BlockWeights = serai_core_pallet::Limits; } #[derive_impl(pallet_timestamp::config_preludes::TestDefaultConfig)] diff --git a/substrate/runtime/src/wasm/map.rs b/substrate/runtime/src/wasm/map.rs new file mode 100644 index 00000000..d9cc9afc --- /dev/null +++ b/substrate/runtime/src/wasm/map.rs @@ -0,0 +1,127 @@ +use super::*; + +impl From> for RuntimeOrigin { + fn from(signer: Option) -> Self { + match signer { + None => RuntimeOrigin::none(), + Some(signer) => RuntimeOrigin::signed(signer.into()), + } + } +} + +impl From for RuntimeCall { + fn from(call: serai_abi::Call) -> Self { + match call { + serai_abi::Call::Coins(call) => { + use serai_abi::coins::Call; + use serai_coins_pallet::Call as Scall; + RuntimeCall::Coins(match call { + Call::transfer { to, coins } => Scall::transfer { to: to.into(), coins }, + Call::burn { coins } => Scall::burn { coins }, + Call::burn_with_instruction { instruction } => { + Scall::burn_with_instruction { instruction } + } + }) + } + serai_abi::Call::ValidatorSets(call) => { + use serai_abi::validator_sets::Call; + use serai_validator_sets_pallet::Call as Scall; + RuntimeCall::ValidatorSets(match call { + Call::set_keys { network, key_pair, signature_participants, signature } => { + Scall::set_keys { network, key_pair, signature_participants, signature } + } + Call::report_slashes { network, slashes, signature } => { + Scall::report_slashes { network, slashes, signature } + } + Call::set_embedded_elliptic_curve_keys { keys } => { + Scall::set_embedded_elliptic_curve_keys { keys } + } + Call::allocate { network, amount } => Scall::allocate { network, amount }, + Call::deallocate { network, amount } => Scall::deallocate { network, amount }, + Call::claim_deallocation { deallocation } => Scall::claim_deallocation { + network: deallocation.network, + session: deallocation.session, + }, + }) + } + serai_abi::Call::Signals(call) => { + use serai_abi::signals::Call; + use serai_signals_pallet::Call as Scall; + RuntimeCall::Signals(match call { + Call::register_retirement_signal { in_favor_of } => { + Scall::register_retirement_signal { in_favor_of } + } + Call::revoke_retirement_signal { was_in_favor_of } => { + Scall::revoke_retirement_signal { retirement_signal: was_in_favor_of } + } + Call::favor { signal, with_network } => Scall::favor { signal, with_network }, + Call::revoke_favor { signal, with_network } => { + Scall::revoke_favor { signal, with_network } + } + Call::stand_against { signal, with_network } => { + Scall::stand_against { signal, with_network } + } + }) + } + serai_abi::Call::Dex(call) => { + use serai_abi::dex::Call; + RuntimeCall::Dex(match call { + Call::add_liquidity { + external_coin, + sri_intended, + external_coin_intended, + sri_minimum, + external_coin_minimum, + } => serai_dex_pallet::Call::add_liquidity { + external_coin, + sri_intended, + external_coin_intended, + sri_minimum, + external_coin_minimum, + }, + Call::transfer_liquidity { to, liquidity_tokens } => { + serai_dex_pallet::Call::transfer_liquidity { to, liquidity_tokens } + } + Call::remove_liquidity { liquidity_tokens, sri_minimum, external_coin_minimum } => { + serai_dex_pallet::Call::remove_liquidity { + liquidity_tokens, + sri_minimum, + external_coin_minimum, + } + } + Call::swap { coins_to_swap, minimum_to_receive } => { + serai_dex_pallet::Call::swap { coins_to_swap, minimum_to_receive } + } + Call::swap_for { coins_to_receive, maximum_to_swap } => { + serai_dex_pallet::Call::swap_for { coins_to_receive, maximum_to_swap } + } + }) + } + serai_abi::Call::GenesisLiquidity(call) => { + use serai_abi::genesis_liquidity::Call; + RuntimeCall::GenesisLiquidity(match call { + Call::oraclize_values { values, signature } => { + serai_genesis_liquidity_pallet::Call::oraclize_values { values, signature } + } + Call::transfer_genesis_liquidity { to, genesis_liquidity } => { + serai_genesis_liquidity_pallet::Call::transfer_genesis_liquidity { + to, + genesis_liquidity, + } + } + Call::remove_genesis_liquidity { genesis_liquidity } => { + serai_genesis_liquidity_pallet::Call::remove_genesis_liquidity { genesis_liquidity } + } + }) + } + serai_abi::Call::InInstructions(call) => { + use serai_abi::in_instructions::Call; + RuntimeCall::InInstructions(match call { + Call::execute_batch { batch } => { + serai_in_instructions_pallet::Call::execute_batch { batch } + } + }) + } + } + } +} diff --git a/substrate/runtime/src/wasm/mod.rs b/substrate/runtime/src/wasm/mod.rs index 00eff9da..b8c136f5 100644 --- a/substrate/runtime/src/wasm/mod.rs +++ b/substrate/runtime/src/wasm/mod.rs @@ -1,7 +1,7 @@ use core::marker::PhantomData; use alloc::{borrow::Cow, vec, vec::Vec}; -use sp_core::{ConstU32, ConstU64, sr25519::Public}; +use sp_core::{Get, ConstU32, ConstU64, sr25519::Public}; use sp_runtime::{ Perbill, Weight, traits::{Header as _, Block as _}, @@ -21,51 +21,10 @@ use serai_abi::{ use serai_coins_pallet::{CoinsInstance, LiquidityTokensInstance}; -/// The lookup for a SeraiAddress -> Public. -pub struct Lookup; -impl sp_runtime::traits::StaticLookup for Lookup { - type Source = SeraiAddress; - type Target = Public; - fn lookup(source: SeraiAddress) -> Result { - Ok(source.into()) - } - fn unlookup(source: Public) -> SeraiAddress { - source.into() - } -} - -// TODO: Remove -#[sp_version::runtime_version] -pub const VERSION: RuntimeVersion = RuntimeVersion { - spec_name: Cow::Borrowed("serai"), - impl_name: Cow::Borrowed("core"), - authoring_version: 0, - spec_version: 0, - impl_version: 0, - apis: RUNTIME_API_VERSIONS, - transaction_version: 0, - system_version: 0, -}; - -frame_support::parameter_types! { - pub const Version: RuntimeVersion = VERSION; - - // TODO - pub BlockLength: frame_system::limits::BlockLength = - frame_system::limits::BlockLength::max_with_normal_ratio( - 100 * 1024, - Perbill::from_percent(75), - ); - // TODO - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::with_sensible_defaults( - Weight::from_parts( - 2u64 * frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, - u64::MAX, - ), - Perbill::from_percent(75), - ); -} +/// Maps `serai_abi` types into the types expected within the Substrate runtime +mod map; +/// The configuration for `frame_system`. +mod system; #[frame_support::runtime] mod runtime { @@ -113,45 +72,6 @@ mod runtime { pub type Grandpa = pallet_grandpa::Pallet; } -impl frame_system::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = BlockWeights; - type BlockLength = BlockLength; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u32; - type Hash = ::Hash; - type Hashing = sp_runtime::traits::BlakeTwo256; - type AccountId = sp_core::sr25519::Public; - type Lookup = Lookup; - type Block = Block; - // Don't track old block hashes within the System pallet - // We use not a number -> hash index, but a hash -> () index, in our own pallet - type BlockHashCount = ConstU64<1>; - type DbWeight = frame_support::weights::constants::RocksDbWeight; - type Version = Version; - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - // We use the default weights as we never expose/call any of these methods - type SystemWeightInfo = (); - // We also don't use the provided extensions framework - type ExtensionsWeightInfo = (); - // We don't invoke any hooks on-set-code as we don't perform upgrades via the blockchain yet via - // nodes, ensuring everyone who upgrades consents to the rules they upgrade to - type OnSetCode = (); - type MaxConsumers = ConstU32<{ u32::MAX }>; - // No migrations set - type SingleBlockMigrations = (); - type MultiBlockMigrator = (); - - type PreInherents = serai_core_pallet::StartOfBlock; - type PostInherents = (); - type PostTransactions = serai_core_pallet::EndOfBlock; -} - impl serai_core_pallet::Config for Runtime {} impl serai_coins_pallet::Config for Runtime { @@ -237,137 +157,6 @@ impl pallet_grandpa::Config for Runtime { type EquivocationReportSystem = (); } -impl From> for RuntimeOrigin { - fn from(signer: Option) -> Self { - match signer { - None => RuntimeOrigin::none(), - Some(signer) => RuntimeOrigin::signed(signer.into()), - } - } -} - -impl From for RuntimeCall { - fn from(call: serai_abi::Call) -> Self { - match call { - serai_abi::Call::Coins(call) => { - use serai_abi::coins::Call; - use serai_coins_pallet::Call as Scall; - RuntimeCall::Coins(match call { - Call::transfer { to, coins } => Scall::transfer { to: to.into(), coins }, - Call::burn { coins } => Scall::burn { coins }, - Call::burn_with_instruction { instruction } => { - Scall::burn_with_instruction { instruction } - } - }) - } - serai_abi::Call::ValidatorSets(call) => { - use serai_abi::validator_sets::Call; - use serai_validator_sets_pallet::Call as Scall; - RuntimeCall::ValidatorSets(match call { - Call::set_keys { network, key_pair, signature_participants, signature } => { - Scall::set_keys { network, key_pair, signature_participants, signature } - } - Call::report_slashes { network, slashes, signature } => { - Scall::report_slashes { network, slashes, signature } - } - Call::set_embedded_elliptic_curve_keys { keys } => { - Scall::set_embedded_elliptic_curve_keys { keys } - } - Call::allocate { network, amount } => Scall::allocate { network, amount }, - Call::deallocate { network, amount } => Scall::deallocate { network, amount }, - Call::claim_deallocation { deallocation } => Scall::claim_deallocation { - network: deallocation.network, - session: deallocation.session, - }, - }) - } - serai_abi::Call::Signals(call) => { - use serai_abi::signals::Call; - use serai_signals_pallet::Call as Scall; - RuntimeCall::Signals(match call { - Call::register_retirement_signal { in_favor_of } => { - Scall::register_retirement_signal { in_favor_of } - } - Call::revoke_retirement_signal { was_in_favor_of } => { - Scall::revoke_retirement_signal { retirement_signal: was_in_favor_of } - } - Call::favor { signal, with_network } => Scall::favor { signal, with_network }, - Call::revoke_favor { signal, with_network } => { - Scall::revoke_favor { signal, with_network } - } - Call::stand_against { signal, with_network } => { - Scall::stand_against { signal, with_network } - } - }) - } - serai_abi::Call::Dex(call) => { - use serai_abi::dex::Call; - match call { - Call::add_liquidity { - external_coin, - sri_intended, - external_coin_intended, - sri_minimum, - external_coin_minimum, - } => RuntimeCall::Dex(serai_dex_pallet::Call::add_liquidity { - external_coin, - sri_intended, - external_coin_intended, - sri_minimum, - external_coin_minimum, - }), - Call::transfer_liquidity { to, liquidity_tokens } => { - RuntimeCall::Dex(serai_dex_pallet::Call::transfer_liquidity { to, liquidity_tokens }) - } - Call::remove_liquidity { liquidity_tokens, sri_minimum, external_coin_minimum } => { - RuntimeCall::Dex(serai_dex_pallet::Call::remove_liquidity { - liquidity_tokens, - sri_minimum, - external_coin_minimum, - }) - } - Call::swap { coins_to_swap, minimum_to_receive } => { - RuntimeCall::Dex(serai_dex_pallet::Call::swap { coins_to_swap, minimum_to_receive }) - } - Call::swap_for { coins_to_receive, maximum_to_swap } => { - RuntimeCall::Dex(serai_dex_pallet::Call::swap_for { coins_to_receive, maximum_to_swap }) - } - } - } - serai_abi::Call::GenesisLiquidity(call) => { - use serai_abi::genesis_liquidity::Call; - match call { - Call::oraclize_values { values, signature } => { - RuntimeCall::GenesisLiquidity(serai_genesis_liquidity_pallet::Call::oraclize_values { - values, - signature, - }) - } - Call::transfer_genesis_liquidity { to, genesis_liquidity } => { - RuntimeCall::GenesisLiquidity( - serai_genesis_liquidity_pallet::Call::transfer_genesis_liquidity { - to, - genesis_liquidity, - }, - ) - } - Call::remove_genesis_liquidity { genesis_liquidity } => RuntimeCall::GenesisLiquidity( - serai_genesis_liquidity_pallet::Call::remove_genesis_liquidity { genesis_liquidity }, - ), - } - } - serai_abi::Call::InInstructions(call) => { - use serai_abi::in_instructions::Call; - match call { - Call::execute_batch { batch } => { - RuntimeCall::InInstructions(serai_in_instructions_pallet::Call::execute_batch { batch }) - } - } - } - } - } -} - type Executive = frame_executive::Executive; const PRIMARY_PROBABILITY: (u64, u64) = (1, 4); @@ -411,7 +200,7 @@ sp_api::impl_runtime_apis! { impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { - VERSION + ::Version::get() } fn initialize_block(header: &Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) @@ -667,13 +456,19 @@ impl serai_abi::TransactionContext for Context { } } + /// The size of the current block. + fn current_block_size(&self) -> usize { + let current_block_size = frame_system::AllExtrinsicsLen::::get().unwrap_or(0); + usize::try_from(current_block_size).unwrap_or(usize::MAX) + } + /// If a block is present in the blockchain. fn block_is_present_in_blockchain(&self, hash: &serai_abi::primitives::BlockHash) -> bool { serai_core_pallet::Pallet::::block_exists(hash) } /// The time embedded into the current block. - fn current_time(&self) -> Option { - todo!("TODO") + fn current_time(&self) -> u64 { + pallet_timestamp::Pallet::::get() } /// Get the next nonce for an account. fn next_nonce(&self, signer: &SeraiAddress) -> u32 { @@ -695,8 +490,8 @@ impl serai_abi::TransactionContext for Context { } } - fn start_transaction(&self) { - Core::start_transaction() + fn start_transaction(&self, len: usize) { + Core::start_transaction(len) } fn consume_next_nonce(&self, signer: &SeraiAddress) { serai_core_pallet::Pallet::::consume_next_nonce(signer) @@ -726,26 +521,7 @@ impl serai_abi::TransactionContext for Context { /* TODO use validator_sets::MembershipProof; -const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); - -parameter_types! { - pub const Version: RuntimeVersion = VERSION; - - pub const SS58Prefix: u8 = 42; // TODO: Remove for Bech32m - - // 1 MB block size limit - pub BlockLength: system::limits::BlockLength = - system::limits::BlockLength::max_with_normal_ratio(BLOCK_SIZE, NORMAL_DISPATCH_RATIO); - pub BlockWeights: system::limits::BlockWeights = - system::limits::BlockWeights::with_sensible_defaults( - Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), - NORMAL_DISPATCH_RATIO, - ); -} - impl timestamp::Config for Runtime { - type Moment = u64; - type OnTimestampSet = Babe; type MinimumPeriod = ConstU64<{ (TARGET_BLOCK_TIME * 1000) / 2 }>; type WeightInfo = (); } @@ -782,14 +558,6 @@ impl signals::Config for Runtime { type RetirementLockInDuration = ConstU32<{ (2 * 7 * 24 * 60 * 60) / (TARGET_BLOCK_TIME as u32) }>; } -impl in_instructions::Config for Runtime { - type RuntimeEvent = RuntimeEvent; -} - -impl genesis_liquidity::Config for Runtime { - type RuntimeEvent = RuntimeEvent; -} - impl emissions::Config for Runtime { type RuntimeEvent = RuntimeEvent; } @@ -818,54 +586,4 @@ impl pallet_authorship::Config for Runtime { /// Longevity of an offence report. pub type ReportLongevity = ::EpochDuration; - -#[cfg(feature = "runtime-benchmarks")] -#[macro_use] -extern crate frame_benchmarking; - -#[cfg(feature = "runtime-benchmarks")] -mod benches { - define_benchmarks!( - [frame_benchmarking, BaselineBench::] - - [system, SystemBench::] - - [balances, Balances] - - [babe, Babe] - [grandpa, Grandpa] - ); -} - -sp_api::impl_runtime_apis! { - impl validator_sets::ValidatorSetsApi for Runtime { - fn external_network_key(network: ExternalNetworkId) -> Option> { - ValidatorSets::external_network_key(network) - } - } - - impl dex::DexApi for Runtime { - fn quote_price_exact_tokens_for_tokens( - coin1: Coin, - coin2: Coin, - amount: SubstrateAmount, - include_fee: bool - ) -> Option { - Dex::quote_price_exact_tokens_for_tokens(coin1, coin2, amount, include_fee) - } - - fn quote_price_tokens_for_exact_tokens( - coin1: Coin, - coin2: Coin, - amount: SubstrateAmount, - include_fee: bool - ) -> Option { - Dex::quote_price_tokens_for_exact_tokens(coin1, coin2, amount, include_fee) - } - - fn get_reserves(coin1: Coin, coin2: Coin) -> Option<(SubstrateAmount, SubstrateAmount)> { - Dex::get_reserves(&coin1, &coin2).ok() - } - } -} */ diff --git a/substrate/runtime/src/wasm/system.rs b/substrate/runtime/src/wasm/system.rs new file mode 100644 index 00000000..b44b5dec --- /dev/null +++ b/substrate/runtime/src/wasm/system.rs @@ -0,0 +1,95 @@ +use super::*; + +/// The lookup for a SeraiAddress -> Public. +pub struct Lookup; +impl sp_runtime::traits::StaticLookup for Lookup { + type Source = SeraiAddress; + type Target = Public; + fn lookup(source: SeraiAddress) -> Result { + Ok(source.into()) + } + fn unlookup(source: Public) -> SeraiAddress { + source.into() + } +} + +/// The runtime version. +pub struct Version; +// TODO: Are we reasonably able to prune `RuntimeVersion` from Substrate? +impl Get for Version { + fn get() -> RuntimeVersion { + #[sp_version::runtime_version] + pub const VERSION: RuntimeVersion = RuntimeVersion { + spec_name: Cow::Borrowed("serai"), + impl_name: Cow::Borrowed("core"), + authoring_version: 0, + spec_version: 0, + impl_version: 0, + apis: RUNTIME_API_VERSIONS, + transaction_version: 0, + system_version: 0, + }; + VERSION + } +} + +impl frame_system::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + + type Hashing = sp_runtime::traits::BlakeTwo256; + type Hash = ::Hash; + + type Block = Block; + type AccountId = sp_core::sr25519::Public; + type Lookup = Lookup; + type Nonce = u32; + + type PreInherents = serai_core_pallet::StartOfBlock; + type PostInherents = (); + type PostTransactions = serai_core_pallet::EndOfBlock; + + /* + We do not globally filter the types of calls which may be performed. Instead, our ABI only + exposes the calls we want exposed, and each call individually errors if it's called when it + shouldn't be. + */ + type BaseCallFilter = frame_support::traits::Everything; + + /* + We do not have `frame_system` track historical block hashes by their block number. Instead, + `serai_core_pallet` populates a hash set (map of `[u8; 32] -> ()`) of all historical block's + hashes within itself. + + The usage of `1` here is solely as `frame_system` requires it be at least `1`. + */ + type BlockHashCount = ConstU64<1>; + + type Version = Version; + type BlockLength = serai_core_pallet::Limits; + type BlockWeights = serai_core_pallet::Limits; + // We assume `serai-node` will be run using the RocksDB backend + type DbWeight = frame_support::weights::constants::RocksDbWeight; + /* + Serai does not expose `frame_system::Call` nor does it use transaction extensions. We + accordingly have no consequence to using the default weights for these accordingly. + */ + type SystemWeightInfo = (); + type ExtensionsWeightInfo = (); + + // We also don't use `frame_system`'s account system at all, leaving us to bottom these out. + type AccountData = (); + type MaxConsumers = ConstU32<{ u32::MAX }>; + type OnNewAccount = (); + type OnKilledAccount = (); + + // Serai does perform any 'on-chain upgrades' to ensure upgrades are opted into by the entity + // running this node and accordingly consented to + type OnSetCode = (); + + // We do not have any migrations declared + type SingleBlockMigrations = (); + type MultiBlockMigrator = (); +}