Skip to main content

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