Skip to content

Commit

Permalink
Merge pull request #2236 from subspace/evm_chain_id
Browse files Browse the repository at this point in the history
Allow incremental EVM chain id for each EVM instance
  • Loading branch information
vedhavyas authored Nov 20, 2023
2 parents 2463744 + 4fc8448 commit 55e84ec
Show file tree
Hide file tree
Showing 13 changed files with 284 additions and 29 deletions.
3 changes: 2 additions & 1 deletion crates/pallet-domains/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
codec = { package = "parity-scale-codec", version = "3.6.5", default-features = false, features = ["derive"] }
domain-runtime-primitives = { version = "0.1.0", default-features = false, path = "../../domains/primitives/runtime" }
frame-benchmarking = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "892bf8e938c6bd2b893d3827d1093cd81baa59a1", optional = true }
frame-support = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "892bf8e938c6bd2b893d3827d1093cd81baa59a1" }
frame-system = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "892bf8e938c6bd2b893d3827d1093cd81baa59a1" }
Expand All @@ -30,7 +31,6 @@ subspace-runtime-primitives = { version = "0.1.0", default-features = false, pat

[dev-dependencies]
domain-pallet-executive = { version = "0.1.0", default-features = false, path = "../../domains/pallets/executive" }
domain-runtime-primitives = { version = "0.1.0", default-features = false, path = "../../domains/primitives/runtime" }
pallet-balances = { version = "4.0.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "892bf8e938c6bd2b893d3827d1093cd81baa59a1" }
pallet-timestamp = { version = "4.0.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "892bf8e938c6bd2b893d3827d1093cd81baa59a1" }
sp-externalities = { version = "0.19.0", git = "https://github.com/subspace/polkadot-sdk", rev = "892bf8e938c6bd2b893d3827d1093cd81baa59a1" }
Expand All @@ -41,6 +41,7 @@ sp-trie = { version = "22.0.0", git = "https://github.com/subspace/polkadot-sdk"
default = ["std"]
std = [
"codec/std",
"domain-runtime-primitives/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
Expand Down
26 changes: 21 additions & 5 deletions crates/pallet-domains/src/domain_registry.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
//! Domain registry for domains
use crate::block_tree::import_genesis_receipt;
use crate::pallet::DomainStakingSummary;
use crate::pallet::{DomainStakingSummary, NextEVMChainId};
use crate::runtime_registry::DomainRuntimeInfo;
use crate::staking::StakingSummary;
use crate::{
Config, DomainHashingFor, DomainRegistry, ExecutionReceiptOf, HoldIdentifier, NextDomainId,
Expand All @@ -18,6 +19,7 @@ use scale_info::TypeInfo;
use sp_core::Get;
use sp_domains::{
derive_domain_block_hash, DomainId, DomainsDigestItem, OperatorAllowList, RuntimeId,
RuntimeType,
};
use sp_runtime::traits::{CheckedAdd, Zero};
use sp_runtime::DigestItem;
Expand All @@ -31,6 +33,7 @@ pub enum Error {
ExceedMaxDomainBlockWeight,
ExceedMaxDomainBlockSize,
MaxDomainId,
MaxEVMChainId,
InvalidSlotProbability,
RuntimeNotFound,
InsufficientFund,
Expand Down Expand Up @@ -70,6 +73,8 @@ pub struct DomainObject<Number, ReceiptHash, AccountId: Ord> {
pub genesis_receipt_hash: ReceiptHash,
/// The domain config.
pub domain_config: DomainConfig<AccountId>,
/// Domain runtime specific information.
pub domain_runtime_info: DomainRuntimeInfo,
}

pub(crate) fn can_instantiate_domain<T: Config>(
Expand Down Expand Up @@ -122,13 +127,23 @@ pub(crate) fn do_instantiate_domain<T: Config>(
can_instantiate_domain::<T>(&owner_account_id, &domain_config)?;

let domain_id = NextDomainId::<T>::get();
let runtime_obj = RuntimeRegistry::<T>::get(domain_config.runtime_id)
.expect("Runtime object must exist as checked in `can_instantiate_domain`; qed");

let domain_runtime_info = match runtime_obj.runtime_type {
RuntimeType::Evm => {
let evm_chain_id = NextEVMChainId::<T>::get();
let next_evm_chain_id = evm_chain_id.checked_add(1).ok_or(Error::MaxEVMChainId)?;
NextEVMChainId::<T>::set(next_evm_chain_id);
DomainRuntimeInfo::EVM {
chain_id: evm_chain_id,
}
}
};

let genesis_receipt = {
let runtime_obj = RuntimeRegistry::<T>::get(domain_config.runtime_id)
.expect("Runtime object must exist as checked in `can_instantiate_domain`; qed");

let state_version = runtime_obj.version.state_version();
let raw_genesis = runtime_obj.into_complete_raw_genesis(domain_id);
let raw_genesis = runtime_obj.into_complete_raw_genesis(domain_id, domain_runtime_info);
let state_root = raw_genesis.state_root::<DomainHashingFor<T>>(state_version);
let genesis_block_hash = derive_domain_block_hash::<T::DomainHeader>(
Zero::zero(),
Expand All @@ -151,6 +166,7 @@ pub(crate) fn do_instantiate_domain<T: Config>(
created_at,
genesis_receipt_hash,
domain_config,
domain_runtime_info,
};
DomainRegistry::<T>::insert(domain_id, domain_obj);

Expand Down
44 changes: 33 additions & 11 deletions crates/pallet-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ mod tests;

pub mod block_tree;
pub mod domain_registry;
// TODO: remove once the runtime is upgraded.
pub mod migrations;
pub mod runtime_registry;
mod staking;
mod staking_epoch;
Expand Down Expand Up @@ -145,6 +147,7 @@ mod pallet {
};
use alloc::string::String;
use codec::FullCodec;
use domain_runtime_primitives::EVMChainId;
use frame_support::pallet_prelude::*;
use frame_support::traits::fungible::{InspectHold, Mutate, MutateHold};
use frame_support::traits::Randomness as RandomnessT;
Expand Down Expand Up @@ -313,6 +316,20 @@ mod pallet {
#[pallet::storage]
pub(super) type NextRuntimeId<T> = StorageValue<_, RuntimeId, ValueQuery>;

/// Starting EVM chain ID for evm runtimes.
pub struct StartingEVMChainId;
impl Get<EVMChainId> for StartingEVMChainId {
fn get() -> EVMChainId {
// after looking at `https://chainlist.org/?testnets=false`
// we think starting with `490000` would not have much clashes
490000
}
}

/// Stores the next evm chain id.
#[pallet::storage]
pub(super) type NextEVMChainId<T> = StorageValue<_, EVMChainId, ValueQuery, StartingEVMChainId>;

#[pallet::storage]
pub(super) type RuntimeRegistry<T: Config> =
StorageMap<_, Identity, RuntimeId, RuntimeObject<BlockNumberFor<T>, T::Hash>, OptionQuery>;
Expand Down Expand Up @@ -1389,17 +1406,19 @@ impl<T: Config> Pallet<T> {
}

pub fn runtime_id(domain_id: DomainId) -> Option<RuntimeId> {
DomainRegistry::<T>::get(domain_id)
crate::migrations::migrate_domain_object::get_domain_object::<T>(domain_id)
.map(|domain_object| domain_object.domain_config.runtime_id)
}

pub fn domain_instance_data(
domain_id: DomainId,
) -> Option<(DomainInstanceData, BlockNumberFor<T>)> {
let domain_obj = DomainRegistry::<T>::get(domain_id)?;
let domain_obj =
crate::migrations::migrate_domain_object::get_domain_object::<T>(domain_id)?;
let runtime_object = RuntimeRegistry::<T>::get(domain_obj.domain_config.runtime_id)?;
let runtime_type = runtime_object.runtime_type.clone();
let raw_genesis = runtime_object.into_complete_raw_genesis(domain_id);
let raw_genesis =
runtime_object.into_complete_raw_genesis(domain_id, domain_obj.domain_runtime_info);
Some((
DomainInstanceData {
runtime_type,
Expand Down Expand Up @@ -1427,7 +1446,7 @@ impl<T: Config> Pallet<T> {
domain_id: DomainId,
) -> Option<BundleProducerElectionParams<BalanceOf<T>>> {
match (
DomainRegistry::<T>::get(domain_id),
crate::migrations::migrate_domain_object::get_domain_object::<T>(domain_id),
DomainStakingSummary::<T>::get(domain_id),
) {
(Some(domain_object), Some(stake_summary)) => Some(BundleProducerElectionParams {
Expand Down Expand Up @@ -1509,9 +1528,10 @@ impl<T: Config> Pallet<T> {

Self::check_bundle_duplication(opaque_bundle)?;

let domain_config = DomainRegistry::<T>::get(domain_id)
.ok_or(BundleError::InvalidDomainId)?
.domain_config;
let domain_config =
crate::migrations::migrate_domain_object::get_domain_object::<T>(domain_id)
.ok_or(BundleError::InvalidDomainId)?
.domain_config;

// TODO: check bundle weight with `domain_config.max_block_weight`

Expand Down Expand Up @@ -1798,10 +1818,12 @@ impl<T: Config> Pallet<T> {

/// Returns the domain block limit of the given domain.
pub fn domain_block_limit(domain_id: DomainId) -> Option<DomainBlockLimit> {
DomainRegistry::<T>::get(domain_id).map(|domain_obj| DomainBlockLimit {
max_block_size: domain_obj.domain_config.max_block_size,
max_block_weight: domain_obj.domain_config.max_block_weight,
})
crate::migrations::migrate_domain_object::get_domain_object::<T>(domain_id).map(
|domain_obj| DomainBlockLimit {
max_block_size: domain_obj.domain_config.max_block_size,
max_block_weight: domain_obj.domain_config.max_block_weight,
},
)
}

/// Returns if there are any ERs in the challenge period that have non empty extrinsics.
Expand Down
161 changes: 161 additions & 0 deletions crates/pallet-domains/src/migrations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
//! Migration module for pallet-domains
// TODO: remove the entire module once the runtime is upgraded
pub mod migrate_domain_object {
use crate::domain_registry::DomainConfig;
use crate::runtime_registry::DomainRuntimeInfo;
use crate::{Config, ReceiptHashFor};
use codec::{Decode, Encode};
use frame_support::pallet_prelude::{OptionQuery, StorageVersion, TypeInfo, Weight};
use frame_support::traits::{GetStorageVersion, OnRuntimeUpgrade};
use frame_support::{storage_alias, Identity};
use frame_system::pallet_prelude::BlockNumberFor;
use sp_core::Get;
use sp_domains::DomainId;
use sp_std::vec::Vec;

/// Previous domain object that needs to be migrated.
#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
pub struct DomainObject<Number, ReceiptHash, AccountId: Ord> {
/// 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: ReceiptHash,
/// The domain config.
pub domain_config: DomainConfig<AccountId>,
}

impl<Number, ReceiptHash, AccountId: Ord> From<DomainObject<Number, ReceiptHash, AccountId>>
for crate::domain_registry::DomainObject<Number, ReceiptHash, AccountId>
{
fn from(old_domain_obj: DomainObject<Number, ReceiptHash, AccountId>) -> Self {
crate::domain_registry::DomainObject {
owner_account_id: old_domain_obj.owner_account_id,
created_at: old_domain_obj.created_at,
genesis_receipt_hash: old_domain_obj.genesis_receipt_hash,
domain_config: old_domain_obj.domain_config,
// 1002 is the evm chain id for gemini networks
domain_runtime_info: DomainRuntimeInfo::EVM { chain_id: 1002 },
}
}
}

#[storage_alias]
pub type DomainRegistry<T: Config> = StorageMap<
crate::Pallet<T>,
Identity,
DomainId,
DomainObject<BlockNumberFor<T>, ReceiptHashFor<T>, <T as frame_system::Config>::AccountId>,
OptionQuery,
>;

/// Whenever there is a migration to specific storage and that storage item is used as part of the runtimeAPI,
/// decoding such storage item fails
/// Here is an example:
/// If runtime upgrade happened on Block #100, migration is not done yet
/// But since the :code already holds the new runtime, runtime api will use the new Runtime when
/// queried at Block #100. This essentially causes a Decode to fail and cause State Corrupt error.
///
/// Once the migration is done at block #101, this runtime api works as expected.
/// To adjust to the case for Block #100 specifically, we try to decode with latest type
/// and then fall back to old type.
pub fn get_domain_object<T: Config>(
domain_id: DomainId,
) -> Option<
crate::domain_registry::DomainObject<
BlockNumberFor<T>,
ReceiptHashFor<T>,
<T as frame_system::Config>::AccountId,
>,
> {
crate::DomainRegistry::<T>::get(domain_id).or_else(|| {
let old_domain_obj = DomainRegistry::<T>::get(domain_id)?;
Some(old_domain_obj.into())
})
}

pub struct MigrateDomainObject<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for MigrateDomainObject<T> {
fn on_runtime_upgrade() -> Weight {
if crate::Pallet::<T>::on_chain_storage_version() > 0 {
log::info!(target: "pallet-domains", "MigrateDomainObject should be removed");
return T::DbWeight::get().reads(1);
}

let updated_domain_objects = DomainRegistry::<T>::drain()
.map(|(domain_id, old_domain_obj)| {
let new_domain_object = old_domain_obj.into();
(domain_id, new_domain_object)
})
.collect::<Vec<(DomainId, crate::domain_registry::DomainObject<_, _, _>)>>();

let count = updated_domain_objects.len() as u64;
log::info!(target: "pallet-domains", "Migrating {count:?} Domain objects");

for (domain_id, domain_obj) in updated_domain_objects {
crate::DomainRegistry::<T>::insert(domain_id, domain_obj)
}

StorageVersion::new(1).put::<crate::Pallet<T>>();

// +1 for storage version read and write
T::DbWeight::get().reads_writes(count + 1, count + 1)
}
}
}

#[cfg(test)]
mod tests {
use crate::domain_registry::DomainConfig;
use crate::runtime_registry::DomainRuntimeInfo;
use crate::tests::{new_test_ext, Test};
use frame_support::traits::OnRuntimeUpgrade;
use sp_core::H256;
use sp_domains::{DomainId, OperatorAllowList};

#[test]
fn migration_domain_objects() {
let mut ext = new_test_ext();

ext.execute_with(|| {
let old_obj = crate::migrations::migrate_domain_object::DomainObject {
owner_account_id: 1,
created_at: 10,
genesis_receipt_hash: H256::random(),
domain_config: DomainConfig {
domain_name: "evm".to_string(),
runtime_id: 0,
max_block_size: 0,
max_block_weight: Default::default(),
bundle_slot_probability: (0, 0),
target_bundles_per_block: 0,
operator_allow_list: OperatorAllowList::Anyone,
},
};

let domain_id = DomainId::new(1);
crate::migrations::migrate_domain_object::DomainRegistry::<Test>::insert(
domain_id, old_obj,
);
});

ext.commit_all().unwrap();

ext.execute_with(|| {
assert_eq!(
crate::migrations::migrate_domain_object::MigrateDomainObject::<Test>::on_runtime_upgrade(),
<Test as frame_system::Config>::DbWeight::get().reads_writes(2, 2),
);

let domain_runtime_info = crate::DomainRegistry::<Test>::get(DomainId::new(1))
.unwrap()
.domain_runtime_info;
assert_eq!(
domain_runtime_info,
DomainRuntimeInfo::EVM { chain_id: 1002 }
)
})
}
}
22 changes: 21 additions & 1 deletion crates/pallet-domains/src/runtime_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::pallet::{NextRuntimeId, RuntimeRegistry, ScheduledRuntimeUpgrades};
use crate::{Config, Event};
use alloc::string::String;
use codec::{Decode, Encode};
use domain_runtime_primitives::EVMChainId;
use frame_support::PalletError;
use frame_system::pallet_prelude::*;
use scale_info::TypeInfo;
Expand Down Expand Up @@ -43,13 +44,32 @@ pub struct RuntimeObject<Number, Hash> {
pub updated_at: Number,
}

/// Domain runtime specific information to create domain raw genesis.
#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Copy)]
pub enum DomainRuntimeInfo {
EVM { chain_id: EVMChainId },
}

impl Default for DomainRuntimeInfo {
fn default() -> Self {
Self::EVM { chain_id: 0 }
}
}

impl<Number, Hash> RuntimeObject<Number, Hash> {
// Return a complete raw genesis with runtime code and domain id set properly
pub fn into_complete_raw_genesis(self, domain_id: DomainId) -> RawGenesis {
pub fn into_complete_raw_genesis(
self,
domain_id: DomainId,
domain_runtime_info: DomainRuntimeInfo,
) -> RawGenesis {
let RuntimeObject {
mut raw_genesis, ..
} = self;
raw_genesis.set_domain_id(domain_id);
match domain_runtime_info {
DomainRuntimeInfo::EVM { chain_id } => raw_genesis.set_evm_chain_id(chain_id),
}
raw_genesis
}
}
Expand Down
Loading

0 comments on commit 55e84ec

Please sign in to comment.