Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Domain registry for domains v2 #1614

Merged
merged 12 commits into from
Jul 3, 2023
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/pallet-domains/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ sp-version = { version = "22.0.0", default-features = false, git = "https://gith
subspace-core-primitives = { version = "0.1.0", default-features = false, path = "../subspace-core-primitives" }

[dev-dependencies]
pallet-balances = { version = "4.0.0-dev", git = "https://github.com/subspace/substrate", rev = "55c157cff49b638a59d81a9f971f0f9a66829c71" }
sp-trie = { version = "22.0.0", git = "https://github.com/subspace/substrate", rev = "55c157cff49b638a59d81a9f971f0f9a66829c71" }
sp-externalities = { version = "0.19.0", git = "https://github.com/subspace/substrate", rev = "55c157cff49b638a59d81a9f971f0f9a66829c71" }

Expand Down
149 changes: 149 additions & 0 deletions crates/pallet-domains/src/domain_registry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
//! Domain registry for domains

use crate::{Config, DomainRegistry, NextDomainId, RuntimeRegistry};
use codec::{Decode, Encode};
use frame_support::traits::{Currency, LockIdentifier, LockableCurrency, WithdrawReasons};
use frame_support::weights::Weight;
use frame_support::{ensure, PalletError};
use scale_info::TypeInfo;
use sp_core::Get;
use sp_domains::{DomainId, GenesisDomain, RuntimeId};
use sp_runtime::traits::CheckedAdd;
use sp_std::vec::Vec;

const DOMAIN_INSTANCE_ID: LockIdentifier = *b"domains ";

pub type EpochIndex = u32;

/// Domain registry specific errors
#[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
pub enum Error {
InvalidBundlesPerBlock,
ExceedMaxDomainBlockWeight,
ExceedMaxDomainBlockSize,
MaxDomainId,
InvalidSlotProbability,
RuntimeNotFound,
InsufficientFund,
DomainNameTooLong,
}

#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
pub struct DomainConfig {
/// A user defined name for this domain, should be a human-readable UTF-8 encoded string.
pub domain_name: Vec<u8>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't there a BoundedVec for this purpose ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the domain_name is constructed by the user not by the runtime, thus we still need to check it manually, and after the domain is instantiated, domain_name is not supposed to change.

/// A pointer to the `RuntimeRegistry` entry for this domain.
pub runtime_id: RuntimeId,
/// The max block size for this domain, may not exceed the system-wide `MaxDomainBlockSize` limit.
pub max_block_size: u32,
/// The max block weight for this domain, may not exceed the system-wide `MaxDomainBlockWeight` limit.
pub max_block_weight: Weight,
/// The probability of successful bundle in a slot (active slots coefficient). This defines the
/// expected bundle production rate, must be `> 0` and `≤ 1`.
pub bundle_slot_probability: (u64, u64),
/// The expected number of bundles for a domain block, must be `≥ 1` and `≤ MaxBundlesPerBlock`.
pub target_bundles_per_block: u32,
}

impl DomainConfig {
pub(crate) fn from_genesis<T: Config>(
genesis_domain: &GenesisDomain<T::AccountId>,
runtime_id: RuntimeId,
) -> Self {
DomainConfig {
domain_name: genesis_domain.domain_name.clone(),
runtime_id,
max_block_size: genesis_domain.max_block_size,
max_block_weight: genesis_domain.max_block_weight,
bundle_slot_probability: genesis_domain.bundle_slot_probability,
target_bundles_per_block: genesis_domain.target_bundles_per_block,
}
}
}

#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
pub struct DomainObject<Number, Hash, AccountId> {
/// The address of the domain creator, used to validate updating the domain config.
pub owner_account_id: AccountId,
/// The consensus chain block number when the domain first instantiated.
pub created_at: Number,
/// The hash of the genesis execution receipt for this domain.
pub genesis_receipt_hash: Hash,
/// The domain genesis config.
pub domain_config: DomainConfig,
NingLin-P marked this conversation as resolved.
Show resolved Hide resolved
}

pub(crate) fn can_instantiate_domain<T: Config>(
owner_account_id: &T::AccountId,
domain_config: &DomainConfig,
) -> Result<(), Error> {
ensure!(
domain_config.domain_name.len() as u32 <= T::MaxDomainBlockSize::get(),
Error::DomainNameTooLong,
);
ensure!(
RuntimeRegistry::<T>::contains_key(domain_config.runtime_id),
Error::RuntimeNotFound
);
ensure!(
domain_config.max_block_size <= T::MaxDomainBlockSize::get(),
Error::ExceedMaxDomainBlockSize
);
ensure!(
domain_config.max_block_weight.ref_time() <= T::MaxDomainBlockWeight::get().ref_time(),
Error::ExceedMaxDomainBlockWeight
);
ensure!(
domain_config.target_bundles_per_block != 0
&& domain_config.target_bundles_per_block <= T::MaxBundlesPerBlock::get(),
Error::InvalidBundlesPerBlock
);

// `bundle_slot_probability` must be `> 0` and `≤ 1`
let (numerator, denominator) = domain_config.bundle_slot_probability;
ensure!(
numerator != 0 && denominator != 0 && numerator <= denominator,
Error::InvalidSlotProbability
);

ensure!(
T::Currency::free_balance(owner_account_id) >= T::DomainInstantiationDeposit::get(),
Error::InsufficientFund
);

Ok(())
}

pub(crate) fn do_instantiate_domain<T: Config>(
domain_config: DomainConfig,
owner_account_id: T::AccountId,
created_at: T::BlockNumber,
) -> Result<DomainId, Error> {
let domain_obj = DomainObject {
owner_account_id: owner_account_id.clone(),
NingLin-P marked this conversation as resolved.
Show resolved Hide resolved
created_at,
// TODO: drive the `genesis_receipt_hash` from genesis config through host function
vedhavyas marked this conversation as resolved.
Show resolved Hide resolved
genesis_receipt_hash: T::Hash::default(),
domain_config,
};
let domain_id = NextDomainId::<T>::get();
DomainRegistry::<T>::insert(domain_id, domain_obj);

let next_domain_id = domain_id.checked_add(&1.into()).ok_or(Error::MaxDomainId)?;
NextDomainId::<T>::set(next_domain_id);

// Lock up fund of the domain instance creator
T::Currency::set_lock(
DOMAIN_INSTANCE_ID,
&owner_account_id,
T::DomainInstantiationDeposit::get(),
WithdrawReasons::all(),
);

// TODO: initialize the stake summary for this domain

// TODO: initialize the genesis block in the domain block tree once we can drive the
// genesis ER from genesis config through host function
Comment on lines +147 to +148
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as genesis_block_hash, the genesis ER is initialized on the instantiation, not the genesis block.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The genesis block here is referring to the block in the block tree, not the actual domain block.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's obviously confusing, I know the confusion essentially comes from the block tree though 🤷‍♂️


Ok(domain_id)
}
130 changes: 117 additions & 13 deletions crates/pallet-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ mod benchmarking;
#[cfg(test)]
mod tests;

pub mod domain_registry;
pub mod runtime_registry;
pub mod weights;

use frame_support::traits::Get;
use frame_support::traits::{Currency, Get};
use frame_system::offchain::SubmitTransaction;
pub use pallet::*;
use sp_core::H256;
Expand All @@ -38,16 +39,25 @@ use sp_runtime::transaction_validity::TransactionValidityError;
use sp_std::vec::Vec;
use subspace_core_primitives::U256;

/// The balance type used by the currency system.
pub type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;

#[frame_support::pallet]
mod pallet {
use crate::calculate_tx_range;
use crate::domain_registry::{
can_instantiate_domain, do_instantiate_domain, DomainConfig, DomainObject,
Error as DomainRegistryError,
};
use crate::runtime_registry::{
do_register_runtime, do_schedule_runtime_upgrade, do_upgrade_runtimes,
register_runtime_at_genesis, Error as RuntimeRegistryError, RuntimeObject,
ScheduledRuntimeUpgrade,
};
use crate::weights::WeightInfo;
use crate::{calculate_tx_range, BalanceOf};
use frame_support::pallet_prelude::{StorageMap, *};
use frame_support::traits::LockableCurrency;
use frame_support::weights::Weight;
use frame_support::{Identity, PalletError};
use frame_system::pallet_prelude::*;
Expand All @@ -56,7 +66,7 @@ mod pallet {
use sp_domains::fraud_proof::FraudProof;
use sp_domains::transaction::InvalidTransactionCode;
use sp_domains::{
DomainId, ExecutorPublicKey, GenesisDomainRuntime, OpaqueBundle, RuntimeId, RuntimeType,
DomainId, ExecutorPublicKey, GenesisDomain, OpaqueBundle, RuntimeId, RuntimeType,
};
use sp_runtime::traits::{BlockNumberProvider, Zero};
use sp_std::fmt::Debug;
Expand All @@ -73,6 +83,29 @@ mod pallet {
/// Delay before a domain runtime is upgraded.
type DomainRuntimeUpgradeDelay: Get<Self::BlockNumber>;

/// The maximum block size limit for all domain.
#[pallet::constant]
type MaxDomainBlockSize: Get<u32>;

/// The maximum block weight limit for all domain.
#[pallet::constant]
type MaxDomainBlockWeight: Get<Weight>;

/// The maximum bundle per block limit for all domain.
#[pallet::constant]
type MaxBundlesPerBlock: Get<u32>;

/// The maximum domain name length limit for all domain.
#[pallet::constant]
type MaxDomainNameLength: Get<u32>;

/// The amount of fund to be locked up for the domain instance creator.
#[pallet::constant]
type DomainInstantiationDeposit: Get<BalanceOf<Self>>;

/// The currency trait.
type Currency: LockableCurrency<Self::AccountId, Moment = Self::BlockNumber>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realize that we shouldn't continue using LockableCurrency as it has been deprecated since paritytech/substrate#12951. Now that we're starting the domains from scratch, better not use the deprecated interfaces from day one. Non-blocker, can be resolved in another PR after. cc @vedhavyas

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, will resolve it in my upcoming PR.


/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;

Expand Down Expand Up @@ -113,6 +146,20 @@ mod pallet {
OptionQuery,
>;

/// Stores the next domain id.
#[pallet::storage]
pub(super) type NextDomainId<T> = StorageValue<_, DomainId, ValueQuery>;

/// The domain registry
#[pallet::storage]
pub(super) type DomainRegistry<T: Config> = StorageMap<
_,
Identity,
DomainId,
DomainObject<T::BlockNumber, T::Hash, T::AccountId>,
OptionQuery,
>;

#[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
pub enum BundleError {
/// The signer of bundle is unexpected.
Expand Down Expand Up @@ -179,6 +226,12 @@ mod pallet {
}
}

impl<T> From<DomainRegistryError> for Error<T> {
fn from(err: DomainRegistryError) -> Self {
Error::DomainRegistry(err)
}
}

#[pallet::error]
pub enum Error<T> {
/// Can not find the block hash of given primary block number.
Expand All @@ -189,6 +242,8 @@ mod pallet {
FraudProof(FraudProofError),
/// Runtime registry specific errors
RuntimeRegistry(RuntimeRegistryError),
/// Domain registry specific errors
DomainRegistry(DomainRegistryError),
}

#[pallet::event]
Expand All @@ -211,6 +266,10 @@ mod pallet {
DomainRuntimeUpgraded {
runtime_id: RuntimeId,
},
DomainInstantiated {
domain_id: DomainId,
runtime_id: RuntimeId,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if the runtime_id is useful here as this is part of the domain object, no ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Runtime event is used to send notifications about changes or conditions in the runtime to external entities like users, chain explorers, or dApps. I think runtime_id is important info about the domain instance that is worth delivery (just like runtime_type in DomainRuntimeCreated event).

Copy link
Contributor

@vedhavyas vedhavyas Jul 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I agree here. Events should signal that domain was created with a specific DomainId. Rest of the information should be fetched from the state as required. If we add runtime_id then we should add runtime_type as well as just runtime_id is useless by itself since users or chain explorers or dapps will have to make state call to fetch that information.

IMO, events should hold as minimum details as possible but still points to bigger information on the state

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I don't have strong opinion here, the runtime_id is removed now PTAL.

},
}

/// Per-domain state for tx range calculation.
Expand Down Expand Up @@ -364,27 +423,72 @@ mod pallet {

Ok(())
}

#[pallet::call_index(4)]
#[pallet::weight((Weight::from_all(10_000), Pays::Yes))]
// TODO: proper benchmark
pub fn instantiate_domain(
origin: OriginFor<T>,
domain_config: DomainConfig,
) -> DispatchResult {
let who = ensure_signed(origin)?;

can_instantiate_domain::<T>(&who, &domain_config).map_err(Error::<T>::from)?;

let runtime_id = domain_config.runtime_id;
let created_at = frame_system::Pallet::<T>::current_block_number();

let domain_id = do_instantiate_domain::<T>(domain_config, who, created_at)
NingLin-P marked this conversation as resolved.
Show resolved Hide resolved
.map_err(Error::<T>::from)?;

Self::deposit_event(Event::DomainInstantiated {
domain_id,
runtime_id,
});

Ok(())
}
}

#[pallet::genesis_config]
#[derive(Default)]
pub struct GenesisConfig {
pub genesis_domain_runtime: Option<GenesisDomainRuntime>,
pub struct GenesisConfig<T: Config> {
pub genesis_domain: Option<GenesisDomain<T::AccountId>>,
}

impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
GenesisConfig {
genesis_domain: None,
}
}
}

#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig {
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
fn build(&self) {
if let Some(genesis_domain_runtime) = &self.genesis_domain_runtime {
if let Some(genesis_domain) = &self.genesis_domain {
// Register the genesis domain runtime
register_runtime_at_genesis::<T>(
genesis_domain_runtime.name.clone(),
genesis_domain_runtime.runtime_type.clone(),
genesis_domain_runtime.runtime_version.clone(),
genesis_domain_runtime.code.clone(),
let runtime_id = register_runtime_at_genesis::<T>(
genesis_domain.runtime_name.clone(),
genesis_domain.runtime_type.clone(),
genesis_domain.runtime_version.clone(),
genesis_domain.code.clone(),
Zero::zero(),
)
.expect("Genesis runtime registration must always succeed");

// Instantiate the genesis domain
let domain_config = DomainConfig::from_genesis::<T>(genesis_domain, runtime_id);
can_instantiate_domain::<T>(&genesis_domain.owner_account_id, &domain_config)
.expect("Genesis domain config must be valid");

let created_at = frame_system::Pallet::<T>::current_block_number();
do_instantiate_domain::<T>(
domain_config,
genesis_domain.owner_account_id.clone(),
created_at,
)
.expect("Genesis domain instantiation must always succeed");
}
}
}
Expand Down
Loading