sp_domains/
lib.rs

1//! Primitives for domains pallet.
2
3#![cfg_attr(not(feature = "std"), no_std)]
4
5pub mod bundle;
6pub mod bundle_producer_election;
7pub mod core_api;
8pub mod execution_receipt;
9pub mod extrinsics;
10pub mod merkle_tree;
11pub mod offline_operators;
12pub mod proof_provider_and_verifier;
13pub mod storage;
14#[cfg(test)]
15mod tests;
16pub mod valued_trie;
17
18#[cfg(not(feature = "std"))]
19extern crate alloc;
20
21use crate::bundle::{BundleVersion, OpaqueBundle, OpaqueBundles};
22use crate::execution_receipt::ExecutionReceiptVersion;
23use crate::storage::{RawGenesis, StorageKey};
24#[cfg(not(feature = "std"))]
25use alloc::collections::BTreeSet;
26#[cfg(not(feature = "std"))]
27use alloc::string::String;
28#[cfg(not(feature = "std"))]
29use alloc::vec::Vec;
30use bundle_producer_election::{BundleProducerElectionParams, ProofOfElectionError};
31use core::num::ParseIntError;
32use core::ops::{Add, Sub};
33use core::str::FromStr;
34use domain_runtime_primitives::{EVMChainId, EthereumAccountId, MultiAccountId};
35use execution_receipt::{ExecutionReceiptFor, SealedSingletonReceipt};
36use frame_support::storage::storage_prefix;
37use frame_support::{Blake2_128Concat, StorageHasher};
38use hex_literal::hex;
39use parity_scale_codec::{Codec, Decode, Encode, MaxEncodedLen};
40use scale_info::TypeInfo;
41use serde::{Deserialize, Serialize};
42use sp_core::H256;
43use sp_core::crypto::KeyTypeId;
44use sp_core::sr25519::vrf::VrfSignature;
45#[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
46use sp_core::sr25519::vrf::{VrfPreOutput, VrfProof};
47use sp_runtime::generic::OpaqueDigestItemId;
48use sp_runtime::traits::{CheckedAdd, Hash as HashT, Header as HeaderT, NumberFor};
49use sp_runtime::{Digest, DigestItem, Percent};
50use sp_runtime_interface::pass_by;
51use sp_runtime_interface::pass_by::PassBy;
52use sp_std::collections::btree_map::BTreeMap;
53use sp_std::fmt::{Display, Formatter};
54use sp_trie::TrieLayout;
55use sp_version::RuntimeVersion;
56use sp_weights::Weight;
57#[cfg(feature = "std")]
58use std::collections::BTreeSet;
59use subspace_core_primitives::hashes::{Blake3Hash, blake3_hash};
60use subspace_core_primitives::pot::PotOutput;
61use subspace_core_primitives::solutions::bidirectional_distance;
62use subspace_core_primitives::{Randomness, U256};
63use subspace_runtime_primitives::{Balance, Moment};
64
65/// Key type for Operator.
66pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"oper");
67
68/// Extrinsics shuffling seed
69pub const DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT: &[u8] = b"extrinsics-shuffling-seed";
70
71mod app {
72    use super::KEY_TYPE;
73    use sp_application_crypto::{app_crypto, sr25519};
74
75    app_crypto!(sr25519, KEY_TYPE);
76}
77
78// TODO: this runtime constant is not support to update, see https://github.com/autonomys/subspace/issues/2712
79// for more detail about the problem and what we need to do to support it.
80//
81// The domain storage fee multiplier used to charge a higher storage fee to the domain
82// transaction to even out the duplicated/illegal domain transaction storage cost, which
83// can not be eliminated right now.
84pub const DOMAIN_STORAGE_FEE_MULTIPLIER: Balance = 3;
85
86/// An operator authority signature.
87pub type OperatorSignature = app::Signature;
88
89/// An operator authority keypair. Necessarily equivalent to the schnorrkel public key used in
90/// the main executor module. If that ever changes, then this must, too.
91#[cfg(feature = "std")]
92pub type OperatorPair = app::Pair;
93
94/// An operator authority identifier.
95pub type OperatorPublicKey = app::Public;
96
97/// A type that implements `BoundToRuntimeAppPublic`, used for operator signing key.
98pub struct OperatorKey;
99
100impl sp_runtime::BoundToRuntimeAppPublic for OperatorKey {
101    type Public = OperatorPublicKey;
102}
103
104/// Stake weight in the domain bundle election.
105///
106/// Derived from the Balance and can't be smaller than u128.
107pub type StakeWeight = u128;
108
109/// The Trie root of all extrinsics included in a bundle.
110pub type ExtrinsicsRoot = H256;
111
112/// Type alias for Header Hashing.
113pub type HeaderHashingFor<Header> = <Header as HeaderT>::Hashing;
114/// Type alias for Header number.
115pub type HeaderNumberFor<Header> = <Header as HeaderT>::Number;
116/// Type alias for Header hash.
117pub type HeaderHashFor<Header> = <Header as HeaderT>::Hash;
118
119/// Unique identifier of a domain.
120#[derive(
121    Clone,
122    Copy,
123    Debug,
124    Hash,
125    Default,
126    Eq,
127    PartialEq,
128    Ord,
129    PartialOrd,
130    Encode,
131    Decode,
132    TypeInfo,
133    Serialize,
134    Deserialize,
135    MaxEncodedLen,
136)]
137pub struct DomainId(u32);
138
139impl From<u32> for DomainId {
140    #[inline]
141    fn from(x: u32) -> Self {
142        Self(x)
143    }
144}
145
146impl From<DomainId> for u32 {
147    #[inline]
148    fn from(domain_id: DomainId) -> Self {
149        domain_id.0
150    }
151}
152
153impl FromStr for DomainId {
154    type Err = ParseIntError;
155
156    fn from_str(s: &str) -> Result<Self, Self::Err> {
157        s.parse::<u32>().map(Into::into)
158    }
159}
160
161impl Add<DomainId> for DomainId {
162    type Output = Self;
163
164    fn add(self, other: DomainId) -> Self {
165        Self(self.0 + other.0)
166    }
167}
168
169impl Sub<DomainId> for DomainId {
170    type Output = Self;
171
172    fn sub(self, other: DomainId) -> Self {
173        Self(self.0 - other.0)
174    }
175}
176
177impl CheckedAdd for DomainId {
178    fn checked_add(&self, rhs: &Self) -> Option<Self> {
179        self.0.checked_add(rhs.0).map(Self)
180    }
181}
182
183impl DomainId {
184    /// Creates a [`DomainId`].
185    pub const fn new(id: u32) -> Self {
186        Self(id)
187    }
188
189    /// Converts the inner integer to little-endian bytes.
190    pub fn to_le_bytes(&self) -> [u8; 4] {
191        self.0.to_le_bytes()
192    }
193}
194
195impl Display for DomainId {
196    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
197        self.0.fmt(f)
198    }
199}
200
201impl PassBy for DomainId {
202    type PassBy = pass_by::Codec<Self>;
203}
204
205/// Identifier of a chain.
206#[derive(
207    Clone,
208    Copy,
209    Debug,
210    Hash,
211    Eq,
212    PartialEq,
213    Ord,
214    PartialOrd,
215    Encode,
216    Decode,
217    TypeInfo,
218    Serialize,
219    Deserialize,
220    MaxEncodedLen,
221)]
222pub enum ChainId {
223    Consensus,
224    Domain(DomainId),
225}
226
227impl ChainId {
228    #[inline]
229    pub fn consensus_chain_id() -> Self {
230        Self::Consensus
231    }
232
233    #[inline]
234    pub fn is_consensus_chain(&self) -> bool {
235        match self {
236            ChainId::Consensus => true,
237            ChainId::Domain(_) => false,
238        }
239    }
240
241    #[inline]
242    pub fn maybe_domain_chain(&self) -> Option<DomainId> {
243        match self {
244            ChainId::Consensus => None,
245            ChainId::Domain(domain_id) => Some(*domain_id),
246        }
247    }
248}
249
250impl From<u32> for ChainId {
251    #[inline]
252    fn from(x: u32) -> Self {
253        Self::Domain(DomainId::new(x))
254    }
255}
256
257impl From<DomainId> for ChainId {
258    #[inline]
259    fn from(x: DomainId) -> Self {
260        Self::Domain(x)
261    }
262}
263
264// TODO: this runtime constant is not support to update, see https://github.com/autonomys/subspace/issues/2712
265// for more detail about the problem and what we need to do to support it.
266//
267/// Initial tx range = U256::MAX / INITIAL_DOMAIN_TX_RANGE.
268pub const INITIAL_DOMAIN_TX_RANGE: u64 = 3;
269
270#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
271pub struct ProofOfElection {
272    /// Domain id.
273    pub domain_id: DomainId,
274    /// The slot number.
275    pub slot_number: u64,
276    /// The PoT output for `slot_number`.
277    pub proof_of_time: PotOutput,
278    /// VRF signature.
279    pub vrf_signature: VrfSignature,
280    /// Operator index in the OperatorRegistry.
281    pub operator_id: OperatorId,
282}
283
284impl ProofOfElection {
285    pub fn verify_vrf_signature(
286        &self,
287        operator_signing_key: &OperatorPublicKey,
288    ) -> Result<(), ProofOfElectionError> {
289        let global_challenge = self
290            .proof_of_time
291            .derive_global_randomness()
292            .derive_global_challenge(self.slot_number);
293        bundle_producer_election::verify_vrf_signature(
294            self.domain_id,
295            operator_signing_key,
296            &self.vrf_signature,
297            &global_challenge,
298        )
299    }
300
301    /// Computes the VRF hash.
302    pub fn vrf_hash(&self) -> Blake3Hash {
303        let mut bytes = self.vrf_signature.pre_output.encode();
304        bytes.append(&mut self.vrf_signature.proof.encode());
305        blake3_hash(&bytes)
306    }
307
308    pub fn slot_number(&self) -> u64 {
309        self.slot_number
310    }
311}
312
313impl ProofOfElection {
314    #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
315    pub fn dummy(domain_id: DomainId, operator_id: OperatorId) -> Self {
316        let output_bytes = sp_std::vec![0u8; VrfPreOutput::max_encoded_len()];
317        let proof_bytes = sp_std::vec![0u8; VrfProof::max_encoded_len()];
318        let vrf_signature = VrfSignature {
319            pre_output: VrfPreOutput::decode(&mut output_bytes.as_slice()).unwrap(),
320            proof: VrfProof::decode(&mut proof_bytes.as_slice()).unwrap(),
321        };
322        Self {
323            domain_id,
324            slot_number: 0u64,
325            proof_of_time: PotOutput::default(),
326            vrf_signature,
327            operator_id,
328        }
329    }
330}
331
332/// Type that represents an operator allow list for Domains.
333#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Serialize, Deserialize)]
334pub enum OperatorAllowList<AccountId: Ord> {
335    /// Anyone can operate for this domain.
336    Anyone,
337    /// Only the specific operators are allowed to operate the domain.
338    /// This essentially makes the domain permissioned.
339    Operators(BTreeSet<AccountId>),
340}
341
342impl<AccountId: Ord> OperatorAllowList<AccountId> {
343    /// Returns true if the allow list is either `Anyone` or the operator is part of the allowed operator list.
344    pub fn is_operator_allowed(&self, operator: &AccountId) -> bool {
345        match self {
346            OperatorAllowList::Anyone => true,
347            OperatorAllowList::Operators(allowed_operators) => allowed_operators.contains(operator),
348        }
349    }
350}
351
352/// Permissioned actions allowed by either specific accounts or anyone.
353#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Serialize, Deserialize)]
354pub enum PermissionedActionAllowedBy<AccountId: Codec + Clone> {
355    Accounts(Vec<AccountId>),
356    Anyone,
357}
358
359impl<AccountId: Codec + PartialEq + Clone> PermissionedActionAllowedBy<AccountId> {
360    pub fn is_allowed(&self, who: &AccountId) -> bool {
361        match self {
362            PermissionedActionAllowedBy::Accounts(accounts) => accounts.contains(who),
363            PermissionedActionAllowedBy::Anyone => true,
364        }
365    }
366
367    pub fn is_anyone_allowed(&self) -> bool {
368        matches!(self, PermissionedActionAllowedBy::Anyone)
369    }
370}
371
372/// EVM-specific domain type (and associated data).
373#[derive(
374    TypeInfo, Debug, Default, Encode, Decode, Clone, PartialEq, Eq, Serialize, Deserialize,
375)]
376pub enum EvmType {
377    #[default]
378    /// An EVM domain where any account can create contracts.
379    Public,
380    /// An EVM domain with a contract creation allow list.
381    Private {
382        /// Accounts initially allowed to create contracts on a private EVM domain.
383        /// The domain owner can update this list using a pallet-domains call (or there's a sudo call).
384        initial_contract_creation_allow_list: PermissionedActionAllowedBy<EthereumAccountId>,
385    },
386}
387
388impl EvmType {
389    /// Returns the initial contract creation allow list, or `None` if this is a public EVM domain.
390    pub fn initial_contract_creation_allow_list(
391        &self,
392    ) -> Option<&PermissionedActionAllowedBy<EthereumAccountId>> {
393        match self {
394            EvmType::Public => None,
395            EvmType::Private {
396                initial_contract_creation_allow_list,
397            } => Some(initial_contract_creation_allow_list),
398        }
399    }
400
401    /// Returns true if the EVM domain is public.
402    pub fn is_public_evm_domain(&self) -> bool {
403        matches!(self, EvmType::Public)
404    }
405
406    /// Returns true if the EVM domain is private.
407    pub fn is_private_evm_domain(&self) -> bool {
408        matches!(self, EvmType::Private { .. })
409    }
410}
411
412/// EVM-specific domain runtime config.
413#[derive(
414    TypeInfo, Debug, Default, Encode, Decode, Clone, PartialEq, Eq, Serialize, Deserialize,
415)]
416pub struct EvmDomainRuntimeConfig {
417    pub evm_type: EvmType,
418}
419
420/// AutoId-specific domain runtime config.
421#[derive(
422    TypeInfo, Debug, Default, Encode, Decode, Clone, PartialEq, Eq, Serialize, Deserialize,
423)]
424pub struct AutoIdDomainRuntimeConfig {
425    // Currently, there is no specific configuration for AutoId.
426}
427
428/// Domain runtime specific information to create domain raw genesis.
429#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Serialize, Deserialize)]
430pub enum DomainRuntimeInfo {
431    Evm {
432        /// The EVM chain id for this domain.
433        chain_id: EVMChainId,
434        /// The EVM-specific domain runtime config.
435        domain_runtime_config: EvmDomainRuntimeConfig,
436    },
437    AutoId {
438        /// The AutoId-specific domain runtime config.
439        domain_runtime_config: AutoIdDomainRuntimeConfig,
440    },
441}
442
443impl From<(EVMChainId, EvmDomainRuntimeConfig)> for DomainRuntimeInfo {
444    fn from(v: (EVMChainId, EvmDomainRuntimeConfig)) -> Self {
445        DomainRuntimeInfo::Evm {
446            chain_id: v.0,
447            domain_runtime_config: v.1,
448        }
449    }
450}
451
452impl From<AutoIdDomainRuntimeConfig> for DomainRuntimeInfo {
453    fn from(auto_id_config: AutoIdDomainRuntimeConfig) -> Self {
454        DomainRuntimeInfo::AutoId {
455            domain_runtime_config: auto_id_config,
456        }
457    }
458}
459
460impl DomainRuntimeInfo {
461    pub fn evm(&self) -> Option<&EvmDomainRuntimeConfig> {
462        match self {
463            DomainRuntimeInfo::Evm {
464                domain_runtime_config,
465                ..
466            } => Some(domain_runtime_config),
467            _ => None,
468        }
469    }
470
471    pub fn initial_contract_creation_allow_list(
472        &self,
473    ) -> Option<&PermissionedActionAllowedBy<EthereumAccountId>> {
474        self.evm()
475            .and_then(|evm_config| evm_config.evm_type.initial_contract_creation_allow_list())
476    }
477
478    pub fn auto_id(&self) -> Option<&AutoIdDomainRuntimeConfig> {
479        match self {
480            DomainRuntimeInfo::AutoId {
481                domain_runtime_config,
482            } => Some(domain_runtime_config),
483            _ => None,
484        }
485    }
486
487    /// If this is an EVM runtime, returns the chain id.
488    pub fn evm_chain_id(&self) -> Option<EVMChainId> {
489        match self {
490            Self::Evm { chain_id, .. } => Some(*chain_id),
491            _ => None,
492        }
493    }
494
495    pub fn is_evm_domain(&self) -> bool {
496        matches!(self, Self::Evm { .. })
497    }
498
499    /// Returns true if the domain is configured as a private EVM domain.
500    /// Returns false for public EVM domains and non-EVM domains.
501    pub fn is_private_evm_domain(&self) -> bool {
502        if let Self::Evm {
503            domain_runtime_config,
504            ..
505        } = self
506        {
507            domain_runtime_config.evm_type.is_private_evm_domain()
508        } else {
509            false
510        }
511    }
512
513    pub fn is_auto_id(&self) -> bool {
514        matches!(self, Self::AutoId { .. })
515    }
516}
517
518#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Serialize, Deserialize)]
519pub struct GenesisDomain<AccountId: Ord, Balance> {
520    // Domain runtime items
521    pub runtime_name: String,
522    pub runtime_type: RuntimeType,
523    pub runtime_version: RuntimeVersion,
524    pub raw_genesis_storage: Vec<u8>,
525
526    // Domain config items
527    pub owner_account_id: AccountId,
528    pub domain_name: String,
529    pub bundle_slot_probability: (u64, u64),
530    pub operator_allow_list: OperatorAllowList<AccountId>,
531    /// Configurations for a specific type of domain runtime, for example, EVM.
532    pub domain_runtime_info: DomainRuntimeInfo,
533
534    // Genesis operator
535    pub signing_key: OperatorPublicKey,
536    pub minimum_nominator_stake: Balance,
537    pub nomination_tax: Percent,
538
539    // initial balances
540    pub initial_balances: Vec<(MultiAccountId, Balance)>,
541}
542
543/// Types of runtime pallet domains currently supports
544#[derive(
545    Debug, Default, Encode, Decode, TypeInfo, Copy, Clone, PartialEq, Eq, Serialize, Deserialize,
546)]
547pub enum RuntimeType {
548    #[default]
549    Evm,
550    AutoId,
551}
552
553/// Type representing the runtime ID.
554pub type RuntimeId = u32;
555
556/// Type representing domain epoch.
557pub type EpochIndex = u32;
558
559/// Type representing operator ID
560pub type OperatorId = u64;
561
562/// Channel identity.
563pub type ChannelId = sp_core::U256;
564
565/// Domains specific digest item.
566#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo)]
567pub enum DomainDigestItem {
568    DomainRuntimeUpgraded(RuntimeId),
569    DomainInstantiated(DomainId),
570}
571
572/// Domains specific digest items.
573pub trait DomainsDigestItem {
574    fn domain_runtime_upgrade(runtime_id: RuntimeId) -> Self;
575    fn as_domain_runtime_upgrade(&self) -> Option<RuntimeId>;
576
577    fn domain_instantiation(domain_id: DomainId) -> Self;
578    fn as_domain_instantiation(&self) -> Option<DomainId>;
579}
580
581impl DomainsDigestItem for DigestItem {
582    fn domain_runtime_upgrade(runtime_id: RuntimeId) -> Self {
583        Self::Other(DomainDigestItem::DomainRuntimeUpgraded(runtime_id).encode())
584    }
585
586    fn as_domain_runtime_upgrade(&self) -> Option<RuntimeId> {
587        match self.try_to::<DomainDigestItem>(OpaqueDigestItemId::Other) {
588            Some(DomainDigestItem::DomainRuntimeUpgraded(runtime_id)) => Some(runtime_id),
589            _ => None,
590        }
591    }
592
593    fn domain_instantiation(domain_id: DomainId) -> Self {
594        Self::Other(DomainDigestItem::DomainInstantiated(domain_id).encode())
595    }
596
597    fn as_domain_instantiation(&self) -> Option<DomainId> {
598        match self.try_to::<DomainDigestItem>(OpaqueDigestItemId::Other) {
599            Some(DomainDigestItem::DomainInstantiated(domain_id)) => Some(domain_id),
600            _ => None,
601        }
602    }
603}
604
605/// EVM chain Id storage key.
606///
607/// This function should ideally use a Host function to fetch the storage key
608/// from the domain runtime. But since the Host function is not available at Genesis, we have to
609/// assume the storage keys.
610/// TODO: once the chain is launched in mainnet, we should use the Host function for all domain instances.
611pub(crate) fn evm_chain_id_storage_key() -> StorageKey {
612    StorageKey(
613        storage_prefix(
614            // This is the name used for `pallet_evm_chain_id` in the `construct_runtime` macro
615            // i.e. `EVMChainId: pallet_evm_chain_id = 82,`
616            "EVMChainId".as_bytes(),
617            // This is the storage item name used inside `pallet_evm_chain_id`
618            "ChainId".as_bytes(),
619        )
620        .to_vec(),
621    )
622}
623
624/// EVM contract creation allow list storage key.
625///
626/// This function should ideally use a Host function to fetch the storage key
627/// from the domain runtime. But since the Host function is not available at Genesis, we have to
628/// assume the storage keys.
629/// TODO: once the chain is launched in mainnet, we should use the Host function for all domain instances.
630pub(crate) fn evm_contract_creation_allowed_by_storage_key() -> StorageKey {
631    StorageKey(
632        storage_prefix(
633            // This is the name used for `pallet_evm_tracker` in the `construct_runtime` macro
634            // i.e. `EVMNoncetracker: pallet_evm_tracker = 84,`
635            "EVMNoncetracker".as_bytes(),
636            // This is the storage item name used inside `pallet_evm_tracker`
637            "ContractCreationAllowedBy".as_bytes(),
638        )
639        .to_vec(),
640    )
641}
642
643/// Total issuance storage key for Domains.
644///
645/// This function should ideally use a Host function to fetch the storage key
646/// from the domain runtime. But since the Host function is not available at Genesis, we have to
647/// assume the storage keys.
648/// TODO: once the chain is launched in mainnet, we should use the Host function for all domain instances.
649pub fn domain_total_issuance_storage_key() -> StorageKey {
650    StorageKey(
651        storage_prefix(
652            // This is the name used for `pallet_balances` in the `construct_runtime` macro
653            "Balances".as_bytes(),
654            // This is the storage item name used inside `pallet_balances`
655            "TotalIssuance".as_bytes(),
656        )
657        .to_vec(),
658    )
659}
660
661/// Account info on frame_system on Domains
662///
663/// This function should ideally use a Host function to fetch the storage key
664/// from the domain runtime. But since the Host function is not available at Genesis, we have to
665/// assume the storage keys.
666/// TODO: once the chain is launched in mainnet, we should use the Host function for all domain instances.
667pub fn domain_account_storage_key<AccountId: Encode>(who: AccountId) -> StorageKey {
668    let storage_prefix = storage_prefix("System".as_bytes(), "Account".as_bytes());
669    let key_hashed = who.using_encoded(Blake2_128Concat::hash);
670
671    let mut final_key = Vec::with_capacity(storage_prefix.len() + key_hashed.len());
672
673    final_key.extend_from_slice(&storage_prefix);
674    final_key.extend_from_slice(key_hashed.as_ref());
675
676    StorageKey(final_key)
677}
678
679/// The storage key of the `SelfDomainId` storage item in `pallet-domain-id`
680///
681/// Any change to the storage item name or `pallet-domain-id` name used in the `construct_runtime`
682/// macro must be reflected here.
683pub fn self_domain_id_storage_key() -> StorageKey {
684    StorageKey(
685        frame_support::storage::storage_prefix(
686            // This is the name used for `pallet-domain-id` in the `construct_runtime` macro
687            // i.e. `SelfDomainId: pallet_domain_id = 90`
688            "SelfDomainId".as_bytes(),
689            // This is the storage item name used inside `pallet-domain-id`
690            "SelfDomainId".as_bytes(),
691        )
692        .to_vec(),
693    )
694}
695
696/// `DomainInstanceData` is used to construct the genesis storage of domain instance chain
697#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, TypeInfo)]
698pub struct DomainInstanceData {
699    pub runtime_type: RuntimeType,
700    pub raw_genesis: RawGenesis,
701}
702
703#[derive(Debug, Decode, Encode, TypeInfo, Clone, PartialEq, Eq)]
704pub struct DomainBundleLimit {
705    /// The max bundle size for the domain.
706    pub max_bundle_size: u32,
707    /// The max bundle weight for the domain.
708    pub max_bundle_weight: Weight,
709}
710
711/// Calculates the max bundle weight and size
712// See https://forum.subspace.network/t/on-bundle-weight-limits-sum/2277 for more details
713// about the formula
714pub fn calculate_max_bundle_weight_and_size(
715    max_domain_block_size: u32,
716    max_domain_block_weight: Weight,
717    consensus_slot_probability: (u64, u64),
718    bundle_slot_probability: (u64, u64),
719) -> Option<DomainBundleLimit> {
720    // (n1 / d1) / (n2 / d2) is equal to (n1 * d2) / (d1 * n2)
721    // This represents: bundle_slot_probability/SLOT_PROBABILITY
722    let expected_bundles_per_block = bundle_slot_probability
723        .0
724        .checked_mul(consensus_slot_probability.1)?
725        .checked_div(
726            bundle_slot_probability
727                .1
728                .checked_mul(consensus_slot_probability.0)?,
729        )?;
730
731    // set the proof size for bundle to be proof size of max domain weight
732    // so that each domain extrinsic can use the full proof size if required
733    let max_proof_size = max_domain_block_weight.proof_size();
734    let max_bundle_weight = max_domain_block_weight
735        .checked_div(expected_bundles_per_block)?
736        .set_proof_size(max_proof_size);
737
738    let max_bundle_size =
739        (max_domain_block_size as u64).checked_div(expected_bundles_per_block)? as u32;
740
741    Some(DomainBundleLimit {
742        max_bundle_size,
743        max_bundle_weight,
744    })
745}
746
747/// Checks if the signer Id hash is within the tx range
748pub fn signer_in_tx_range(bundle_vrf_hash: &U256, signer_id_hash: &U256, tx_range: &U256) -> bool {
749    let distance_from_vrf_hash = bidirectional_distance(bundle_vrf_hash, signer_id_hash);
750    distance_from_vrf_hash <= (*tx_range / 2)
751}
752
753/// Receipt invalidity type.
754#[derive(Debug, Decode, Encode, TypeInfo, Clone, PartialEq, Eq)]
755pub enum InvalidReceipt {
756    /// The field `invalid_bundles` in [`ExecutionReceiptFor`] is invalid.
757    InvalidBundles,
758}
759
760#[derive(Debug, Decode, Encode, TypeInfo, Clone, PartialEq, Eq)]
761pub enum ReceiptValidity {
762    Valid,
763    Invalid(InvalidReceipt),
764}
765
766/// Empty extrinsics root.
767pub const EMPTY_EXTRINSIC_ROOT: ExtrinsicsRoot = ExtrinsicsRoot {
768    0: hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314"),
769};
770
771pub fn derive_domain_block_hash<DomainHeader: HeaderT>(
772    domain_block_number: DomainHeader::Number,
773    extrinsics_root: DomainHeader::Hash,
774    state_root: DomainHeader::Hash,
775    parent_domain_block_hash: DomainHeader::Hash,
776    digest: Digest,
777) -> DomainHeader::Hash {
778    let domain_header = DomainHeader::new(
779        domain_block_number,
780        extrinsics_root,
781        state_root,
782        parent_domain_block_hash,
783        digest,
784    );
785
786    domain_header.hash()
787}
788
789/// Represents the extrinsic either as full data or hash of the data.
790#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
791pub enum ExtrinsicDigest {
792    /// Actual extrinsic data that is inlined since it is less than 33 bytes.
793    Data(Vec<u8>),
794    /// Extrinsic Hash.
795    Hash(H256),
796}
797
798impl ExtrinsicDigest {
799    pub fn new<Layout: TrieLayout>(ext: Vec<u8>) -> Self
800    where
801        Layout::Hash: HashT,
802        <Layout::Hash as HashT>::Output: Into<H256>,
803    {
804        if let Some(threshold) = Layout::MAX_INLINE_VALUE {
805            if ext.len() >= threshold as usize {
806                ExtrinsicDigest::Hash(Layout::Hash::hash(&ext).into())
807            } else {
808                ExtrinsicDigest::Data(ext)
809            }
810        } else {
811            ExtrinsicDigest::Data(ext)
812        }
813    }
814}
815
816/// Trait that tracks the balances on Domains.
817pub trait DomainsTransfersTracker<Balance> {
818    type Error;
819
820    /// Initializes the domain balance
821    fn initialize_domain_balance(domain_id: DomainId, amount: Balance) -> Result<(), Self::Error>;
822
823    /// Notes a transfer between chains.
824    /// Balance on from_chain_id is reduced if it is a domain chain
825    fn note_transfer(
826        from_chain_id: ChainId,
827        to_chain_id: ChainId,
828        amount: Balance,
829    ) -> Result<(), Self::Error>;
830
831    /// Confirms a transfer between chains.
832    fn confirm_transfer(
833        from_chain_id: ChainId,
834        to_chain_id: ChainId,
835        amount: Balance,
836    ) -> Result<(), Self::Error>;
837
838    /// Claims a rejected transfer between chains.
839    fn claim_rejected_transfer(
840        from_chain_id: ChainId,
841        to_chain_id: ChainId,
842        amount: Balance,
843    ) -> Result<(), Self::Error>;
844
845    /// Rejects a initiated transfer between chains.
846    fn reject_transfer(
847        from_chain_id: ChainId,
848        to_chain_id: ChainId,
849        amount: Balance,
850    ) -> Result<(), Self::Error>;
851
852    /// Reduces a given amount from the domain balance
853    fn reduce_domain_balance(domain_id: DomainId, amount: Balance) -> Result<(), Self::Error>;
854}
855
856/// Trait to check domain owner.
857pub trait DomainOwner<AccountId> {
858    /// Returns true if the account is the domain owner.
859    fn is_domain_owner(domain_id: DomainId, acc: AccountId) -> bool;
860}
861
862impl<AccountId> DomainOwner<AccountId> for () {
863    fn is_domain_owner(_domain_id: DomainId, _acc: AccountId) -> bool {
864        false
865    }
866}
867
868/// Post hook to know if the domain had bundle submitted in the previous block.
869pub trait DomainBundleSubmitted {
870    /// Called in the next block initialisation if there was a domain bundle in the previous block.
871    /// This hook if called for domain represents that there is a new domain block for parent consensus block.
872    fn domain_bundle_submitted(domain_id: DomainId);
873}
874
875impl DomainBundleSubmitted for () {
876    fn domain_bundle_submitted(_domain_id: DomainId) {}
877}
878
879/// A hook to call after a domain is instantiated
880pub trait OnDomainInstantiated {
881    fn on_domain_instantiated(domain_id: DomainId);
882}
883
884impl OnDomainInstantiated for () {
885    fn on_domain_instantiated(_domain_id: DomainId) {}
886}
887
888/// Domain chains allowlist updates.
889#[derive(Default, Debug, Encode, Decode, PartialEq, Eq, Clone, TypeInfo)]
890pub struct DomainAllowlistUpdates {
891    /// Chains that are allowed to open a channel with this chain.
892    pub allow_chains: BTreeSet<ChainId>,
893    /// Chains that are not allowed to open a channel with this chain.
894    pub remove_chains: BTreeSet<ChainId>,
895}
896
897impl DomainAllowlistUpdates {
898    pub fn is_empty(&self) -> bool {
899        self.allow_chains.is_empty() && self.remove_chains.is_empty()
900    }
901
902    pub fn clear(&mut self) {
903        self.allow_chains.clear();
904        self.remove_chains.clear();
905    }
906}
907
908/// Domain Sudo runtime call.
909///
910/// This structure exists because we need to generate a storage proof for FP
911/// and Storage shouldn't be None. So each domain must always hold this value even if
912/// there is an empty runtime call inside
913#[derive(Default, Debug, Encode, Decode, PartialEq, Eq, Clone, TypeInfo)]
914pub struct DomainSudoCall {
915    pub maybe_call: Option<Vec<u8>>,
916}
917
918impl DomainSudoCall {
919    pub fn clear(&mut self) {
920        self.maybe_call.take();
921    }
922}
923
924/// EVM Domain "update contract creation allowed by" runtime call.
925///
926/// This structure exists because we need to generate a storage proof for FP
927/// and Storage shouldn't be None. So each domain must always hold this value even if
928/// there is an empty runtime call inside
929#[derive(Default, Debug, Encode, Decode, PartialEq, Eq, Clone, TypeInfo)]
930pub struct EvmDomainContractCreationAllowedByCall {
931    pub maybe_call: Option<PermissionedActionAllowedBy<EthereumAccountId>>,
932}
933
934impl EvmDomainContractCreationAllowedByCall {
935    pub fn clear(&mut self) {
936        self.maybe_call.take();
937    }
938}
939
940#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
941pub struct RuntimeObject<Number, Hash> {
942    pub runtime_name: String,
943    pub runtime_type: RuntimeType,
944    pub runtime_upgrades: u32,
945    pub instance_count: u32,
946    pub hash: Hash,
947    // The raw genesis storage that contains the runtime code.
948    // NOTE: don't use this field directly but `into_complete_raw_genesis` instead
949    pub raw_genesis: RawGenesis,
950    pub version: RuntimeVersion,
951    pub created_at: Number,
952    pub updated_at: Number,
953}
954
955/// Digest storage key in frame_system.
956/// Unfortunately, the digest storage is private and not possible to derive the key from it directly.
957pub fn system_digest_final_key() -> Vec<u8> {
958    frame_support::storage::storage_prefix("System".as_ref(), "Digest".as_ref()).to_vec()
959}
960
961/// Hook to handle chain rewards.
962pub trait OnChainRewards<Balance> {
963    fn on_chain_rewards(chain_id: ChainId, reward: Balance);
964}
965
966impl<Balance> OnChainRewards<Balance> for () {
967    fn on_chain_rewards(_chain_id: ChainId, _reward: Balance) {}
968}
969
970#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
971pub enum OperatorRewardSource<Number> {
972    Bundle {
973        at_block_number: Number,
974    },
975    XDMProtocolFees,
976    #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
977    Dummy,
978}
979
980/// Bundle and Execution Versions.
981#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone, Copy)]
982pub struct BundleAndExecutionReceiptVersion {
983    pub bundle_version: BundleVersion,
984    pub execution_receipt_version: ExecutionReceiptVersion,
985}
986
987/// Represents a nominator's storage fee deposit information
988#[derive(Debug, Encode, Decode, TypeInfo, Clone, PartialEq, Eq)]
989pub struct StorageFeeDeposit<Balance> {
990    /// Original amount contributed to storage fees
991    pub total_deposited: Balance,
992    /// Current value adjusted for fund performance (gains/losses)
993    pub current_value: Balance,
994}
995
996/// Represents a nominator's pending deposit that hasn't been converted to shares yet
997#[derive(Debug, Encode, Decode, TypeInfo, Clone, PartialEq, Eq)]
998pub struct PendingDeposit<Balance> {
999    /// The amount of the pending deposit
1000    pub amount: Balance,
1001    /// The epoch when this deposit will become effective
1002    pub effective_epoch: EpochIndex,
1003}
1004
1005/// Represents a nominator's pending withdrawal with unlock timing
1006#[derive(Debug, Encode, Decode, TypeInfo, Clone, PartialEq, Eq)]
1007pub struct PendingWithdrawal<Balance, DomainBlockNumber> {
1008    /// The amount of stake that will be withdrawn
1009    pub stake_withdrawal_amount: Balance,
1010    /// The amount of storage fee deposit that will be refunded
1011    pub storage_fee_refund: Balance,
1012    /// The domain block number when this withdrawal can be unlocked
1013    pub unlock_at_block: DomainBlockNumber,
1014}
1015
1016/// Complete nominator position information for a specific operator
1017#[derive(Debug, Encode, Decode, TypeInfo, Clone, PartialEq, Eq)]
1018pub struct NominatorPosition<Balance, DomainBlockNumber, Share> {
1019    /// Current value of the nominator's position (shares converted to balance using current share price)
1020    pub current_staked_value: Balance,
1021    /// Total shares owned by nominator
1022    pub total_shares: Share,
1023    /// Storage fee deposit information (original and current adjusted values)
1024    pub storage_fee_deposit: StorageFeeDeposit<Balance>,
1025    /// Pending deposit not yet converted to shares
1026    pub pending_deposit: Option<PendingDeposit<Balance>>,
1027    /// Pending withdrawals with unlock timing
1028    pub pending_withdrawals: Vec<PendingWithdrawal<Balance, DomainBlockNumber>>,
1029}
1030
1031sp_api::decl_runtime_apis! {
1032    /// APIs used to access the domains pallet.
1033    // When updating this version, document new APIs with "Only present in API versions" comments.
1034    #[api_version(6)]
1035    pub trait DomainsApi<DomainHeader: HeaderT> {
1036        /// Submits the transaction bundle via an unsigned extrinsic.
1037        fn submit_bundle_unsigned(opaque_bundle: OpaqueBundle<NumberFor<Block>, Block::Hash, DomainHeader, Balance>);
1038
1039        /// Submits a singleton receipt via an unsigned extrinsic.
1040        fn submit_receipt_unsigned(singleton_receipt: SealedSingletonReceipt<NumberFor<Block>, Block::Hash, DomainHeader, Balance>);
1041
1042        /// Extracts the bundles successfully stored from the given extrinsics.
1043        fn extract_successful_bundles(
1044            domain_id: DomainId,
1045            extrinsics: Vec<Block::Extrinsic>,
1046        ) -> OpaqueBundles<Block, DomainHeader, Balance>;
1047
1048        /// Generates a randomness seed for extrinsics shuffling.
1049        fn extrinsics_shuffling_seed() -> Randomness;
1050
1051        /// Returns the current WASM bundle for the given `domain_id`.
1052        fn domain_runtime_code(domain_id: DomainId) -> Option<Vec<u8>>;
1053
1054        /// Returns the runtime id for the given `domain_id`.
1055        fn runtime_id(domain_id: DomainId) -> Option<RuntimeId>;
1056
1057        /// Returns the list of runtime upgrades in the current block.
1058        fn runtime_upgrades() -> Vec<RuntimeId>;
1059
1060        /// Returns the domain instance data for the given `domain_id`.
1061        fn domain_instance_data(domain_id: DomainId) -> Option<(DomainInstanceData, NumberFor<Block>)>;
1062
1063        /// Returns the current timestamp at the current height.
1064        fn domain_timestamp() -> Moment;
1065
1066        /// Returns the consensus transaction byte fee that will used to charge the domain
1067        /// transaction for consensus chain storage fees.
1068        fn consensus_transaction_byte_fee() -> Balance;
1069
1070        /// Returns the current Tx range for the given domain Id.
1071        fn domain_tx_range(domain_id: DomainId) -> U256;
1072
1073        /// Returns the genesis state root if not pruned.
1074        fn genesis_state_root(domain_id: DomainId) -> Option<H256>;
1075
1076        /// Returns the best execution chain number.
1077        fn head_receipt_number(domain_id: DomainId) -> HeaderNumberFor<DomainHeader>;
1078
1079        /// Returns the block number of oldest unconfirmed execution receipt.
1080        fn oldest_unconfirmed_receipt_number(domain_id: DomainId) -> Option<HeaderNumberFor<DomainHeader>>;
1081
1082        /// Returns the domain bundle limit of the given domain.
1083        fn domain_bundle_limit(domain_id: DomainId) -> Option<DomainBundleLimit>;
1084
1085        /// Returns true if there are any ERs in the challenge period with non empty extrinsics.
1086        fn non_empty_er_exists(domain_id: DomainId) -> bool;
1087
1088        /// Returns the current best block number for the domain.
1089        fn domain_best_number(domain_id: DomainId) -> Option<HeaderNumberFor<DomainHeader>>;
1090
1091        /// Returns the execution receipt with the given hash.
1092        fn execution_receipt(receipt_hash: HeaderHashFor<DomainHeader>) -> Option<ExecutionReceiptFor<DomainHeader, Block, Balance>>;
1093
1094        /// Returns the current epoch and the next epoch operators of the given domain.
1095        fn domain_operators(domain_id: DomainId) -> Option<(BTreeMap<OperatorId, Balance>, Vec<OperatorId>)>;
1096
1097        /// Returns the execution receipt hash of the given domain and domain block number.
1098        fn receipt_hash(domain_id: DomainId, domain_number: HeaderNumberFor<DomainHeader>) -> Option<HeaderHashFor<DomainHeader>>;
1099
1100        /// Returns the latest confirmed domain block number and hash.
1101        fn latest_confirmed_domain_block(domain_id: DomainId) -> Option<(HeaderNumberFor<DomainHeader>, HeaderHashFor<DomainHeader>)>;
1102
1103        /// Returns true if the receipt exists and is going to be pruned
1104        fn is_bad_er_pending_to_prune(domain_id: DomainId, receipt_hash: HeaderHashFor<DomainHeader>) -> bool;
1105
1106        /// Returns the balance of the storage fund account.
1107        fn storage_fund_account_balance(operator_id: OperatorId) -> Balance;
1108
1109        /// Returns true if the given domain's runtime code has been upgraded since `at`.
1110        fn is_domain_runtime_upgraded_since(domain_id: DomainId, at: NumberFor<Block>) -> Option<bool>;
1111
1112        /// Returns the domain sudo call for the given domain, if any.
1113        fn domain_sudo_call(domain_id: DomainId) -> Option<Vec<u8>>;
1114
1115        /// Returns the "set contract creation allowed by" call for the given EVM domain, if any.
1116        fn evm_domain_contract_creation_allowed_by_call(domain_id: DomainId) -> Option<PermissionedActionAllowedBy<EthereumAccountId>>;
1117
1118        /// Returns the last confirmed domain block execution receipt.
1119        fn last_confirmed_domain_block_receipt(domain_id: DomainId) -> Option<ExecutionReceiptFor<DomainHeader, Block, Balance>>;
1120
1121        /// Returns the current bundle version that is accepted by runtime.
1122        fn current_bundle_and_execution_receipt_version() -> BundleAndExecutionReceiptVersion;
1123
1124        /// Returns genesis execution receipt for domains.
1125        fn genesis_execution_receipt(domain_id: DomainId) -> Option<ExecutionReceiptFor<DomainHeader, Block, Balance>>;
1126
1127        /// Returns the complete nominator position for a given operator and account.
1128        ///
1129        /// This calculates the total position including:
1130        /// - Current stake value (converted from shares using current share price)
1131        /// - Total storage fee deposits (known + pending)
1132        /// - Pending deposits (not yet converted to shares)
1133        /// - Pending withdrawals (with unlock timing)
1134        ///
1135        fn nominator_position(
1136            operator_id: OperatorId,
1137            nominator_account: sp_runtime::AccountId32,
1138        ) -> Option<NominatorPosition<Balance, HeaderNumberFor<DomainHeader>, Balance>>;
1139
1140        /// Returns the block pruning depth for domains
1141        /// Available from Api version 6.
1142        fn block_pruning_depth() -> NumberFor<Block>;
1143    }
1144
1145    pub trait BundleProducerElectionApi<Balance: Encode + Decode> {
1146        fn bundle_producer_election_params(domain_id: DomainId) -> Option<BundleProducerElectionParams<Balance>>;
1147
1148        fn operator(operator_id: OperatorId) -> Option<(OperatorPublicKey, Balance)>;
1149    }
1150}