#![cfg_attr(not(feature = "std"), no_std)]
pub mod bundle_producer_election;
pub mod core_api;
pub mod extrinsics;
pub mod merkle_tree;
pub mod proof_provider_and_verifier;
pub mod storage;
#[cfg(test)]
mod tests;
pub mod valued_trie;
#[cfg(not(feature = "std"))]
extern crate alloc;
use crate::storage::{RawGenesis, StorageKey};
#[cfg(not(feature = "std"))]
use alloc::collections::BTreeSet;
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use bundle_producer_election::{BundleProducerElectionParams, ProofOfElectionError};
use core::num::ParseIntError;
use core::ops::{Add, Sub};
use core::str::FromStr;
use domain_runtime_primitives::MultiAccountId;
use frame_support::storage::storage_prefix;
use frame_support::{Blake2_128Concat, StorageHasher};
use hexlit::hex;
use parity_scale_codec::{Codec, Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
use sp_core::crypto::KeyTypeId;
use sp_core::sr25519::vrf::VrfSignature;
#[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
use sp_core::sr25519::vrf::{VrfPreOutput, VrfProof};
use sp_core::H256;
use sp_runtime::generic::OpaqueDigestItemId;
use sp_runtime::traits::{
BlakeTwo256, Block as BlockT, CheckedAdd, Hash as HashT, Header as HeaderT, NumberFor, Zero,
};
use sp_runtime::{Digest, DigestItem, OpaqueExtrinsic, Percent};
use sp_runtime_interface::pass_by;
use sp_runtime_interface::pass_by::PassBy;
use sp_std::collections::btree_map::BTreeMap;
use sp_std::fmt::{Display, Formatter};
use sp_trie::TrieLayout;
use sp_version::RuntimeVersion;
use sp_weights::Weight;
#[cfg(feature = "std")]
use std::collections::BTreeSet;
use subspace_core_primitives::hashes::{blake3_hash, Blake3Hash};
use subspace_core_primitives::pot::PotOutput;
use subspace_core_primitives::solutions::bidirectional_distance;
use subspace_core_primitives::{Randomness, U256};
use subspace_runtime_primitives::{Balance, Moment};
pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"oper");
pub const DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT: &[u8] = b"extrinsics-shuffling-seed";
mod app {
use super::KEY_TYPE;
use sp_application_crypto::{app_crypto, sr25519};
app_crypto!(sr25519, KEY_TYPE);
}
pub const DOMAIN_STORAGE_FEE_MULTIPLIER: Balance = 3;
pub type OperatorSignature = app::Signature;
#[cfg(feature = "std")]
pub type OperatorPair = app::Pair;
pub type OperatorPublicKey = app::Public;
pub struct OperatorKey;
impl sp_runtime::BoundToRuntimeAppPublic for OperatorKey {
type Public = OperatorPublicKey;
}
pub type StakeWeight = u128;
pub type ExtrinsicsRoot = H256;
pub type HeaderHashingFor<Header> = <Header as HeaderT>::Hashing;
pub type HeaderNumberFor<Header> = <Header as HeaderT>::Number;
pub type HeaderHashFor<Header> = <Header as HeaderT>::Hash;
#[derive(
Clone,
Copy,
Debug,
Hash,
Default,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
TypeInfo,
Serialize,
Deserialize,
MaxEncodedLen,
)]
pub struct DomainId(u32);
impl From<u32> for DomainId {
#[inline]
fn from(x: u32) -> Self {
Self(x)
}
}
impl From<DomainId> for u32 {
#[inline]
fn from(domain_id: DomainId) -> Self {
domain_id.0
}
}
impl FromStr for DomainId {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<u32>().map(Into::into)
}
}
impl Add<DomainId> for DomainId {
type Output = Self;
fn add(self, other: DomainId) -> Self {
Self(self.0 + other.0)
}
}
impl Sub<DomainId> for DomainId {
type Output = Self;
fn sub(self, other: DomainId) -> Self {
Self(self.0 - other.0)
}
}
impl CheckedAdd for DomainId {
fn checked_add(&self, rhs: &Self) -> Option<Self> {
self.0.checked_add(rhs.0).map(Self)
}
}
impl DomainId {
pub const fn new(id: u32) -> Self {
Self(id)
}
pub fn to_le_bytes(&self) -> [u8; 4] {
self.0.to_le_bytes()
}
}
impl Display for DomainId {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
self.0.fmt(f)
}
}
impl PassBy for DomainId {
type PassBy = pass_by::Codec<Self>;
}
#[derive(
Clone,
Copy,
Debug,
Hash,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
TypeInfo,
Serialize,
Deserialize,
MaxEncodedLen,
)]
pub enum ChainId {
Consensus,
Domain(DomainId),
}
impl ChainId {
#[inline]
pub fn consensus_chain_id() -> Self {
Self::Consensus
}
#[inline]
pub fn is_consensus_chain(&self) -> bool {
match self {
ChainId::Consensus => true,
ChainId::Domain(_) => false,
}
}
#[inline]
pub fn maybe_domain_chain(&self) -> Option<DomainId> {
match self {
ChainId::Consensus => None,
ChainId::Domain(domain_id) => Some(*domain_id),
}
}
}
impl From<u32> for ChainId {
#[inline]
fn from(x: u32) -> Self {
Self::Domain(DomainId::new(x))
}
}
impl From<DomainId> for ChainId {
#[inline]
fn from(x: DomainId) -> Self {
Self::Domain(x)
}
}
#[derive(Clone, Debug, Decode, Default, Encode, Eq, PartialEq, TypeInfo)]
pub struct BlockFees<Balance> {
pub consensus_storage_fee: Balance,
pub domain_execution_fee: Balance,
pub burned_balance: Balance,
pub chain_rewards: BTreeMap<ChainId, Balance>,
}
impl<Balance> BlockFees<Balance>
where
Balance: CheckedAdd,
{
pub fn new(
domain_execution_fee: Balance,
consensus_storage_fee: Balance,
burned_balance: Balance,
chain_rewards: BTreeMap<ChainId, Balance>,
) -> Self {
BlockFees {
consensus_storage_fee,
domain_execution_fee,
burned_balance,
chain_rewards,
}
}
pub fn total_fees(&self) -> Option<Balance> {
self.consensus_storage_fee
.checked_add(&self.domain_execution_fee)
.and_then(|balance| balance.checked_add(&self.burned_balance))
}
}
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone, Default)]
pub struct Transfers<Balance> {
pub transfers_in: BTreeMap<ChainId, Balance>,
pub transfers_out: BTreeMap<ChainId, Balance>,
pub rejected_transfers_claimed: BTreeMap<ChainId, Balance>,
pub transfers_rejected: BTreeMap<ChainId, Balance>,
}
impl<Balance> Transfers<Balance> {
pub fn is_valid(&self, chain_id: ChainId) -> bool {
!self.transfers_rejected.contains_key(&chain_id)
&& !self.transfers_in.contains_key(&chain_id)
&& !self.transfers_out.contains_key(&chain_id)
&& !self.rejected_transfers_claimed.contains_key(&chain_id)
}
}
pub const INITIAL_DOMAIN_TX_RANGE: u64 = 3;
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub struct BundleHeader<Number, Hash, DomainHeader: HeaderT, Balance> {
pub proof_of_election: ProofOfElection,
pub receipt: ExecutionReceipt<
Number,
Hash,
HeaderNumberFor<DomainHeader>,
HeaderHashFor<DomainHeader>,
Balance,
>,
pub estimated_bundle_weight: Weight,
pub bundle_extrinsics_root: HeaderHashFor<DomainHeader>,
}
impl<Number: Encode, Hash: Encode, DomainHeader: HeaderT, Balance: Encode>
BundleHeader<Number, Hash, DomainHeader, Balance>
{
pub fn hash(&self) -> HeaderHashFor<DomainHeader> {
HeaderHashingFor::<DomainHeader>::hash_of(self)
}
}
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub struct SealedBundleHeader<Number, Hash, DomainHeader: HeaderT, Balance> {
pub header: BundleHeader<Number, Hash, DomainHeader, Balance>,
pub signature: OperatorSignature,
}
impl<Number: Encode, Hash: Encode, DomainHeader: HeaderT, Balance: Encode>
SealedBundleHeader<Number, Hash, DomainHeader, Balance>
{
pub fn new(
header: BundleHeader<Number, Hash, DomainHeader, Balance>,
signature: OperatorSignature,
) -> Self {
Self { header, signature }
}
pub fn pre_hash(&self) -> HeaderHashFor<DomainHeader> {
self.header.hash()
}
pub fn hash(&self) -> HeaderHashFor<DomainHeader> {
HeaderHashingFor::<DomainHeader>::hash_of(self)
}
pub fn slot_number(&self) -> u64 {
self.header.proof_of_election.slot_number
}
}
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub struct Bundle<Extrinsic, Number, Hash, DomainHeader: HeaderT, Balance> {
pub sealed_header: SealedBundleHeader<Number, Hash, DomainHeader, Balance>,
pub extrinsics: Vec<Extrinsic>,
}
impl<Extrinsic: Encode, Number: Encode, Hash: Encode, DomainHeader: HeaderT, Balance: Encode>
Bundle<Extrinsic, Number, Hash, DomainHeader, Balance>
{
pub fn hash(&self) -> H256 {
BlakeTwo256::hash_of(self)
}
pub fn domain_id(&self) -> DomainId {
self.sealed_header.header.proof_of_election.domain_id
}
pub fn extrinsics_root(&self) -> HeaderHashFor<DomainHeader> {
self.sealed_header.header.bundle_extrinsics_root
}
pub fn operator_id(&self) -> OperatorId {
self.sealed_header.header.proof_of_election.operator_id
}
pub fn receipt(
&self,
) -> &ExecutionReceipt<
Number,
Hash,
HeaderNumberFor<DomainHeader>,
HeaderHashFor<DomainHeader>,
Balance,
> {
&self.sealed_header.header.receipt
}
pub fn into_receipt(
self,
) -> ExecutionReceipt<
Number,
Hash,
HeaderNumberFor<DomainHeader>,
HeaderHashFor<DomainHeader>,
Balance,
> {
self.sealed_header.header.receipt
}
pub fn size(&self) -> u32 {
self.encoded_size() as u32
}
pub fn body_size(&self) -> u32 {
self.extrinsics
.iter()
.map(|tx| tx.encoded_size() as u32)
.sum::<u32>()
}
pub fn estimated_weight(&self) -> Weight {
self.sealed_header.header.estimated_bundle_weight
}
pub fn slot_number(&self) -> u64 {
self.sealed_header.header.proof_of_election.slot_number
}
}
pub type OpaqueBundle<Number, Hash, DomainHeader, Balance> =
Bundle<OpaqueExtrinsic, Number, Hash, DomainHeader, Balance>;
pub type OpaqueBundles<Block, DomainHeader, Balance> =
Vec<OpaqueBundle<NumberFor<Block>, <Block as BlockT>::Hash, DomainHeader, Balance>>;
impl<Extrinsic: Encode, Number, Hash, DomainHeader: HeaderT, Balance>
Bundle<Extrinsic, Number, Hash, DomainHeader, Balance>
{
pub fn into_opaque_bundle(self) -> OpaqueBundle<Number, Hash, DomainHeader, Balance> {
let Bundle {
sealed_header,
extrinsics,
} = self;
let opaque_extrinsics = extrinsics
.into_iter()
.map(|xt| {
OpaqueExtrinsic::from_bytes(&xt.encode())
.expect("We have just encoded a valid extrinsic; qed")
})
.collect();
OpaqueBundle {
sealed_header,
extrinsics: opaque_extrinsics,
}
}
}
#[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
pub fn dummy_opaque_bundle<
Number: Encode,
Hash: Default + Encode,
DomainHeader: HeaderT,
Balance: Encode,
>(
domain_id: DomainId,
operator_id: OperatorId,
receipt: ExecutionReceipt<
Number,
Hash,
HeaderNumberFor<DomainHeader>,
HeaderHashFor<DomainHeader>,
Balance,
>,
) -> OpaqueBundle<Number, Hash, DomainHeader, Balance> {
use sp_core::crypto::UncheckedFrom;
let header = BundleHeader {
proof_of_election: ProofOfElection::dummy(domain_id, operator_id),
receipt,
estimated_bundle_weight: Default::default(),
bundle_extrinsics_root: Default::default(),
};
let signature = OperatorSignature::unchecked_from([0u8; 64]);
OpaqueBundle {
sealed_header: SealedBundleHeader::new(header, signature),
extrinsics: Vec::new(),
}
}
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub struct BundleDigest<Hash> {
pub header_hash: Hash,
pub extrinsics_root: Hash,
pub size: u32,
}
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub struct ExecutionReceipt<Number, Hash, DomainNumber, DomainHash, Balance> {
pub domain_block_number: DomainNumber,
pub domain_block_hash: DomainHash,
pub domain_block_extrinsic_root: DomainHash,
pub parent_domain_block_receipt_hash: DomainHash,
pub consensus_block_number: Number,
pub consensus_block_hash: Hash,
pub inboxed_bundles: Vec<InboxedBundle<DomainHash>>,
pub final_state_root: DomainHash,
pub execution_trace: Vec<DomainHash>,
pub execution_trace_root: H256,
pub block_fees: BlockFees<Balance>,
pub transfers: Transfers<Balance>,
}
impl<Number, Hash, DomainNumber, DomainHash, Balance>
ExecutionReceipt<Number, Hash, DomainNumber, DomainHash, Balance>
{
pub fn bundles_extrinsics_roots(&self) -> Vec<&DomainHash> {
self.inboxed_bundles
.iter()
.map(|b| &b.extrinsics_root)
.collect()
}
pub fn valid_bundle_digest_at(&self, index: usize) -> Option<DomainHash>
where
DomainHash: Copy,
{
match self.inboxed_bundles.get(index).map(|ib| &ib.bundle) {
Some(BundleValidity::Valid(bundle_digest_hash)) => Some(*bundle_digest_hash),
_ => None,
}
}
pub fn valid_bundle_digests(&self) -> Vec<DomainHash>
where
DomainHash: Copy,
{
self.inboxed_bundles
.iter()
.filter_map(|b| match b.bundle {
BundleValidity::Valid(bundle_digest_hash) => Some(bundle_digest_hash),
BundleValidity::Invalid(_) => None,
})
.collect()
}
pub fn valid_bundle_indexes(&self) -> Vec<u32> {
self.inboxed_bundles
.iter()
.enumerate()
.filter_map(|(index, b)| match b.bundle {
BundleValidity::Valid(_) => Some(index as u32),
BundleValidity::Invalid(_) => None,
})
.collect()
}
}
impl<
Number: Encode + Zero,
Hash: Encode + Default,
DomainNumber: Encode + Zero,
DomainHash: Clone + Encode + Default,
Balance: Encode + Zero + Default,
> ExecutionReceipt<Number, Hash, DomainNumber, DomainHash, Balance>
{
pub fn hash<DomainHashing: HashT<Output = DomainHash>>(&self) -> DomainHash {
DomainHashing::hash_of(self)
}
pub fn genesis(
genesis_state_root: DomainHash,
genesis_extrinsic_root: DomainHash,
genesis_domain_block_hash: DomainHash,
) -> Self {
ExecutionReceipt {
domain_block_number: Zero::zero(),
domain_block_hash: genesis_domain_block_hash,
domain_block_extrinsic_root: genesis_extrinsic_root,
parent_domain_block_receipt_hash: Default::default(),
consensus_block_hash: Default::default(),
consensus_block_number: Zero::zero(),
inboxed_bundles: Vec::new(),
final_state_root: genesis_state_root.clone(),
execution_trace: sp_std::vec![genesis_state_root],
execution_trace_root: Default::default(),
block_fees: Default::default(),
transfers: Default::default(),
}
}
#[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
pub fn dummy<DomainHashing>(
consensus_block_number: Number,
consensus_block_hash: Hash,
domain_block_number: DomainNumber,
parent_domain_block_receipt_hash: DomainHash,
) -> ExecutionReceipt<Number, Hash, DomainNumber, DomainHash, Balance>
where
DomainHashing: HashT<Output = DomainHash>,
{
let execution_trace = sp_std::vec![Default::default(), Default::default()];
let execution_trace_root = {
let trace: Vec<[u8; 32]> = execution_trace
.iter()
.map(|r: &DomainHash| r.encode().try_into().expect("H256 must fit into [u8; 32]"))
.collect();
crate::merkle_tree::MerkleTree::from_leaves(trace.as_slice())
.root()
.expect("Compute merkle root of trace should success")
.into()
};
ExecutionReceipt {
domain_block_number,
domain_block_hash: Default::default(),
domain_block_extrinsic_root: Default::default(),
parent_domain_block_receipt_hash,
consensus_block_number,
consensus_block_hash,
inboxed_bundles: sp_std::vec![InboxedBundle::dummy(Default::default())],
final_state_root: Default::default(),
execution_trace,
execution_trace_root,
block_fees: Default::default(),
transfers: Default::default(),
}
}
}
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub struct ProofOfElection {
pub domain_id: DomainId,
pub slot_number: u64,
pub proof_of_time: PotOutput,
pub vrf_signature: VrfSignature,
pub operator_id: OperatorId,
}
impl ProofOfElection {
pub fn verify_vrf_signature(
&self,
operator_signing_key: &OperatorPublicKey,
) -> Result<(), ProofOfElectionError> {
let global_challenge = self
.proof_of_time
.derive_global_randomness()
.derive_global_challenge(self.slot_number);
bundle_producer_election::verify_vrf_signature(
self.domain_id,
operator_signing_key,
&self.vrf_signature,
&global_challenge,
)
}
pub fn vrf_hash(&self) -> Blake3Hash {
let mut bytes = self.vrf_signature.pre_output.encode();
bytes.append(&mut self.vrf_signature.proof.encode());
blake3_hash(&bytes)
}
pub fn slot_number(&self) -> u64 {
self.slot_number
}
}
impl ProofOfElection {
#[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
pub fn dummy(domain_id: DomainId, operator_id: OperatorId) -> Self {
let output_bytes = sp_std::vec![0u8; VrfPreOutput::max_encoded_len()];
let proof_bytes = sp_std::vec![0u8; VrfProof::max_encoded_len()];
let vrf_signature = VrfSignature {
pre_output: VrfPreOutput::decode(&mut output_bytes.as_slice()).unwrap(),
proof: VrfProof::decode(&mut proof_bytes.as_slice()).unwrap(),
};
Self {
domain_id,
slot_number: 0u64,
proof_of_time: PotOutput::default(),
vrf_signature,
operator_id,
}
}
}
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub struct SingletonReceipt<Number, Hash, DomainHeader: HeaderT, Balance> {
pub proof_of_election: ProofOfElection,
pub receipt: ExecutionReceipt<
Number,
Hash,
HeaderNumberFor<DomainHeader>,
HeaderHashFor<DomainHeader>,
Balance,
>,
}
impl<Number: Encode, Hash: Encode, DomainHeader: HeaderT, Balance: Encode>
SingletonReceipt<Number, Hash, DomainHeader, Balance>
{
pub fn hash(&self) -> HeaderHashFor<DomainHeader> {
HeaderHashingFor::<DomainHeader>::hash_of(&self)
}
}
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub struct SealedSingletonReceipt<Number, Hash, DomainHeader: HeaderT, Balance> {
pub singleton_receipt: SingletonReceipt<Number, Hash, DomainHeader, Balance>,
pub signature: OperatorSignature,
}
impl<Number: Encode, Hash: Encode, DomainHeader: HeaderT, Balance: Encode>
SealedSingletonReceipt<Number, Hash, DomainHeader, Balance>
{
pub fn domain_id(&self) -> DomainId {
self.singleton_receipt.proof_of_election.domain_id
}
pub fn operator_id(&self) -> OperatorId {
self.singleton_receipt.proof_of_election.operator_id
}
pub fn slot_number(&self) -> u64 {
self.singleton_receipt.proof_of_election.slot_number
}
pub fn receipt(
&self,
) -> &ExecutionReceipt<
Number,
Hash,
HeaderNumberFor<DomainHeader>,
HeaderHashFor<DomainHeader>,
Balance,
> {
&self.singleton_receipt.receipt
}
pub fn into_receipt(
self,
) -> ExecutionReceipt<
Number,
Hash,
HeaderNumberFor<DomainHeader>,
HeaderHashFor<DomainHeader>,
Balance,
> {
self.singleton_receipt.receipt
}
pub fn pre_hash(&self) -> HeaderHashFor<DomainHeader> {
HeaderHashingFor::<DomainHeader>::hash_of(&self.singleton_receipt)
}
pub fn size(&self) -> u32 {
self.encoded_size() as u32
}
}
#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum OperatorAllowList<AccountId: Ord> {
Anyone,
Operators(BTreeSet<AccountId>),
}
impl<AccountId: Ord> OperatorAllowList<AccountId> {
pub fn is_operator_allowed(&self, operator: &AccountId) -> bool {
match self {
OperatorAllowList::Anyone => true,
OperatorAllowList::Operators(allowed_operators) => allowed_operators.contains(operator),
}
}
}
#[derive(TypeInfo, Encode, Decode, Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum PermissionedActionAllowedBy<AccountId: Codec + Clone> {
Accounts(Vec<AccountId>),
Anyone,
}
impl<AccountId: Codec + PartialEq + Clone> PermissionedActionAllowedBy<AccountId> {
pub fn is_allowed(&self, who: &AccountId) -> bool {
match self {
PermissionedActionAllowedBy::Accounts(accounts) => accounts.contains(who),
PermissionedActionAllowedBy::Anyone => true,
}
}
}
#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GenesisDomain<AccountId: Ord, Balance> {
pub runtime_name: String,
pub runtime_type: RuntimeType,
pub runtime_version: RuntimeVersion,
pub raw_genesis_storage: Vec<u8>,
pub owner_account_id: AccountId,
pub domain_name: String,
pub bundle_slot_probability: (u64, u64),
pub operator_allow_list: OperatorAllowList<AccountId>,
pub signing_key: OperatorPublicKey,
pub minimum_nominator_stake: Balance,
pub nomination_tax: Percent,
pub initial_balances: Vec<(MultiAccountId, Balance)>,
}
#[derive(
Debug, Default, Encode, Decode, TypeInfo, Clone, PartialEq, Eq, Serialize, Deserialize,
)]
pub enum RuntimeType {
#[default]
Evm,
AutoId,
}
pub type RuntimeId = u32;
pub type EpochIndex = u32;
pub type OperatorId = u64;
pub type ChannelId = sp_core::U256;
#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo)]
pub enum DomainDigestItem {
DomainRuntimeUpgraded(RuntimeId),
DomainInstantiated(DomainId),
}
pub trait DomainsDigestItem {
fn domain_runtime_upgrade(runtime_id: RuntimeId) -> Self;
fn as_domain_runtime_upgrade(&self) -> Option<RuntimeId>;
fn domain_instantiation(domain_id: DomainId) -> Self;
fn as_domain_instantiation(&self) -> Option<DomainId>;
}
impl DomainsDigestItem for DigestItem {
fn domain_runtime_upgrade(runtime_id: RuntimeId) -> Self {
Self::Other(DomainDigestItem::DomainRuntimeUpgraded(runtime_id).encode())
}
fn as_domain_runtime_upgrade(&self) -> Option<RuntimeId> {
match self.try_to::<DomainDigestItem>(OpaqueDigestItemId::Other) {
Some(DomainDigestItem::DomainRuntimeUpgraded(runtime_id)) => Some(runtime_id),
_ => None,
}
}
fn domain_instantiation(domain_id: DomainId) -> Self {
Self::Other(DomainDigestItem::DomainInstantiated(domain_id).encode())
}
fn as_domain_instantiation(&self) -> Option<DomainId> {
match self.try_to::<DomainDigestItem>(OpaqueDigestItemId::Other) {
Some(DomainDigestItem::DomainInstantiated(domain_id)) => Some(domain_id),
_ => None,
}
}
}
pub(crate) fn evm_chain_id_storage_key() -> StorageKey {
StorageKey(
storage_prefix(
"EVMChainId".as_bytes(),
"ChainId".as_bytes(),
)
.to_vec(),
)
}
pub fn domain_total_issuance_storage_key() -> StorageKey {
StorageKey(
storage_prefix(
"Balances".as_bytes(),
"TotalIssuance".as_bytes(),
)
.to_vec(),
)
}
pub fn domain_account_storage_key<AccountId: Encode>(who: AccountId) -> StorageKey {
let storage_prefix = storage_prefix("System".as_bytes(), "Account".as_bytes());
let key_hashed = who.using_encoded(Blake2_128Concat::hash);
let mut final_key = Vec::with_capacity(storage_prefix.len() + key_hashed.len());
final_key.extend_from_slice(&storage_prefix);
final_key.extend_from_slice(key_hashed.as_ref());
StorageKey(final_key)
}
pub fn self_domain_id_storage_key() -> StorageKey {
StorageKey(
frame_support::storage::storage_prefix(
"SelfDomainId".as_bytes(),
"SelfDomainId".as_bytes(),
)
.to_vec(),
)
}
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, TypeInfo)]
pub struct DomainInstanceData {
pub runtime_type: RuntimeType,
pub raw_genesis: RawGenesis,
}
#[derive(Debug, Decode, Encode, TypeInfo, Clone, PartialEq, Eq)]
pub struct DomainBundleLimit {
pub max_bundle_size: u32,
pub max_bundle_weight: Weight,
}
pub fn calculate_max_bundle_weight_and_size(
max_domain_block_size: u32,
max_domain_block_weight: Weight,
consensus_slot_probability: (u64, u64),
bundle_slot_probability: (u64, u64),
) -> Option<DomainBundleLimit> {
let expected_bundles_per_block = bundle_slot_probability
.0
.checked_mul(consensus_slot_probability.1)?
.checked_div(
bundle_slot_probability
.1
.checked_mul(consensus_slot_probability.0)?,
)?;
let max_proof_size = max_domain_block_weight.proof_size();
let max_bundle_weight = max_domain_block_weight
.checked_div(expected_bundles_per_block)?
.set_proof_size(max_proof_size);
let max_bundle_size =
(max_domain_block_size as u64).checked_div(expected_bundles_per_block)? as u32;
Some(DomainBundleLimit {
max_bundle_size,
max_bundle_weight,
})
}
pub fn signer_in_tx_range(bundle_vrf_hash: &U256, signer_id_hash: &U256, tx_range: &U256) -> bool {
let distance_from_vrf_hash = bidirectional_distance(bundle_vrf_hash, signer_id_hash);
distance_from_vrf_hash <= (*tx_range / 2)
}
#[derive(Debug, Decode, Encode, TypeInfo, Clone, PartialEq, Eq)]
pub enum InvalidReceipt {
InvalidBundles,
}
#[derive(Debug, Decode, Encode, TypeInfo, Clone, PartialEq, Eq)]
pub enum ReceiptValidity {
Valid,
Invalid(InvalidReceipt),
}
#[derive(Debug, Decode, Encode, TypeInfo, Clone, PartialEq, Eq)]
pub enum InvalidBundleType {
#[codec(index = 0)]
UndecodableTx(u32),
#[codec(index = 1)]
OutOfRangeTx(u32),
#[codec(index = 2)]
IllegalTx(u32),
#[codec(index = 3)]
InvalidXDM(u32),
#[codec(index = 4)]
InherentExtrinsic(u32),
#[codec(index = 5)]
InvalidBundleWeight,
}
impl InvalidBundleType {
pub fn checking_order(&self) -> u64 {
let extrinsic_order = match self {
Self::UndecodableTx(i) => *i,
Self::OutOfRangeTx(i) => *i,
Self::IllegalTx(i) => *i,
Self::InvalidXDM(i) => *i,
Self::InherentExtrinsic(i) => *i,
Self::InvalidBundleWeight => u32::MAX,
};
let rule_order = match self {
Self::UndecodableTx(_) => 1,
Self::OutOfRangeTx(_) => 2,
Self::InherentExtrinsic(_) => 3,
Self::InvalidXDM(_) => 4,
Self::IllegalTx(_) => 5,
Self::InvalidBundleWeight => 6,
};
((extrinsic_order as u64) << 32) | (rule_order as u64)
}
pub fn extrinsic_index(&self) -> Option<u32> {
match self {
Self::UndecodableTx(i) => Some(*i),
Self::OutOfRangeTx(i) => Some(*i),
Self::IllegalTx(i) => Some(*i),
Self::InvalidXDM(i) => Some(*i),
Self::InherentExtrinsic(i) => Some(*i),
Self::InvalidBundleWeight => None,
}
}
}
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub enum BundleValidity<Hash> {
Invalid(InvalidBundleType),
Valid(Hash),
}
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub struct InboxedBundle<Hash> {
pub bundle: BundleValidity<Hash>,
pub extrinsics_root: Hash,
}
impl<Hash> InboxedBundle<Hash> {
pub fn valid(bundle_digest_hash: Hash, extrinsics_root: Hash) -> Self {
InboxedBundle {
bundle: BundleValidity::Valid(bundle_digest_hash),
extrinsics_root,
}
}
pub fn invalid(invalid_bundle_type: InvalidBundleType, extrinsics_root: Hash) -> Self {
InboxedBundle {
bundle: BundleValidity::Invalid(invalid_bundle_type),
extrinsics_root,
}
}
pub fn is_invalid(&self) -> bool {
matches!(self.bundle, BundleValidity::Invalid(_))
}
pub fn invalid_extrinsic_index(&self) -> Option<u32> {
match &self.bundle {
BundleValidity::Invalid(invalid_bundle_type) => invalid_bundle_type.extrinsic_index(),
BundleValidity::Valid(_) => None,
}
}
#[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
pub fn dummy(extrinsics_root: Hash) -> Self
where
Hash: Default,
{
InboxedBundle {
bundle: BundleValidity::Valid(Hash::default()),
extrinsics_root,
}
}
}
pub const EMPTY_EXTRINSIC_ROOT: ExtrinsicsRoot = ExtrinsicsRoot {
0: hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314"),
};
pub fn derive_domain_block_hash<DomainHeader: HeaderT>(
domain_block_number: DomainHeader::Number,
extrinsics_root: DomainHeader::Hash,
state_root: DomainHeader::Hash,
parent_domain_block_hash: DomainHeader::Hash,
digest: Digest,
) -> DomainHeader::Hash {
let domain_header = DomainHeader::new(
domain_block_number,
extrinsics_root,
state_root,
parent_domain_block_hash,
digest,
);
domain_header.hash()
}
#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
pub enum ExtrinsicDigest {
Data(Vec<u8>),
Hash(H256),
}
impl ExtrinsicDigest {
pub fn new<Layout: TrieLayout>(ext: Vec<u8>) -> Self
where
Layout::Hash: HashT,
<Layout::Hash as HashT>::Output: Into<H256>,
{
if let Some(threshold) = Layout::MAX_INLINE_VALUE {
if ext.len() >= threshold as usize {
ExtrinsicDigest::Hash(Layout::Hash::hash(&ext).into())
} else {
ExtrinsicDigest::Data(ext)
}
} else {
ExtrinsicDigest::Data(ext)
}
}
}
pub trait DomainsTransfersTracker<Balance> {
type Error;
fn initialize_domain_balance(domain_id: DomainId, amount: Balance) -> Result<(), Self::Error>;
fn note_transfer(
from_chain_id: ChainId,
to_chain_id: ChainId,
amount: Balance,
) -> Result<(), Self::Error>;
fn confirm_transfer(
from_chain_id: ChainId,
to_chain_id: ChainId,
amount: Balance,
) -> Result<(), Self::Error>;
fn claim_rejected_transfer(
from_chain_id: ChainId,
to_chain_id: ChainId,
amount: Balance,
) -> Result<(), Self::Error>;
fn reject_transfer(
from_chain_id: ChainId,
to_chain_id: ChainId,
amount: Balance,
) -> Result<(), Self::Error>;
fn reduce_domain_balance(domain_id: DomainId, amount: Balance) -> Result<(), Self::Error>;
}
pub trait DomainOwner<AccountId> {
fn is_domain_owner(domain_id: DomainId, acc: AccountId) -> bool;
}
impl<AccountId> DomainOwner<AccountId> for () {
fn is_domain_owner(_domain_id: DomainId, _acc: AccountId) -> bool {
false
}
}
pub trait DomainBundleSubmitted {
fn domain_bundle_submitted(domain_id: DomainId);
}
impl DomainBundleSubmitted for () {
fn domain_bundle_submitted(_domain_id: DomainId) {}
}
pub trait OnDomainInstantiated {
fn on_domain_instantiated(domain_id: DomainId);
}
impl OnDomainInstantiated for () {
fn on_domain_instantiated(_domain_id: DomainId) {}
}
pub type ExecutionReceiptFor<DomainHeader, CBlock, Balance> = ExecutionReceipt<
NumberFor<CBlock>,
<CBlock as BlockT>::Hash,
<DomainHeader as HeaderT>::Number,
<DomainHeader as HeaderT>::Hash,
Balance,
>;
#[derive(Default, Debug, Encode, Decode, PartialEq, Eq, Clone, TypeInfo)]
pub struct DomainAllowlistUpdates {
pub allow_chains: BTreeSet<ChainId>,
pub remove_chains: BTreeSet<ChainId>,
}
impl DomainAllowlistUpdates {
pub fn is_empty(&self) -> bool {
self.allow_chains.is_empty() && self.remove_chains.is_empty()
}
pub fn clear(&mut self) {
self.allow_chains.clear();
self.remove_chains.clear();
}
}
#[derive(Default, Debug, Encode, Decode, PartialEq, Eq, Clone, TypeInfo)]
pub struct DomainSudoCall {
pub maybe_call: Option<Vec<u8>>,
}
impl DomainSudoCall {
pub fn clear(&mut self) {
self.maybe_call.take();
}
}
#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
pub struct RuntimeObject<Number, Hash> {
pub runtime_name: String,
pub runtime_type: RuntimeType,
pub runtime_upgrades: u32,
pub instance_count: u32,
pub hash: Hash,
pub raw_genesis: RawGenesis,
pub version: RuntimeVersion,
pub created_at: Number,
pub updated_at: Number,
}
pub fn system_digest_final_key() -> Vec<u8> {
frame_support::storage::storage_prefix("System".as_ref(), "Digest".as_ref()).to_vec()
}
pub fn operator_block_fees_final_key() -> Vec<u8> {
frame_support::storage::storage_prefix("BlockFees".as_ref(), "CollectedBlockFees".as_ref())
.to_vec()
}
#[derive(Debug, Encode)]
pub struct OperatorSigningKeyProofOfOwnershipData<AccountId> {
pub operator_owner: AccountId,
}
pub trait OnChainRewards<Balance> {
fn on_chain_rewards(chain_id: ChainId, reward: Balance);
}
impl<Balance> OnChainRewards<Balance> for () {
fn on_chain_rewards(_chain_id: ChainId, _reward: Balance) {}
}
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub enum OperatorRewardSource<Number> {
Bundle {
at_block_number: Number,
},
XDMProtocolFees,
#[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
Dummy,
}
sp_api::decl_runtime_apis! {
pub trait DomainsApi<DomainHeader: HeaderT> {
fn submit_bundle_unsigned(opaque_bundle: OpaqueBundle<NumberFor<Block>, Block::Hash, DomainHeader, Balance>);
fn submit_receipt_unsigned(singleton_receipt: SealedSingletonReceipt<NumberFor<Block>, Block::Hash, DomainHeader, Balance>);
fn extract_successful_bundles(
domain_id: DomainId,
extrinsics: Vec<Block::Extrinsic>,
) -> OpaqueBundles<Block, DomainHeader, Balance>;
fn extrinsics_shuffling_seed() -> Randomness;
fn domain_runtime_code(domain_id: DomainId) -> Option<Vec<u8>>;
fn runtime_id(domain_id: DomainId) -> Option<RuntimeId>;
fn domain_instance_data(domain_id: DomainId) -> Option<(DomainInstanceData, NumberFor<Block>)>;
fn timestamp() -> Moment;
fn domain_tx_range(domain_id: DomainId) -> U256;
fn genesis_state_root(domain_id: DomainId) -> Option<H256>;
fn head_receipt_number(domain_id: DomainId) -> HeaderNumberFor<DomainHeader>;
fn oldest_unconfirmed_receipt_number(domain_id: DomainId) -> Option<HeaderNumberFor<DomainHeader>>;
fn domain_bundle_limit(domain_id: DomainId) -> Option<DomainBundleLimit>;
fn non_empty_er_exists(domain_id: DomainId) -> bool;
fn domain_best_number(domain_id: DomainId) -> Option<HeaderNumberFor<DomainHeader>>;
fn execution_receipt(receipt_hash: HeaderHashFor<DomainHeader>) -> Option<ExecutionReceiptFor<DomainHeader, Block, Balance>>;
fn domain_operators(domain_id: DomainId) -> Option<(BTreeMap<OperatorId, Balance>, Vec<OperatorId>)>;
fn operator_id_by_signing_key(signing_key: OperatorPublicKey) -> Option<OperatorId>;
fn receipt_hash(domain_id: DomainId, domain_number: HeaderNumberFor<DomainHeader>) -> Option<HeaderHashFor<DomainHeader>>;
fn consensus_chain_byte_fee() -> Balance;
fn latest_confirmed_domain_block(domain_id: DomainId) -> Option<(HeaderNumberFor<DomainHeader>, HeaderHashFor<DomainHeader>)>;
fn is_bad_er_pending_to_prune(domain_id: DomainId, receipt_hash: HeaderHashFor<DomainHeader>) -> bool;
fn storage_fund_account_balance(operator_id: OperatorId) -> Balance;
fn is_domain_runtime_upgraded_since(domain_id: DomainId, at: NumberFor<Block>) -> Option<bool>;
fn domain_sudo_call(domain_id: DomainId) -> Option<Vec<u8>>;
fn last_confirmed_domain_block_receipt(domain_id: DomainId) ->Option<ExecutionReceiptFor<DomainHeader, Block, Balance>>;
}
pub trait BundleProducerElectionApi<Balance: Encode + Decode> {
fn bundle_producer_election_params(domain_id: DomainId) -> Option<BundleProducerElectionParams<Balance>>;
fn operator(operator_id: OperatorId) -> Option<(OperatorPublicKey, Balance)>;
}
}