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