diff --git a/Cargo.lock b/Cargo.lock index 32bd7a82e4..9dcecae169 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1866,6 +1866,7 @@ version = "0.1.0" dependencies = [ "async-trait", "sc-consensus", + "sc-consensus-subspace", "sp-blockchain", "sp-consensus", "sp-core", @@ -1907,6 +1908,8 @@ dependencies = [ "sp-consensus", "sp-consensus-slots", "sp-core", + "sp-domain-digests", + "sp-domain-tracker", "sp-domains", "sp-keyring", "sp-keystore", @@ -2008,6 +2011,7 @@ dependencies = [ "domain-client-consensus-relay-chain", "domain-client-executor", "domain-client-executor-gossip", + "domain-client-message-relayer", "domain-runtime-primitives", "frame-benchmarking", "frame-benchmarking-cli", @@ -2019,6 +2023,7 @@ dependencies = [ "sc-chain-spec", "sc-client-api", "sc-consensus", + "sc-consensus-subspace", "sc-executor", "sc-network", "sc-rpc", @@ -2036,9 +2041,11 @@ dependencies = [ "sp-consensus", "sp-consensus-slots", "sp-core", + "sp-domain-tracker", "sp-domains", "sp-inherents", "sp-keystore", + "sp-messenger", "sp-offchain", "sp-runtime", "sp-session", @@ -2066,7 +2073,9 @@ dependencies = [ "log", "pallet-balances", "pallet-domain-registry", + "pallet-domain-tracker", "pallet-executor-registry", + "pallet-messenger", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", @@ -2074,9 +2083,11 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-core", + "sp-domain-tracker", "sp-domains", "sp-inherents", "sp-io", + "sp-messenger", "sp-offchain", "sp-runtime", "sp-session", @@ -4932,11 +4943,13 @@ dependencies = [ "frame-system", "log", "pallet-balances", + "pallet-domain-tracker", "pallet-executor-registry", "parity-scale-codec", "scale-info", "serde", "sp-core", + "sp-domain-tracker", "sp-domains", "sp-executor-registry", "sp-io", @@ -4955,6 +4968,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core", + "sp-domain-digests", "sp-domain-tracker", "sp-domains", "sp-io", @@ -8033,6 +8047,17 @@ dependencies = [ "syn", ] +[[package]] +name = "sp-domain-digests" +version = "0.1.0" +dependencies = [ + "parity-scale-codec", + "sp-api", + "sp-core", + "sp-domain-tracker", + "sp-runtime", +] + [[package]] name = "sp-domain-tracker" version = "0.1.0" @@ -8041,7 +8066,6 @@ dependencies = [ "sp-api", "sp-core", "sp-domains", - "sp-inherents", ] [[package]] @@ -8211,6 +8235,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-api", + "sp-core", "sp-domains", "sp-runtime", "sp-trie", @@ -9408,7 +9433,9 @@ dependencies = [ "log", "pallet-balances", "pallet-domain-registry", + "pallet-domain-tracker", "pallet-executor-registry", + "pallet-messenger", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", @@ -9416,9 +9443,11 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-core", + "sp-domain-tracker", "sp-domains", "sp-inherents", "sp-io", + "sp-messenger", "sp-offchain", "sp-runtime", "sp-session", diff --git a/crates/sc-consensus-subspace/src/notification.rs b/crates/sc-consensus-subspace/src/notification.rs index 3075ab07da..2edbec48ac 100644 --- a/crates/sc-consensus-subspace/src/notification.rs +++ b/crates/sc-consensus-subspace/src/notification.rs @@ -29,7 +29,7 @@ type SharedNotificationSenders = Arc>>>; /// The sending half of the Subspace notification channel(s). #[derive(Clone)] -pub(crate) struct SubspaceNotificationSender { +pub struct SubspaceNotificationSender { subscribers: SharedNotificationSenders, } @@ -40,7 +40,7 @@ impl SubspaceNotificationSender(&self, get_value: F) + pub fn notify(&self, get_value: F) where F: FnOnce() -> T, { @@ -83,7 +83,7 @@ impl SubspaceNotificationStream( +pub fn channel( stream_name: &'static str, ) -> (SubspaceNotificationSender, SubspaceNotificationStream) where diff --git a/crates/subspace-node/src/bin/subspace-node.rs b/crates/subspace-node/src/bin/subspace-node.rs index 2191c48966..0b72380205 100644 --- a/crates/subspace-node/src/bin/subspace-node.rs +++ b/crates/subspace-node/src/bin/subspace-node.rs @@ -16,6 +16,7 @@ //! Subspace node implementation. +use domain_service::Configuration; use frame_benchmarking_cli::BenchmarkCmd; use futures::future::TryFutureExt; use futures::StreamExt; @@ -427,11 +428,11 @@ fn main() -> Result<(), Error> { ); // Increase default number of peers - if secondary_chain_cli.run.network_params.out_peers == 25 { - secondary_chain_cli.run.network_params.out_peers = 50; + if secondary_chain_cli.run.run_system.network_params.out_peers == 25 { + secondary_chain_cli.run.run_system.network_params.out_peers = 50; } - let secondary_chain_config = SubstrateCli::create_configuration( + let service_config = SubstrateCli::create_configuration( &secondary_chain_cli, &secondary_chain_cli, tokio_handle.clone(), @@ -442,6 +443,14 @@ fn main() -> Result<(), Error> { )) })?; + let secondary_chain_config = + Configuration::new(service_config, secondary_chain_cli.run.relayer_id) + .map_err(|error| { + sc_service::Error::Other(format!( + "Failed to create secondary chain configuration: {error:?}" + )) + })?; + let imported_block_notification_stream = || { primary_chain_node .imported_block_notification_stream diff --git a/crates/subspace-node/src/secondary_chain/cli.rs b/crates/subspace-node/src/secondary_chain/cli.rs index 84da938d94..2bf7d52fe3 100644 --- a/crates/subspace-node/src/secondary_chain/cli.rs +++ b/crates/subspace-node/src/secondary_chain/cli.rs @@ -18,7 +18,7 @@ use crate::core_domain::cli::CoreDomainCli; use clap::Parser; use sc_cli::{ ChainSpec, CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams, - NetworkParams, Result, RunCmd, RuntimeVersion, SharedParams, SubstrateCli, + NetworkParams, Result, RunCmd as SubstrateRunCmd, RuntimeVersion, SharedParams, SubstrateCli, }; use sc_service::config::PrometheusConfig; use sc_service::BasePath; @@ -43,10 +43,14 @@ pub enum Subcommand { } #[derive(Debug, Parser)] -struct DomainCli { +pub struct DomainCli { /// Run a node. #[clap(flatten)] - pub run_system: RunCmd, + pub run_system: SubstrateRunCmd, + + /// Optional relayer address to relay messages on behalf. + #[arg(long)] + pub relayer_id: Option, #[clap(raw = true)] pub core_domain_args: Vec, @@ -54,7 +58,7 @@ struct DomainCli { pub struct SecondaryChainCli { /// Run a node. - pub run: RunCmd, + pub run: DomainCli, /// The base path that should be used by the secondary chain. pub base_path: Option, @@ -86,7 +90,7 @@ impl SecondaryChainCli { Self { base_path: base_path.as_mut().map(|path| path.join("system")), chain_spec, - run: domain_cli.run_system, + run: domain_cli, }, maybe_core_domain_cli, ) @@ -129,7 +133,7 @@ impl SubstrateCli for SecondaryChainCli { // In case there are bootstrap nodes specified explicitly, ignore those that are in the // chain spec - if !self.run.network_params.bootnodes.is_empty() { + if !self.run.run_system.network_params.bootnodes.is_empty() { let mut chain_spec_value: Value = serde_json::from_str(&chain_spec.as_json(true)?) .map_err(|error| error.to_string())?; if let Some(boot_nodes) = chain_spec_value.get_mut("bootNodes") { @@ -172,19 +176,19 @@ impl DefaultConfigurationValues for SecondaryChainCli { impl CliConfiguration for SecondaryChainCli { fn shared_params(&self) -> &SharedParams { - self.run.shared_params() + self.run.run_system.shared_params() } fn import_params(&self) -> Option<&ImportParams> { - self.run.import_params() + self.run.run_system.import_params() } fn network_params(&self) -> Option<&NetworkParams> { - self.run.network_params() + self.run.run_system.network_params() } fn keystore_params(&self) -> Option<&KeystoreParams> { - self.run.keystore_params() + self.run.run_system.keystore_params() } fn base_path(&self) -> Result> { @@ -200,15 +204,15 @@ impl CliConfiguration for SecondaryChainCli { } fn rpc_http(&self, default_listen_port: u16) -> Result> { - self.run.rpc_http(default_listen_port) + self.run.run_system.rpc_http(default_listen_port) } fn rpc_ipc(&self) -> Result> { - self.run.rpc_ipc() + self.run.run_system.rpc_ipc() } fn rpc_ws(&self, default_listen_port: u16) -> Result> { - self.run.rpc_ws(default_listen_port) + self.run.run_system.rpc_ws(default_listen_port) } fn prometheus_config( @@ -216,65 +220,67 @@ impl CliConfiguration for SecondaryChainCli { default_listen_port: u16, chain_spec: &Box, ) -> Result> { - self.run.prometheus_config(default_listen_port, chain_spec) + self.run + .run_system + .prometheus_config(default_listen_port, chain_spec) } fn chain_id(&self, is_dev: bool) -> Result { - self.run.chain_id(is_dev) + self.run.run_system.chain_id(is_dev) } fn role(&self, is_dev: bool) -> Result { - self.run.role(is_dev) + self.run.run_system.role(is_dev) } fn transaction_pool(&self, is_dev: bool) -> Result { - self.run.transaction_pool(is_dev) + self.run.run_system.transaction_pool(is_dev) } fn trie_cache_maximum_size(&self) -> Result> { - self.run.trie_cache_maximum_size() + self.run.run_system.trie_cache_maximum_size() } fn rpc_methods(&self) -> Result { - self.run.rpc_methods() + self.run.run_system.rpc_methods() } fn rpc_ws_max_connections(&self) -> Result> { - self.run.rpc_ws_max_connections() + self.run.run_system.rpc_ws_max_connections() } fn rpc_cors(&self, is_dev: bool) -> Result>> { - self.run.rpc_cors(is_dev) + self.run.run_system.rpc_cors(is_dev) } fn default_heap_pages(&self) -> Result> { - self.run.default_heap_pages() + self.run.run_system.default_heap_pages() } fn force_authoring(&self) -> Result { - self.run.force_authoring() + self.run.run_system.force_authoring() } fn disable_grandpa(&self) -> Result { - self.run.disable_grandpa() + self.run.run_system.disable_grandpa() } fn max_runtime_instances(&self) -> Result> { - self.run.max_runtime_instances() + self.run.run_system.max_runtime_instances() } fn announce_block(&self) -> Result { - self.run.announce_block() + self.run.run_system.announce_block() } fn dev_key_seed(&self, is_dev: bool) -> Result> { - self.run.dev_key_seed(is_dev) + self.run.run_system.dev_key_seed(is_dev) } fn telemetry_endpoints( &self, chain_spec: &Box, ) -> Result> { - self.run.telemetry_endpoints(chain_spec) + self.run.run_system.telemetry_endpoints(chain_spec) } } diff --git a/domains/client/consensus-relay-chain/Cargo.toml b/domains/client/consensus-relay-chain/Cargo.toml index b8ca55d350..58afa0422e 100644 --- a/domains/client/consensus-relay-chain/Cargo.toml +++ b/domains/client/consensus-relay-chain/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] async-trait = "0.1.57" sc-consensus = { git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } +sc-consensus-subspace = { version = "0.1.0", path = "../../../crates/sc-consensus-subspace" } sp-blockchain = { git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } sp-consensus = { git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } sp-core = { git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } diff --git a/domains/client/consensus-relay-chain/src/import_queue.rs b/domains/client/consensus-relay-chain/src/import_queue.rs index c9ab4da242..c5178bd75a 100644 --- a/domains/client/consensus-relay-chain/src/import_queue.rs +++ b/domains/client/consensus-relay-chain/src/import_queue.rs @@ -18,12 +18,14 @@ use sc_consensus::import_queue::{BasicQueue, Verifier as VerifierT}; use sc_consensus::{ BlockCheckParams, BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult, }; +use sc_consensus_subspace::notification::{SubspaceNotificationSender, SubspaceNotificationStream}; use sp_blockchain::Result as ClientResult; use sp_consensus::error::Error as ConsensusError; use sp_consensus::{BlockOrigin, CacheKeyId}; use sp_core::traits::SpawnEssentialNamed; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; use std::collections::HashMap; +use std::fmt::Debug; use std::marker::PhantomData; use substrate_prometheus_endpoint::Registry; @@ -32,17 +34,32 @@ use substrate_prometheus_endpoint::Registry; /// This is used to set `block_import_params.fork_choice` to `false` as long as the block origin is /// not `NetworkInitialSync`. The best block for secondary chain is determined by the primary chain. /// Meaning we will update the best block, as it is included by the primary chain. -struct SecondaryChainBlockImport(I); +struct SecondaryChainBlockImport +where + Number: Clone + Send + Sync + Debug + 'static, +{ + inner: I, + imported_block_notification_sender: SubspaceNotificationSender, +} -impl SecondaryChainBlockImport { +impl SecondaryChainBlockImport +where + Number: Clone + Send + Sync + Debug + 'static, +{ /// Create a new instance. - fn new(inner: I) -> Self { - Self(inner) + fn new( + inner: I, + imported_block_notification_sender: SubspaceNotificationSender, + ) -> Self { + Self { + inner, + imported_block_notification_sender, + } } } #[async_trait::async_trait] -impl BlockImport for SecondaryChainBlockImport +impl BlockImport for SecondaryChainBlockImport> where Block: BlockT, I: BlockImport + Send, @@ -54,7 +71,7 @@ where &mut self, block: BlockCheckParams, ) -> Result { - self.0.check_block(block).await + self.inner.check_block(block).await } async fn import_block( @@ -64,10 +81,14 @@ where ) -> Result { // Best block is determined by the primary chain, or if we are doing the initial sync // we import all blocks as new best. + let number = *block_import_params.header.number(); block_import_params.fork_choice = Some(ForkChoiceStrategy::Custom( block_import_params.origin == BlockOrigin::NetworkInitialSync, )); - self.0.import_block(block_import_params, cache).await + let import_result = self.inner.import_block(block_import_params, cache).await?; + self.imported_block_notification_sender + .notify(move || number); + Ok(import_result) } } @@ -107,20 +128,34 @@ where } /// Start an import queue for a Cumulus collator that does not uses any special authoring logic. +#[allow(clippy::type_complexity)] pub fn import_queue( block_import: I, spawner: &impl SpawnEssentialNamed, registry: Option<&Registry>, -) -> ClientResult> +) -> ClientResult<( + BasicQueue, + SubspaceNotificationStream>, +)> where I: BlockImport + Send + Sync + 'static, I::Transaction: Send, { - Ok(BasicQueue::new( - Verifier::default(), - Box::new(SecondaryChainBlockImport::new(block_import)), - None, - spawner, - registry, + let (imported_block_notification_sender, imported_block_notification_receiver) = + sc_consensus_subspace::notification::channel( + "system_domain_imported_block_notification_stream", + ); + Ok(( + BasicQueue::new( + Verifier::default(), + Box::new(SecondaryChainBlockImport::>::new( + block_import, + imported_block_notification_sender, + )), + None, + spawner, + registry, + ), + imported_block_notification_receiver, )) } diff --git a/domains/client/domain-executor/Cargo.toml b/domains/client/domain-executor/Cargo.toml index 418e90fb59..7fc9137da6 100644 --- a/domains/client/domain-executor/Cargo.toml +++ b/domains/client/domain-executor/Cargo.toml @@ -41,6 +41,8 @@ domain-block-builder = { path = "../block-builder" } domain-client-executor-gossip = { path = "../executor-gossip" } domain-runtime-primitives = { path = "../../primitives/runtime" } sp-domains = { path = "../../../crates/sp-domains" } +sp-domain-digests = { path = "../../primitives/digests" } +sp-domain-tracker = { path = "../../primitives/domain-tracker" } subspace-fraud-proof = { path = "../../../crates/subspace-fraud-proof" } subspace-core-primitives = { path = "../../../crates/subspace-core-primitives" } subspace-runtime-primitives = { path = "../../../crates/subspace-runtime-primitives" } diff --git a/domains/client/domain-executor/src/core_bundle_processor.rs b/domains/client/domain-executor/src/core_bundle_processor.rs index ad84936242..db6aca72a2 100644 --- a/domains/client/domain-executor/src/core_bundle_processor.rs +++ b/domains/client/domain-executor/src/core_bundle_processor.rs @@ -15,10 +15,13 @@ use sp_api::{NumberFor, ProvideRuntimeApi}; use sp_blockchain::HeaderBackend; use sp_consensus::{BlockOrigin, SyncOracle}; use sp_core::traits::{CodeExecutor, SpawnNamed}; +use sp_domain_digests::AsPredigest; +use sp_domain_tracker::StateRootUpdate; use sp_domains::{DomainId, ExecutionReceipt, ExecutorApi, OpaqueBundle}; use sp_keystore::SyncCryptoStorePtr; use sp_runtime::generic::BlockId; use sp_runtime::traits::{Block as BlockT, HashFor, Header as HeaderT, One}; +use sp_runtime::Digest; use std::borrow::Cow; use std::collections::{BTreeMap, VecDeque}; use std::fmt::Debug; @@ -326,12 +329,27 @@ where extrinsics.push(set_code_extrinsic); } + // include the latest state root of the system domain + let system_domain_hash = self.system_domain_client.info().best_hash; + let digests = self + .system_domain_client + .header(BlockId::Hash(system_domain_hash))? + .map(|header| { + let item = AsPredigest::system_domain_state_root_update(StateRootUpdate { + number: *header.number(), + state_root: *header.state_root(), + }); + + Digest { logs: vec![item] } + }) + .unwrap_or_default(); + let block_builder = BlockBuilder::new( &*self.client, parent_hash, parent_number, RecordProof::No, - Default::default(), + digests, &*self.backend, extrinsics, )?; diff --git a/domains/client/domain-executor/src/system_bundle_processor.rs b/domains/client/domain-executor/src/system_bundle_processor.rs index b715d1052d..3f979a7c8e 100644 --- a/domains/client/domain-executor/src/system_bundle_processor.rs +++ b/domains/client/domain-executor/src/system_bundle_processor.rs @@ -15,10 +15,13 @@ use sp_api::{NumberFor, ProvideRuntimeApi}; use sp_blockchain::HeaderBackend; use sp_consensus::{BlockOrigin, SyncOracle}; use sp_core::traits::{CodeExecutor, SpawnNamed}; +use sp_domain_digests::AsPredigest; +use sp_domain_tracker::StateRootUpdate; use sp_domains::{ExecutionReceipt, ExecutorApi, OpaqueBundle, SignedOpaqueBundle}; use sp_keystore::SyncCryptoStorePtr; use sp_runtime::generic::BlockId; use sp_runtime::traits::{Block as BlockT, HashFor, Header as HeaderT, One}; +use sp_runtime::Digest; use std::borrow::Cow; use std::collections::{BTreeMap, VecDeque}; use std::fmt::Debug; @@ -312,12 +315,25 @@ where extrinsics.push(set_code_extrinsic); } + let digests = self + .client + .header(BlockId::Hash(parent_hash))? + .map(|header| { + let item = AsPredigest::system_domain_state_root_update(StateRootUpdate { + number: parent_number, + state_root: *header.state_root(), + }); + + Digest { logs: vec![item] } + }) + .unwrap_or_default(); + let block_builder = BlockBuilder::new( &*self.client, parent_hash, parent_number, RecordProof::No, - Default::default(), + digests, &*self.backend, extrinsics, )?; diff --git a/domains/client/relayer/src/lib.rs b/domains/client/relayer/src/lib.rs index b38901d83a..2a67d24c83 100644 --- a/domains/client/relayer/src/lib.rs +++ b/domains/client/relayer/src/lib.rs @@ -2,12 +2,12 @@ #![allow(dead_code)] #![warn(rust_2018_idioms)] -mod worker; +pub mod worker; use domain_runtime_primitives::RelayerId; use parity_scale_codec::{Decode, Encode}; -use sc_client_api::{AuxStore, HeaderBackend, ProofProvider, StorageKey, StorageProof}; -use sp_api::{ProvideRuntimeApi, StateBackend}; +use sc_client_api::{AuxStore, HeaderBackend, ProofProvider, StorageProof}; +use sp_api::ProvideRuntimeApi; use sp_domain_tracker::DomainTrackerApi; use sp_domains::DomainId; use sp_messenger::messages::{ @@ -72,11 +72,7 @@ impl From for Error { impl Relayer where Block: BlockT, - Client: HeaderBackend - + AuxStore - + StateBackend<::Hashing> - + ProofProvider - + ProvideRuntimeApi, + Client: HeaderBackend + AuxStore + ProofProvider + ProvideRuntimeApi, Client::Api: RelayerApi>, { pub(crate) fn domain_id(client: &Arc) -> Result { @@ -99,14 +95,14 @@ where fn construct_system_domain_storage_proof_for_key_at( system_domain_client: &Arc, block_hash: Block::Hash, - key: &StorageKey, + key: &[u8], ) -> Result, Block::Hash>, Error> { system_domain_client .header(BlockId::Hash(block_hash))? .map(|header| *header.state_root()) .and_then(|state_root| { let proof = system_domain_client - .read_proof(block_hash, &mut [key.as_ref()].into_iter()) + .read_proof(block_hash, &mut [key].into_iter()) .ok()?; Some(Proof { state_root, @@ -121,7 +117,7 @@ where fn construct_core_domain_storage_proof_for_key_at( core_domain_client: &Arc, block_hash: Block::Hash, - key: &StorageKey, + key: &[u8], core_domain_proof: StorageProof, ) -> Result, Block::Hash>, Error> { core_domain_client @@ -129,7 +125,7 @@ where .map(|header| (*header.number(), *header.state_root())) .and_then(|(number, state_root)| { let proof = core_domain_client - .read_proof(block_hash, &mut [key.as_ref()].into_iter()) + .read_proof(block_hash, &mut [key].into_iter()) .ok()?; Some(Proof { state_root, @@ -142,7 +138,7 @@ where fn construct_cross_domain_message_and_submit< Submitter: Fn(CrossDomainMessage>) -> Result<(), sp_api::ApiError>, - ProofConstructor: Fn(Block::Hash, &StorageKey) -> Result, Block::Hash>, Error>, + ProofConstructor: Fn(Block::Hash, &[u8]) -> Result, Block::Hash>, Error>, >( block_hash: Block::Hash, msgs: Vec, diff --git a/domains/client/relayer/src/worker.rs b/domains/client/relayer/src/worker.rs index 7a09f98c51..b6b0c60401 100644 --- a/domains/client/relayer/src/worker.rs +++ b/domains/client/relayer/src/worker.rs @@ -1,4 +1,4 @@ -use crate::{BlockT, Error, HeaderBackend, HeaderT, Relayer, StateBackend, LOG_TARGET}; +use crate::{BlockT, Error, HeaderBackend, HeaderT, Relayer, LOG_TARGET}; use domain_runtime_primitives::RelayerId; use futures::{Stream, StreamExt}; use sc_client_api::{AuxStore, ProofProvider}; @@ -18,26 +18,29 @@ pub async fn relay_system_domain_messages( system_domain_client: Arc, system_domain_block_import: DBI, system_domain_sync_oracle: SO, -) -> Result<(), Error> -where +) where Block: BlockT, - Client: HeaderBackend - + AuxStore - + StateBackend<::Hashing> - + ProofProvider - + ProvideRuntimeApi, + Client: HeaderBackend + AuxStore + ProofProvider + ProvideRuntimeApi, Client::Api: RelayerApi>, DBI: Stream> + Unpin, SO: SyncOracle, { - relay_domain_messages( + let result = relay_domain_messages( relayer_id, system_domain_client, system_domain_block_import, Relayer::submit_messages_from_system_domain, system_domain_sync_oracle, ) - .await + .await; + + if let Err(err) = result { + tracing::error!( + target: LOG_TARGET, + ?err, + "Failed to start relayer for system domain" + ) + } } /// Starts relaying core domain messages to other domains. @@ -50,14 +53,9 @@ pub async fn relay_core_domain_messages( core_domain_block_import: DBI, system_domain_sync_oracle: SO, core_domain_sync_oracle: SO, -) -> Result<(), Error> -where +) where Block: BlockT, - CDC: HeaderBackend - + AuxStore - + StateBackend<::Hashing> - + ProofProvider - + ProvideRuntimeApi, + CDC: HeaderBackend + AuxStore + ProofProvider + ProvideRuntimeApi, CDC::Api: RelayerApi>, DBI: Stream> + Unpin, SDC: HeaderBackend + ProvideRuntimeApi + ProofProvider, @@ -67,7 +65,7 @@ where let combined_sync_oracle = CombinedSyncOracle::new(&system_domain_sync_oracle, &core_domain_sync_oracle); - relay_domain_messages( + let result = relay_domain_messages( relayer_id, core_domain_client, core_domain_block_import, @@ -81,7 +79,14 @@ where }, combined_sync_oracle, ) - .await + .await; + if let Err(err) = result { + tracing::error!( + target: LOG_TARGET, + ?err, + "Failed to start relayer for core domain" + ) + } } async fn relay_domain_messages( @@ -93,11 +98,7 @@ async fn relay_domain_messages( ) -> Result<(), Error> where Block: BlockT, - Client: HeaderBackend - + AuxStore - + StateBackend<::Hashing> - + ProofProvider - + ProvideRuntimeApi, + Client: HeaderBackend + AuxStore + ProofProvider + ProvideRuntimeApi, Client::Api: RelayerApi>, SDBI: Stream> + Unpin, MP: Fn(RelayerId, &Arc, Block::Hash) -> Result<(), Error>, diff --git a/domains/pallets/domain-registry/Cargo.toml b/domains/pallets/domain-registry/Cargo.toml index ba725fdc8b..4e1547731b 100644 --- a/domains/pallets/domain-registry/Cargo.toml +++ b/domains/pallets/domain-registry/Cargo.toml @@ -20,6 +20,7 @@ scale-info = { version = "2.1.2", default-features = false, features = ["derive" serde = { version = "1.0.143", optional = true } sp-core = { git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f", default-features = false } sp-domains = { version = "0.1.0", path = "../../../crates/sp-domains", default-features = false } +sp-domain-tracker = { version = "0.1.0", path = "../../primitives/domain-tracker", default-features = false } sp-executor-registry = { version = "0.1.0", path = "../../primitives/executor-registry", default-features = false } sp-runtime = { git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f", default-features = false } sp-std = { git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f", default-features = false } @@ -27,6 +28,7 @@ sp-trie = { git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c [dev-dependencies] pallet-balances = { git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } +pallet-domain-tracker = { path = "../../pallets/domain-tracker" } pallet-executor-registry = { path = "../executor-registry" } sp-core = { git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } sp-io = { git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } @@ -42,6 +44,7 @@ std = [ "serde/std", "sp-core/std", "sp-domains/std", + "sp-domain-tracker/std", "sp-executor-registry/std", "sp-runtime/std", "sp-std/std", diff --git a/domains/pallets/domain-registry/src/lib.rs b/domains/pallets/domain-registry/src/lib.rs index 3448a4ea7e..7375bb2f0e 100644 --- a/domains/pallets/domain-registry/src/lib.rs +++ b/domains/pallets/domain-registry/src/lib.rs @@ -24,6 +24,7 @@ use codec::{Decode, Encode}; use frame_support::traits::{Currency, Get, LockIdentifier, LockableCurrency, WithdrawReasons}; use frame_support::weights::Weight; pub use pallet::*; +use sp_domain_tracker::CoreDomainTracker; use sp_domains::bundle_election::{ verify_bundle_solution_threshold, ReadBundleElectionParamsError, }; @@ -32,7 +33,7 @@ use sp_domains::{ InvalidTransactionProof, ProofOfElection, SignedOpaqueBundle, StakeWeight, }; use sp_executor_registry::{ExecutorRegistry, OnNewEpoch}; -use sp_runtime::traits::{BlakeTwo256, One, Saturating, Zero}; +use sp_runtime::traits::{BlakeTwo256, Hash, One, Saturating, Zero}; use sp_runtime::Percent; use sp_std::collections::btree_map::BTreeMap; use sp_std::vec; @@ -44,16 +45,20 @@ type BalanceOf = type DomainConfig = sp_domains::DomainConfig<::Hash, BalanceOf, Weight>; +type StateRootOf = <::Hashing as Hash>::Output; + const DOMAIN_LOCK_ID: LockIdentifier = *b"_domains"; #[frame_support::pallet] mod pallet { use super::{BalanceOf, DomainConfig}; + use crate::StateRootOf; use codec::Codec; use frame_support::pallet_prelude::{StorageMap, StorageNMap, *}; use frame_support::traits::LockableCurrency; use frame_system::pallet_prelude::*; use sp_core::H256; + use sp_domain_tracker::CoreDomainTracker; use sp_domains::bundle_election::ReadBundleElectionParamsError; use sp_domains::{ BundleEquivocationProof, DomainId, ExecutionReceipt, ExecutorPublicKey, FraudProof, @@ -115,6 +120,9 @@ mod pallet { /// Number of execution receipts kept in the state. #[pallet::constant] type ReceiptsPruningDepth: Get; + + /// Core domain tracker that tracks the state roots of the core domains. + type CoreDomainTracker: CoreDomainTracker>; } #[pallet::pallet] @@ -1001,6 +1009,8 @@ impl Pallet { (domain_id, primary_number, execution_receipt.secondary_hash), state_root, ); + + T::CoreDomainTracker::add_core_domain_state_root(domain_id, primary_number, *state_root) } /* TODO: diff --git a/domains/pallets/domain-registry/src/tests.rs b/domains/pallets/domain-registry/src/tests.rs index e479c1465d..71c7117663 100644 --- a/domains/pallets/domain-registry/src/tests.rs +++ b/domains/pallets/domain-registry/src/tests.rs @@ -28,6 +28,7 @@ frame_support::construct_runtime!( System: frame_system, Balances: pallet_balances, ExecutorRegistry: pallet_executor_registry, + DomainTracker: pallet_domain_tracker, DomainRegistry: pallet_domain_registry, } ); @@ -90,6 +91,17 @@ parameter_types! { pub const WithdrawalDuration: BlockNumber = 10; } +parameter_types! { + pub const StateRootsBound: u32 = 50; + pub const RelayConfirmationDepth: BlockNumber = 7; +} + +impl pallet_domain_tracker::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ConfirmedStateRootsBound = StateRootsBound; + type RelayerConfirmationDepth = RelayConfirmationDepth; +} + impl pallet_executor_registry::Config for Test { type RuntimeEvent = RuntimeEvent; type Currency = Balances; @@ -122,6 +134,7 @@ impl pallet_domain_registry::Config for Test { type MinDomainOperatorStake = MinDomainOperatorStake; type MaximumReceiptDrift = MaximumReceiptDrift; type ReceiptsPruningDepth = ReceiptsPruningDepth; + type CoreDomainTracker = DomainTracker; } fn new_test_ext() -> sp_io::TestExternalities { diff --git a/domains/pallets/domain-tracker/Cargo.toml b/domains/pallets/domain-tracker/Cargo.toml index 934e1cebc2..d27fcd5c77 100644 --- a/domains/pallets/domain-tracker/Cargo.toml +++ b/domains/pallets/domain-tracker/Cargo.toml @@ -20,6 +20,7 @@ frame-system = { version = "4.0.0-dev", default-features = false, git = "https:/ scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } sp-core = { version = "6.0.0", default-features = false, git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } sp-domains = { version = "0.1.0", default-features = false, path = "../../../crates/sp-domains" } +sp-domain-digests = { version = "0.1.0", default-features = false, path = "../../primitives/digests" } sp-domain-tracker = { version = "0.1.0", default-features = false, path = "../../primitives/domain-tracker" } sp-messenger = { version = "0.1.0", default-features = false, path = "../../primitives/messenger" } sp-runtime = { version = "6.0.0", default-features = false, git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } @@ -39,6 +40,7 @@ std = [ "scale-info/std", "sp-core/std", "sp-domains/std", + "sp-domain-digests/std", "sp-domain-tracker/std", "sp-messenger/std", "sp-runtime/std", diff --git a/domains/pallets/domain-tracker/src/lib.rs b/domains/pallets/domain-tracker/src/lib.rs index 5c27ab34c9..44050b83ef 100644 --- a/domains/pallets/domain-tracker/src/lib.rs +++ b/domains/pallets/domain-tracker/src/lib.rs @@ -33,13 +33,13 @@ pub(crate) type StateRootOf = <::Hashing as Hash>: mod pallet { use crate::StateRootOf; use frame_support::pallet_prelude::*; - use frame_system::ensure_none; - use frame_system::pallet_prelude::{BlockNumberFor, OriginFor}; + use frame_system::pallet_prelude::BlockNumberFor; use sp_core::storage::StorageKey; - use sp_domain_tracker::{InherentType, NoFatalError, INHERENT_IDENTIFIER}; + use sp_domain_digests::AsPredigest; + use sp_domain_tracker::CoreDomainTracker; use sp_domains::DomainId; use sp_messenger::DomainTracker; - use sp_runtime::traits::{CheckedSub, One}; + use sp_runtime::traits::{CheckedAdd, CheckedSub, One}; use sp_std::vec::Vec; #[pallet::config] @@ -48,7 +48,10 @@ mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Total number of confirmed state roots to store at a time. - type StateRootsBound: Get; + type ConfirmedStateRootsBound: Get; + + /// K depth confirmation for relayers to relay messages. + type RelayerConfirmationDepth: Get; } #[pallet::pallet] @@ -57,17 +60,23 @@ mod pallet { pub struct Pallet(_); /// All confirmed domain state roots bounded to the StateRootBound value. + /// Max number of state roots per domain is bound to StateRootBound. #[pallet::storage] - #[pallet::getter(fn system_domain_state_roots)] - pub(super) type SystemDomainStateRoots = - StorageValue<_, Vec>, ValueQuery>; + #[pallet::getter(fn confirmed_domain_state_roots)] + pub(super) type ConfirmedDomainStateRoots = StorageDoubleMap< + _, + Identity, + DomainId, + Identity, + T::BlockNumber, + StateRootOf, + OptionQuery, + >; - /// Latest Confirmed Core domain state roots bounded to the StateRootBound value. - /// This is essentially used by relayer and updated by the system domain runtime when there is - /// a new state root confirmed for a given core domain. + /// All unconfirmed domain state roots. #[pallet::storage] - #[pallet::getter(fn core_domains_state_root)] - pub(super) type CoreDomainsStateRoot = StorageDoubleMap< + #[pallet::getter(fn unconfirmed_domain_state_roots)] + pub(super) type UnconfirmedDomainStateRoots = StorageDoubleMap< _, Identity, DomainId, @@ -77,129 +86,113 @@ mod pallet { OptionQuery, >; - /// Flag to allow only one update per block through inherent. + /// Latest block number of the domain. #[pallet::storage] - #[pallet::getter(fn state_roots_updated)] - pub(super) type StateRootsUpdated = StorageValue<_, bool, ValueQuery>; + #[pallet::getter(fn latest_domain_block_number)] + pub(super) type LatestDomainBlockNumber = + StorageMap<_, Identity, DomainId, T::BlockNumber, OptionQuery>; /// Events emitted by pallet-domain-tracker. #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// Emits when state roots are updated. - StateRootsUpdated, - } - - /// Errors emitted by pallet-domain-tracker. - #[pallet::error] - pub enum Error { - /// Emits on second call to set state roots of the domain. - StateRootsAlreadyUpdated, - } - - #[pallet::call] - impl Pallet { - /// Updates the state root of the system domain. - /// Also ensures the state root count is bounded to the max limit for each domain. - #[pallet::weight((10_000, Pays::No))] - pub fn update_system_domain_state_root( - origin: OriginFor, - state_root: StateRootOf, - ) -> DispatchResult { - ensure_none(origin)?; - ensure!( - !StateRootsUpdated::::get(), - Error::::StateRootsAlreadyUpdated - ); - - Self::do_update_system_domain_state_root(state_root); - StateRootsUpdated::::set(true); - Self::deposit_event(Event::::StateRootsUpdated); - Ok(()) - } - } - - #[pallet::inherent] - impl ProvideInherent for Pallet { - type Call = Call; - type Error = NoFatalError<()>; - const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER; - - fn create_inherent(data: &InherentData) -> Option { - let inherent_data = data - .get_data::>>(&INHERENT_IDENTIFIER) - .expect("Domain tracker inherent data is not correctly encoded") - .expect("Domain tracker inherent data must be provided."); - - Some(Call::update_system_domain_state_root { - state_root: inherent_data.system_domain_state_root, - }) - } - - fn is_inherent(call: &Self::Call) -> bool { - matches!(call, Call::update_system_domain_state_root { .. }) - } - } + pub enum Event {} #[pallet::hooks] impl Hooks> for Pallet { - fn on_finalize(_n: BlockNumberFor) { - assert!( - StateRootsUpdated::::take(), - "StateRoots must be updated once a block." - ); + fn on_initialize(_n: BlockNumberFor) -> Weight { + if let Some(state_root_update) = >::digest() + .logs + .iter() + .find_map(|s| { + s.as_system_domain_state_root_update::>() + }) + { + Self::add_system_domain_state_root( + state_root_update.number, + state_root_update.state_root, + ); + } + + Weight::zero() } } impl DomainTracker> for Pallet { fn system_domain_state_roots() -> Vec> { - SystemDomainStateRoots::::get() + let confirmed_state_roots: Vec> = + ConfirmedDomainStateRoots::::iter_prefix_values(DomainId::SYSTEM).collect(); + confirmed_state_roots } fn storage_key_for_core_domain_state_root( domain_id: DomainId, block_number: T::BlockNumber, ) -> StorageKey { - StorageKey(CoreDomainsStateRoot::::hashed_key_for( + StorageKey(ConfirmedDomainStateRoots::::hashed_key_for( domain_id, block_number, )) } } + impl CoreDomainTracker> for Pallet { + fn add_core_domain_state_root( + domain_id: DomainId, + block_number: T::BlockNumber, + state_root: StateRootOf, + ) { + let _ = Self::add_and_confirm_domain_state_root(domain_id, block_number, state_root); + } + } + impl Pallet { - pub fn do_update_system_domain_state_root(state_root: StateRootOf) { - SystemDomainStateRoots::::mutate(|state_roots| { - state_roots.push(state_root); - if state_roots.len() > T::StateRootsBound::get() as usize { - let first_idx = state_roots.len() - T::StateRootsBound::get() as usize; - *state_roots = state_roots.split_off(first_idx); - } - }); + /// Adds new state root at a given block. + /// Also, confirms block state root at confirmation depth. + /// Also, prunes confirmed state roots beyond StateRootBound. + pub fn add_system_domain_state_root( + block_number: T::BlockNumber, + state_root: StateRootOf, + ) { + let _ = + Self::add_and_confirm_domain_state_root(DomainId::SYSTEM, block_number, state_root); } - /// Adds a new state root for the core domain mapped to domain_id. - /// This is only called on system domain runtime by the domain registry. - /// TODO(ved): ensure this is called when the core domain state roots are available. - pub fn add_confirmed_core_domain_state_root( + fn add_and_confirm_domain_state_root( domain_id: DomainId, block_number: T::BlockNumber, state_root: StateRootOf, - ) { - CoreDomainsStateRoot::::insert(domain_id, block_number, state_root); - // ensure to bound the total state roots - match block_number.checked_sub(&T::StateRootsBound::get().into()) { - // nothing to clean up yet - None => (), - Some(mut from) => { - while CoreDomainsStateRoot::::take(domain_id, from).is_some() { - from = match from.checked_sub(&One::one()) { - None => return, - Some(from) => from, - } + ) -> Option<()> { + if let Some(latest_block_number) = LatestDomainBlockNumber::::get(domain_id) { + if block_number <= latest_block_number { + // remove all the blocks that are pruned due to fork + let mut prune_block_above = block_number; + while UnconfirmedDomainStateRoots::::take(domain_id, prune_block_above) + .is_some() + { + prune_block_above = prune_block_above.checked_add(&One::one())?; } } } + + UnconfirmedDomainStateRoots::::insert(domain_id, block_number, state_root); + LatestDomainBlockNumber::::insert(domain_id, block_number); + // confirm state root at relayer confirmation depth + let confirmed_block = block_number.checked_sub(&T::RelayerConfirmationDepth::get())?; + let confirmed_state_root = + UnconfirmedDomainStateRoots::::take(domain_id, confirmed_block)?; + ConfirmedDomainStateRoots::::insert( + domain_id, + confirmed_block, + confirmed_state_root, + ); + + // prune confirmed state roots that are below StateRootBound + let mut prune_from_and_below = confirmed_block + .checked_sub(&T::BlockNumber::from(T::ConfirmedStateRootsBound::get()))?; + while ConfirmedDomainStateRoots::::take(domain_id, prune_from_and_below).is_some() { + prune_from_and_below = prune_from_and_below.checked_sub(&One::one())? + } + + Some(()) } /// Returns storage key to generate storage proof for the relayer. @@ -207,17 +200,17 @@ mod pallet { pub fn storage_key_for_core_domain_state_root( domain_id: DomainId, block_number: T::BlockNumber, - ) -> Option { + ) -> Option> { if !domain_id.is_core() - || !CoreDomainsStateRoot::::contains_key(domain_id, block_number) + || !ConfirmedDomainStateRoots::::contains_key(domain_id, block_number) { return None; }; - Some(StorageKey(CoreDomainsStateRoot::::hashed_key_for( + Some(ConfirmedDomainStateRoots::::hashed_key_for( domain_id, block_number, - ))) + )) } } } diff --git a/domains/pallets/domain-tracker/src/mock.rs b/domains/pallets/domain-tracker/src/mock.rs index a143afb5bf..da00665ddf 100644 --- a/domains/pallets/domain-tracker/src/mock.rs +++ b/domains/pallets/domain-tracker/src/mock.rs @@ -9,13 +9,14 @@ type Block = frame_system::mocking::MockBlock; pub(crate) type AccountId = u64; frame_support::construct_runtime!( - pub struct MockRuntime where + pub struct MockRuntime + where Block = Block, NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - DomainTracker: crate::{Pallet, Call, Storage, Event}, + System: frame_system, + DomainTracker: crate, } ); @@ -47,12 +48,14 @@ impl frame_system::Config for MockRuntime { } parameter_types! { - pub const StateRootsBound: u32 = 2; + pub const StateRootsBound: u32 = 2; + pub const RelayConfirmationDepth: u64 = 2; } impl crate::Config for MockRuntime { type RuntimeEvent = RuntimeEvent; - type StateRootsBound = StateRootsBound; + type ConfirmedStateRootsBound = StateRootsBound; + type RelayerConfirmationDepth = RelayConfirmationDepth; } pub fn new_test_ext() -> sp_io::TestExternalities { diff --git a/domains/pallets/domain-tracker/src/tests.rs b/domains/pallets/domain-tracker/src/tests.rs index 3d507f7569..413baabfa4 100644 --- a/domains/pallets/domain-tracker/src/tests.rs +++ b/domains/pallets/domain-tracker/src/tests.rs @@ -1,82 +1,98 @@ -use crate::mock::{new_test_ext, DomainTracker, MockRuntime, RuntimeOrigin, StateRootsBound}; -use crate::pallet::{CoreDomainsStateRoot, SystemDomainStateRoots}; -use crate::Error; -use frame_support::{assert_err, assert_ok}; -use sp_domain_tracker::InherentType; +use crate::mock::{new_test_ext, DomainTracker}; use sp_domains::DomainId; use sp_runtime::traits::{BlakeTwo256, Hash}; #[test] fn test_update_state_root() { new_test_ext().execute_with(|| { - let data = InherentType { - system_domain_state_root: BlakeTwo256::hash_of(&1), - }; + let state_root = BlakeTwo256::hash_of(&1); + assert!(DomainTracker::confirmed_domain_state_roots(DomainId::SYSTEM, 1).is_none()); + assert!(DomainTracker::unconfirmed_domain_state_roots(DomainId::SYSTEM, 1).is_none()); - assert!(DomainTracker::system_domain_state_roots().is_empty()); - assert!(!DomainTracker::state_roots_updated()); - - let res = DomainTracker::update_system_domain_state_root( - RuntimeOrigin::none(), - data.system_domain_state_root, - ); - assert_ok!(res); - assert_eq!( - DomainTracker::system_domain_state_roots(), - vec![BlakeTwo256::hash_of(&1)] - ); - assert!(DomainTracker::state_roots_updated()); - - // cannot update twice in same block - let res = DomainTracker::update_system_domain_state_root( - RuntimeOrigin::none(), - data.system_domain_state_root, + DomainTracker::add_system_domain_state_root(1, state_root); + assert!(DomainTracker::confirmed_domain_state_roots(DomainId::SYSTEM, 1).is_none()); + assert!( + DomainTracker::unconfirmed_domain_state_roots(DomainId::SYSTEM, 1) + == Some(BlakeTwo256::hash_of(&1)) ); - assert_err!(res, Error::::StateRootsAlreadyUpdated) }) } #[test] fn test_state_roots_bounded() { new_test_ext().execute_with(|| { - SystemDomainStateRoots::::set(vec![ - BlakeTwo256::hash_of(&1), - BlakeTwo256::hash_of(&2), - ]); + for number in 0..=5u64 { + DomainTracker::add_system_domain_state_root(number, BlakeTwo256::hash_of(&number)); + } - let data = InherentType { - system_domain_state_root: BlakeTwo256::hash_of(&3), - }; + // 0, 1 should be pruned + for number in 0..=1 { + assert!( + DomainTracker::confirmed_domain_state_roots(DomainId::SYSTEM, number).is_none() + ); + assert!( + DomainTracker::unconfirmed_domain_state_roots(DomainId::SYSTEM, number).is_none() + ); + } - assert!( - DomainTracker::system_domain_state_roots().len() == StateRootsBound::get() as usize - ); - assert!(!DomainTracker::state_roots_updated()); + // 2, 3 should confirmed and not in unconfirmed + for number in 2..=3 { + assert!( + DomainTracker::confirmed_domain_state_roots(DomainId::SYSTEM, number).is_some() + ); + assert!( + DomainTracker::unconfirmed_domain_state_roots(DomainId::SYSTEM, number).is_none() + ); + } - let res = DomainTracker::update_system_domain_state_root( - RuntimeOrigin::none(), - data.system_domain_state_root, - ); - assert_ok!(res); - assert_eq!( - DomainTracker::system_domain_state_roots(), - vec![BlakeTwo256::hash_of(&2), BlakeTwo256::hash_of(&3)] - ); - assert!(DomainTracker::state_roots_updated()); + // 4, 5 should unconfirmed and not in confirmed + for number in 4..=5 { + assert!( + DomainTracker::confirmed_domain_state_roots(DomainId::SYSTEM, number).is_none() + ); + assert!( + DomainTracker::unconfirmed_domain_state_roots(DomainId::SYSTEM, number).is_some() + ); + } }) } #[test] -fn test_core_domain_state_roots_bounded() { +fn test_state_roots_re_org() { new_test_ext().execute_with(|| { - let domain_id = DomainId::new(11); - CoreDomainsStateRoot::::insert(domain_id, 1, BlakeTwo256::hash_of(&1)); - CoreDomainsStateRoot::::insert(domain_id, 2, BlakeTwo256::hash_of(&2)); + for number in 0..=5u64 { + DomainTracker::add_system_domain_state_root(number, BlakeTwo256::hash_of(&number)); + } + + // let new latest be 4 + DomainTracker::add_system_domain_state_root(4, BlakeTwo256::hash_of(&4)); + + // 0, 1 should be pruned + for number in 0..=1 { + assert!( + DomainTracker::confirmed_domain_state_roots(DomainId::SYSTEM, number).is_none() + ); + assert!( + DomainTracker::unconfirmed_domain_state_roots(DomainId::SYSTEM, number).is_none() + ); + } + + // 2, 3 should confirmed and not in unconfirmed + for number in 2..=3 { + assert!( + DomainTracker::confirmed_domain_state_roots(DomainId::SYSTEM, number).is_some() + ); + assert!( + DomainTracker::unconfirmed_domain_state_roots(DomainId::SYSTEM, number).is_none() + ); + } + + // 4 should unconfirmed and not in confirmed + assert!(DomainTracker::confirmed_domain_state_roots(DomainId::SYSTEM, 4).is_none()); + assert!(DomainTracker::unconfirmed_domain_state_roots(DomainId::SYSTEM, 4).is_some()); - assert!(DomainTracker::core_domains_state_root(domain_id, 1).is_some()); - DomainTracker::add_confirmed_core_domain_state_root(domain_id, 3, BlakeTwo256::hash_of(&3)); - assert!(DomainTracker::core_domains_state_root(domain_id, 1).is_none()); - assert!(DomainTracker::storage_key_for_core_domain_state_root(domain_id, 3).is_some()); - assert!(DomainTracker::storage_key_for_core_domain_state_root(domain_id, 1).is_none()); + // 5 should be pruned as well + assert!(DomainTracker::confirmed_domain_state_roots(DomainId::SYSTEM, 5).is_none()); + assert!(DomainTracker::unconfirmed_domain_state_roots(DomainId::SYSTEM, 5).is_none()); }) } diff --git a/domains/pallets/messenger/src/lib.rs b/domains/pallets/messenger/src/lib.rs index b1c9ffd43a..da435f6d8e 100644 --- a/domains/pallets/messenger/src/lib.rs +++ b/domains/pallets/messenger/src/lib.rs @@ -35,7 +35,8 @@ use frame_system::offchain::SubmitTransaction; pub use pallet::*; use scale_info::TypeInfo; use sp_core::U256; -use sp_messenger::messages::{ChannelId, CrossDomainMessage, FeeModel, Message, Nonce}; +use sp_domains::DomainId; +use sp_messenger::messages::{ChannelId, CrossDomainMessage, FeeModel, Message, MessageId, Nonce}; use sp_runtime::traits::Hash; use sp_runtime::DispatchError; @@ -107,6 +108,7 @@ mod pallet { }; use sp_messenger::DomainTracker as DomainTrackerT; use sp_runtime::ArithmeticError; + use sp_std::boxed::Box; #[pallet::config] pub trait Config: frame_system::Config { @@ -856,4 +858,14 @@ where } } } + + /// Returns true if the outbox message has not received the response yet. + pub fn should_relay_outbox_message(dst_domain: DomainId, msg_id: MessageId) -> bool { + Outbox::::contains_key((dst_domain, msg_id.0, msg_id.1)) + } + + /// Returns true if the inbox message response has not received acknowledgement yet. + pub fn should_relay_inbox_message_response(dst_domain: DomainId, msg_id: MessageId) -> bool { + InboxResponses::::contains_key((dst_domain, msg_id.0, msg_id.1)) + } } diff --git a/domains/pallets/messenger/src/mock.rs b/domains/pallets/messenger/src/mock.rs index f35a74b15b..bd2cb2f4cd 100644 --- a/domains/pallets/messenger/src/mock.rs +++ b/domains/pallets/messenger/src/mock.rs @@ -16,7 +16,7 @@ pub type TestExternalities = sp_state_machine::TestExternalities; macro_rules! impl_runtime { ($runtime:ty, $domain_id:literal) => { - use crate::mock::{TestExternalities, MockEndpoint, MessageId, Balance, AccountId}; + use crate::mock::{TestExternalities, MockEndpoint, MessageId, Balance, AccountId, mock_domain_tracker}; use sp_domains::DomainId; use frame_support::parameter_types; use frame_support::pallet_prelude::PhantomData; @@ -39,7 +39,7 @@ macro_rules! impl_runtime { UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, - DomainTracker: pallet_domain_tracker::{Pallet, Call, Storage, Event}, + DomainTracker: mock_domain_tracker::{Pallet, Storage}, Messenger: crate::{Pallet, Call, Event}, Balances: pallet_balances::{Pallet, Call, Config, Storage, Event}, Transporter: pallet_transporter::{Pallet, Call, Storage, Event}, @@ -79,13 +79,11 @@ macro_rules! impl_runtime { } parameter_types! { - pub const StateRootsBound: u32 = 2; + pub const ConfirmedStateRootsBound: u32 = 2; + pub const RelayerConfirmationDepth: u64 = 2; } - impl pallet_domain_tracker::Config for $runtime { - type RuntimeEvent = RuntimeEvent; - type StateRootsBound = StateRootsBound; - } + impl mock_domain_tracker::Config for $runtime {} parameter_types! { pub const SelfDomainId: DomainId = DomainId::new($domain_id); @@ -199,6 +197,46 @@ impl EndpointHandler for MockEndpoint { } } +#[frame_support::pallet] +pub(crate) mod mock_domain_tracker { + use crate::mock::DomainId; + use frame_support::pallet_prelude::*; + use sp_core::storage::StorageKey; + use sp_core::H256; + use sp_messenger::DomainTracker; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + /// Pallet messenger used to communicate between domains and other blockchains. + #[pallet::pallet] + #[pallet::generate_store(pub (super) trait Store)] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::storage] + pub(super) type StateRoot = StorageMap<_, Identity, u64, H256, ValueQuery>; + + impl DomainTracker for Pallet { + fn system_domain_state_roots() -> Vec { + vec![StateRoot::::get(0)] + } + + fn storage_key_for_core_domain_state_root( + _domain_id: DomainId, + _block_number: u64, + ) -> StorageKey { + StorageKey(StateRoot::::hashed_key_for(0)) + } + } + + impl Pallet { + pub fn set_state_root(state_root: H256) { + StateRoot::::insert(0, state_root) + } + } +} + pub(crate) mod domain_a { impl_runtime!(Runtime, 1); } diff --git a/domains/pallets/messenger/src/relayer.rs b/domains/pallets/messenger/src/relayer.rs index e3e9b5b38a..21758e976c 100644 --- a/domains/pallets/messenger/src/relayer.rs +++ b/domains/pallets/messenger/src/relayer.rs @@ -5,13 +5,14 @@ use crate::{ }; use frame_support::ensure; use frame_support::traits::ReservableCurrency; -use sp_core::storage::StorageKey; use sp_domains::DomainId; use sp_messenger::messages::{ MessageId, RelayerMessageWithStorageKey, RelayerMessagesWithStorageKey, }; use sp_runtime::traits::Get; use sp_runtime::{ArithmeticError, DispatchError, DispatchResult}; +use sp_std::borrow::ToOwned; +use sp_std::vec::Vec; /// Relayer address to which rewards are paid. pub type RelayerId = ::AccountId; @@ -146,8 +147,8 @@ impl Pallet { // create storage keys for inbox responses assigned_messages.inbox_responses.into_iter().for_each( |(domain_id, (channel_id, nonce))| { - let key = InboxResponses::::hashed_key_for((domain_id, channel_id, nonce)); - let storage_key = StorageKey(key); + let storage_key = + InboxResponses::::hashed_key_for((domain_id, channel_id, nonce)); messages_with_storage_key .inbox_responses .push(RelayerMessageWithStorageKey { @@ -165,8 +166,7 @@ impl Pallet { .outbox .into_iter() .for_each(|(domain_id, (channel_id, nonce))| { - let key = Outbox::::hashed_key_for((domain_id, channel_id, nonce)); - let storage_key = StorageKey(key); + let storage_key = Outbox::::hashed_key_for((domain_id, channel_id, nonce)); messages_with_storage_key .outbox .push(RelayerMessageWithStorageKey { diff --git a/domains/pallets/messenger/src/tests.rs b/domains/pallets/messenger/src/tests.rs index 44abc510e5..53b57f0afd 100644 --- a/domains/pallets/messenger/src/tests.rs +++ b/domains/pallets/messenger/src/tests.rs @@ -78,10 +78,7 @@ fn create_channel(domain_id: DomainId, channel_id: ChannelId, fee_model: FeeMode assert_eq!(messages_with_keys.inbox_responses.len(), 0); let expected_key = Outbox::::hashed_key_for((domain_id, channel_id, Nonce::zero())); - assert_eq!( - messages_with_keys.outbox[0].storage_key, - StorageKey(expected_key) - ); + assert_eq!(messages_with_keys.outbox[0].storage_key, expected_key); } fn close_channel(domain_id: DomainId, channel_id: ChannelId, last_delivered_nonce: Option) { @@ -314,7 +311,7 @@ fn open_channel_between_domains( )); assert_eq!( messages_with_keys.inbox_responses[0].storage_key, - StorageKey(expected_key) + expected_key ); }); @@ -509,7 +506,7 @@ fn channel_relay_request_and_response( }; domain_b_test_ext.execute_with(|| { // set state root - domain_b::DomainTracker::do_update_system_domain_state_root(xdm.proof.state_root); + domain_b::DomainTracker::set_state_root(xdm.proof.state_root); // validate the message let pre_check = @@ -572,7 +569,7 @@ fn channel_relay_request_and_response( }, }; domain_a_test_ext.execute_with(|| { - domain_a::DomainTracker::do_update_system_domain_state_root(xdm.proof.state_root); + domain_a::DomainTracker::set_state_root(xdm.proof.state_root); // validate message response let pre_check = crate::Pallet::::pre_dispatch( diff --git a/domains/pallets/messenger/src/verification.rs b/domains/pallets/messenger/src/verification.rs index 5ca118ff85..6822baaf94 100644 --- a/domains/pallets/messenger/src/verification.rs +++ b/domains/pallets/messenger/src/verification.rs @@ -3,8 +3,8 @@ use frame_support::PalletError; use hash_db::Hasher; use scale_info::TypeInfo; use sp_core::storage::StorageKey; +use sp_std::marker::PhantomData; use sp_trie::{read_trie_value, LayoutV1, StorageProof}; -use std::marker::PhantomData; /// Verification error. #[derive(Debug, PartialEq, Eq, Encode, Decode, PalletError, TypeInfo)] diff --git a/domains/pallets/transporter/src/lib.rs b/domains/pallets/transporter/src/lib.rs index 907ad86973..edf8dc8102 100644 --- a/domains/pallets/transporter/src/lib.rs +++ b/domains/pallets/transporter/src/lib.rs @@ -70,6 +70,7 @@ mod pallet { Endpoint, EndpointHandler as EndpointHandlerT, EndpointId, EndpointRequest, EndpointResponse, Sender, }; + use sp_std::vec; #[pallet::config] pub trait Config: frame_system::Config { diff --git a/domains/primitives/digests/Cargo.toml b/domains/primitives/digests/Cargo.toml new file mode 100644 index 0000000000..516a8ad5ac --- /dev/null +++ b/domains/primitives/digests/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "sp-domain-digests" +version = "0.1.0" +authors = ["Vedhavyas Singareddi "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://subspace.network" +repository = "https://github.com/subspace/subspace" +description = "Primitives of domain related digests" +include = [ + "/src", + "/Cargo.toml", + "/README.md", +] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] } +sp-api = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } +sp-core = { version = "6.0.0", default-features = false, git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } +sp-domain-tracker = { path = "../domain-tracker", default-features = false} +sp-runtime = { version = "6.0.0", default-features = false, git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } + +[features] +default = ["std"] +std = [ + "codec/std", + "sp-api/std", + "sp-core/std", + "sp-domain-tracker/std", + "sp-runtime/std", +] diff --git a/domains/primitives/digests/src/lib.rs b/domains/primitives/digests/src/lib.rs new file mode 100644 index 0000000000..712a659380 --- /dev/null +++ b/domains/primitives/digests/src/lib.rs @@ -0,0 +1,34 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode}; +use sp_domain_tracker::StateRootUpdate; +use sp_runtime::{ConsensusEngineId, DigestItem}; + +const DOMAIN_ENGINE_ID: ConsensusEngineId = *b"DMN_"; + +/// Trait to provide simpler abstractions to create predigests for runtime. +pub trait AsPredigest { + /// Returns state root update digest + fn as_system_domain_state_root_update( + &self, + ) -> Option>; + + /// Creates a new digest from state root update for system domain. + fn system_domain_state_root_update( + update: StateRootUpdate, + ) -> Self; +} + +impl AsPredigest for DigestItem { + fn as_system_domain_state_root_update( + &self, + ) -> Option> { + self.pre_runtime_try_to(&DOMAIN_ENGINE_ID) + } + + fn system_domain_state_root_update( + update: StateRootUpdate, + ) -> Self { + DigestItem::PreRuntime(DOMAIN_ENGINE_ID, update.encode()) + } +} diff --git a/domains/primitives/domain-tracker/Cargo.toml b/domains/primitives/domain-tracker/Cargo.toml index 94b7699dde..a69c67bc54 100644 --- a/domains/primitives/domain-tracker/Cargo.toml +++ b/domains/primitives/domain-tracker/Cargo.toml @@ -17,8 +17,7 @@ include = [ codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } sp-core = { version = "6.0.0", default-features = false, git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } -sp-domains = { version = "0.1.0", path = "../../../crates/sp-domains" } -sp-inherents = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } +sp-domains = { version = "0.1.0", default-features = false, path = "../../../crates/sp-domains" } [features] default = ["std"] @@ -27,5 +26,4 @@ std = [ "sp-api/std", "sp-core/std", "sp-domains/std", - "sp-inherents/std" ] diff --git a/domains/primitives/domain-tracker/src/lib.rs b/domains/primitives/domain-tracker/src/lib.rs index f2aca52c88..88459adc1f 100644 --- a/domains/primitives/domain-tracker/src/lib.rs +++ b/domains/primitives/domain-tracker/src/lib.rs @@ -18,25 +18,26 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode}; -use sp_core::storage::StorageKey; +use sp_core::sp_std; use sp_domains::DomainId; -use sp_inherents::{InherentIdentifier, IsFatalError}; +use sp_std::vec::Vec; -/// The identifier for the `domain-tracker` inherent. -pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"dmn-trkr"; - -#[derive(Encode)] -pub struct NoFatalError(E); -impl IsFatalError for NoFatalError { - fn is_fatal_error(&self) -> bool { - false - } +/// Predigest item that contains the Confirmed Block's state root for domain tracker +#[derive(Debug, Clone, Encode, Decode)] +pub struct StateRootUpdate { + pub number: Number, + pub state_root: StateRoot, } -/// Inherent type provided by the domain-tracker. -#[derive(Encode, Decode)] -pub struct InherentType { - pub system_domain_state_root: StateRoot, +/// Implemented by the Domain tracker and used by the domain registry on System domain to +/// add the new state roots of the core domain. +pub trait CoreDomainTracker { + /// Adds the latest state root for a given domain. + fn add_core_domain_state_root( + domain_id: DomainId, + block_number: BlockNumber, + state_root: StateRoot, + ); } sp_api::decl_runtime_apis! { @@ -48,6 +49,6 @@ sp_api::decl_runtime_apis! { /// Returns the storage key for the state root at a block number for core domain /// as present on the system domain. /// Returns None if the block number is not confirmed yet. - fn storage_key_for_core_domain_state_root(domain_id: DomainId, block_number: BlockNumber) -> Option; + fn storage_key_for_core_domain_state_root(domain_id: DomainId, block_number: BlockNumber) -> Option>; } } diff --git a/domains/primitives/messenger/Cargo.toml b/domains/primitives/messenger/Cargo.toml index 6bbb886ae0..317ac893be 100644 --- a/domains/primitives/messenger/Cargo.toml +++ b/domains/primitives/messenger/Cargo.toml @@ -18,7 +18,8 @@ codec = { package = "parity-scale-codec", version = "3.1.5", default-features = frame-support = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } -sp-domains = { version = "0.1.0", path = "../../../crates/sp-domains" } +sp-core = { version = "6.0.0", default-features = false, git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } +sp-domains = { version = "0.1.0", default-features = false, path = "../../../crates/sp-domains" } sp-runtime = { version = "6.0.0", default-features = false, git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } sp-trie = { version = "6.0.0", default-features = false, git = "https://github.com/subspace/substrate", rev = "66d3f73c5ef66c975fa5f54b6736ccd6821e395f" } @@ -29,6 +30,7 @@ std = [ "frame-support/std", "scale-info/std", "sp-api/std", + "sp-core/std", "sp-domains/std", "sp-runtime/std", "sp-trie/std" diff --git a/domains/primitives/messenger/src/endpoint.rs b/domains/primitives/messenger/src/endpoint.rs index 964fcc5f5b..c74236caeb 100644 --- a/domains/primitives/messenger/src/endpoint.rs +++ b/domains/primitives/messenger/src/endpoint.rs @@ -3,7 +3,8 @@ use frame_support::Parameter; use scale_info::TypeInfo; use sp_domains::DomainId; use sp_runtime::traits::Member; -use sp_runtime::{DispatchError, DispatchResult}; +use sp_runtime::{sp_std, DispatchError, DispatchResult}; +use sp_std::vec::Vec; /// Represents a particular endpoint in a given Execution environment. pub type EndpointId = u64; diff --git a/domains/primitives/messenger/src/lib.rs b/domains/primitives/messenger/src/lib.rs index 0fa306dcb8..85a648e344 100644 --- a/domains/primitives/messenger/src/lib.rs +++ b/domains/primitives/messenger/src/lib.rs @@ -24,6 +24,8 @@ use codec::{Decode, Encode}; use messages::{CrossDomainMessage, MessageId, RelayerMessagesWithStorageKey}; use sp_domains::DomainId; use sp_runtime::app_crypto::sp_core::storage::StorageKey; +use sp_runtime::sp_std; +use sp_std::vec::Vec; /// Implemented by domain registry on system domain or system domain tracker on core domains. /// This trait supports utilities to verify the message coming from src_domain to system domain. diff --git a/domains/primitives/messenger/src/messages.rs b/domains/primitives/messenger/src/messages.rs index 28d7d9278d..8f15ecc7e9 100644 --- a/domains/primitives/messenger/src/messages.rs +++ b/domains/primitives/messenger/src/messages.rs @@ -2,10 +2,10 @@ use crate::endpoint::{EndpointRequest, EndpointResponse}; use codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_domains::DomainId; -use sp_runtime::app_crypto::sp_core::storage::StorageKey; use sp_runtime::app_crypto::sp_core::U256; use sp_runtime::traits::CheckedAdd; -use sp_runtime::DispatchError; +use sp_runtime::{sp_std, DispatchError}; +use sp_std::vec::Vec; use sp_trie::StorageProof; /// Channel identity. @@ -153,7 +153,7 @@ pub struct RelayerMessageWithStorageKey { /// Message nonce within the channel. pub nonce: Nonce, /// Storage key to generate proof for using proof backend. - pub storage_key: StorageKey, + pub storage_key: Vec, } /// Set of messages with storage keys to be relayed by a given relayer. diff --git a/domains/runtime/system/Cargo.toml b/domains/runtime/system/Cargo.toml index f49438daba..ec0496df34 100644 --- a/domains/runtime/system/Cargo.toml +++ b/domains/runtime/system/Cargo.toml @@ -48,8 +48,12 @@ core-payments-domain-runtime = { path = "../../runtime/core-payments", default-f domain-pallet-executive = { path = "../../pallets/executive", default-features = false } domain-runtime-primitives = { path = "../../primitives/runtime", default-features = false } pallet-domain-registry = { path = "../../pallets/domain-registry", default-features = false } +pallet-domain-tracker = { path = "../../pallets/domain-tracker", default-features = false } pallet-executor-registry = { path = "../../pallets/executor-registry", default-features = false } +pallet-messenger = { path = "../../pallets/messenger", default-features = false } sp-domains = { path = "../../../crates/sp-domains", default-features = false } +sp-domain-tracker = { path = "../../primitives/domain-tracker", default-features = false } +sp-messenger = { path = "../../primitives/messenger", default-features = false } subspace-runtime-primitives = { path = "../../../crates/subspace-runtime-primitives", default-features = false } system-runtime-primitives = { path = "../../primitives/system-runtime", default-features = false } @@ -80,6 +84,8 @@ std = [ "frame-system/std", "frame-system-rpc-runtime-api/std", "pallet-balances/std", + "pallet-domain-tracker/std", + "pallet-messenger/std", "pallet-transaction-payment-rpc-runtime-api/std", "pallet-transaction-payment/std", "core-payments-domain-runtime/std", @@ -88,6 +94,8 @@ std = [ "pallet-domain-registry/std", "pallet-executor-registry/std", "sp-domains/std", + "sp-domain-tracker/std", + "sp-messenger/std", "subspace-runtime-primitives/std", "system-runtime-primitives/std", ] diff --git a/domains/runtime/system/src/runtime.rs b/domains/runtime/system/src/runtime.rs index 6fdb1f2d3b..9b0efe99f6 100644 --- a/domains/runtime/system/src/runtime.rs +++ b/domains/runtime/system/src/runtime.rs @@ -1,3 +1,4 @@ +use domain_runtime_primitives::RelayerId; pub use domain_runtime_primitives::{ AccountId, Address, Balance, BlockNumber, Hash, Index, Signature, }; @@ -14,8 +15,12 @@ use sp_core::crypto::KeyTypeId; use sp_core::OpaqueMetadata; use sp_domains::bundle_election::BundleElectionParams; use sp_domains::{DomainId, ExecutorPublicKey, SignedOpaqueBundle}; +use sp_messenger::endpoint::{Endpoint, EndpointHandler}; +use sp_messenger::messages::{CrossDomainMessage, MessageId, RelayerMessagesWithStorageKey}; use sp_runtime::traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, NumberFor}; use sp_runtime::transaction_validity::{TransactionSource, TransactionValidity}; +#[cfg(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; use sp_runtime::{create_runtime_str, generic, impl_opaque_keys, ApplyExtrinsicResult}; pub use sp_runtime::{MultiAddress, Perbill, Permill}; use sp_std::prelude::*; @@ -27,9 +32,6 @@ use subspace_runtime_primitives::{SHANNON, SSC}; // Make core-payments WASM runtime available. include!(concat!(env!("OUT_DIR"), "/core_payments_wasm_bundle.rs")); -#[cfg(any(feature = "std", test))] -pub use sp_runtime::BuildStorage; - /// Block header type as expected by this runtime. pub type Header = generic::Header; @@ -297,6 +299,48 @@ impl pallet_domain_registry::Config for Runtime { type MinDomainOperatorStake = MinDomainOperatorStake; type MaximumReceiptDrift = MaximumReceiptDrift; type ReceiptsPruningDepth = ReceiptsPruningDepth; + type CoreDomainTracker = DomainTracker; +} + +parameter_types! { + pub const StateRootsBound: u32 = 50; + pub const RelayConfirmationDepth: BlockNumber = 7; +} + +impl pallet_domain_tracker::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ConfirmedStateRootsBound = StateRootsBound; + type RelayerConfirmationDepth = RelayConfirmationDepth; +} + +parameter_types! { + pub const MaximumRelayers: u32 = 100; + pub const RelayerDeposit: Balance = 100 * SSC; + pub const SystemDomainId: DomainId = DomainId::SYSTEM; +} + +impl pallet_messenger::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SelfDomainId = SystemDomainId; + type DomainTracker = DomainTracker; + + fn get_endpoint_response_handler( + _endpoint: &Endpoint, + ) -> Option>> { + None + } + + type Currency = Balances; + type MaximumRelayers = MaximumRelayers; + type RelayerDeposit = RelayerDeposit; +} + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + RuntimeCall: From, +{ + type Extrinsic = UncheckedExtrinsic; + type OverarchingCall = RuntimeCall; } // Create the runtime by composing the FRAME pallets that were previously configured. @@ -322,6 +366,8 @@ construct_runtime!( // built. ExecutorRegistry: pallet_executor_registry = 4, DomainRegistry: pallet_domain_registry = 5, + DomainTracker: pallet_domain_tracker = 6, + Messenger: pallet_messenger = 7, } ); @@ -516,6 +562,46 @@ impl_runtime_apis! { } } + impl sp_domain_tracker::DomainTrackerApi for Runtime { + fn storage_key_for_core_domain_state_root( + domain_id: DomainId, + block_number: BlockNumber, + ) -> Option> { + DomainTracker::storage_key_for_core_domain_state_root(domain_id, block_number) + } + } + + impl sp_messenger::RelayerApi for Runtime { + fn domain_id() -> DomainId { + SystemDomainId::get() + } + + fn relay_confirmation_depth() -> BlockNumber { + RelayConfirmationDepth::get() + } + + fn relayer_assigned_messages(relayer_id: RelayerId) -> RelayerMessagesWithStorageKey { + Messenger::relayer_assigned_messages(relayer_id) + } + + fn submit_outbox_message_unsigned(msg: CrossDomainMessage<::Hash, BlockNumber>) { + Messenger::submit_outbox_message_unsigned(msg) + } + + fn submit_inbox_response_message_unsigned(msg: CrossDomainMessage<::Hash, BlockNumber>) { + Messenger::submit_inbox_response_message_unsigned(msg) + } + + fn should_relay_outbox_message(dst_domain_id: DomainId, msg_id: MessageId) -> bool { + Messenger::should_relay_outbox_message(dst_domain_id, msg_id) + } + + fn should_relay_inbox_message_response(dst_domain_id: DomainId, msg_id: MessageId) -> bool { + Messenger::should_relay_inbox_message_response(dst_domain_id, msg_id) + } + } + + #[cfg(feature = "runtime-benchmarks")] impl frame_benchmarking::Benchmark for Runtime { fn benchmark_metadata(extra: bool) -> ( diff --git a/domains/service/Cargo.toml b/domains/service/Cargo.toml index 38e71473a8..d732f78ddd 100644 --- a/domains/service/Cargo.toml +++ b/domains/service/Cargo.toml @@ -66,11 +66,15 @@ sp-transaction-pool = { git = "https://github.com/subspace/substrate", rev = "66 domain-client-consensus-relay-chain = { path = "../client/consensus-relay-chain" } domain-client-executor = { path = "../client/domain-executor" } domain-client-executor-gossip = { path = "../client/executor-gossip" } +domain-client-message-relayer = { path = "../client/relayer" } domain-runtime-primitives = { path = "../primitives/runtime" } system-runtime-primitives = { path = "../primitives/system-runtime" } # Subspace dependencies +sc-consensus-subspace = { version = "0.1.0", path = "../../crates/sc-consensus-subspace" } sp-domains = { path = "../../crates/sp-domains" } +sp-domain-tracker = { path = "../../domains/primitives/domain-tracker" } +sp-messenger = { path = "../../domains/primitives/messenger" } subspace-core-primitives = { path = "../../crates/subspace-core-primitives" } subspace-runtime-primitives = { path = "../../crates/subspace-runtime-primitives" } diff --git a/domains/service/src/core_domain.rs b/domains/service/src/core_domain.rs index dc727f69c3..9ed2d33383 100644 --- a/domains/service/src/core_domain.rs +++ b/domains/service/src/core_domain.rs @@ -114,7 +114,7 @@ where client.clone(), ); - let import_queue = domain_client_consensus_relay_chain::import_queue( + let (import_queue, _) = domain_client_consensus_relay_chain::import_queue( client.clone(), &task_manager.spawn_essential_handle(), config.prometheus_registry(), diff --git a/domains/service/src/lib.rs b/domains/service/src/lib.rs index b2101ddfca..b5baa69bd9 100644 --- a/domains/service/src/lib.rs +++ b/domains/service/src/lib.rs @@ -5,4 +5,4 @@ mod rpc; mod system_domain; pub use self::core_domain::{new_full as new_full_core, NewFull as NewFullCore}; -pub use self::system_domain::{new_full, NewFull}; +pub use self::system_domain::{new_full, Configuration, NewFull}; diff --git a/domains/service/src/system_domain.rs b/domains/service/src/system_domain.rs index e7a727652d..5e7f4f2df3 100644 --- a/domains/service/src/system_domain.rs +++ b/domains/service/src/system_domain.rs @@ -1,16 +1,18 @@ use domain_client_executor::SystemExecutor; use domain_client_executor_gossip::ExecutorGossipParams; -use domain_runtime_primitives::DomainCoreApi; +use domain_runtime_primitives::{AccountId, Balance, DomainCoreApi, Hash, RelayerId}; use futures::channel::mpsc; use futures::Stream; +use jsonrpsee::tracing; use pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi; use sc_client_api::{BlockBackend, StateBackendFor}; use sc_consensus::ForkChoiceStrategy; +use sc_consensus_subspace::notification::SubspaceNotificationStream; use sc_executor::{NativeElseWasmExecutor, NativeExecutionDispatch}; use sc_network::NetworkService; use sc_service::{ - BuildNetworkParams, Configuration, NetworkStarter, PartialComponents, SpawnTasksParams, - TFullBackend, TFullClient, TaskManager, + BuildNetworkParams, Configuration as ServiceConfiguration, NetworkStarter, PartialComponents, + SpawnTasksParams, TFullBackend, TFullClient, TaskManager, }; use sc_telemetry::{Telemetry, TelemetryWorker, TelemetryWorkerHandle}; use sc_utils::mpsc::tracing_unbounded; @@ -19,8 +21,10 @@ use sp_block_builder::BlockBuilder; use sp_blockchain::HeaderBackend; use sp_consensus::SelectChain; use sp_consensus_slots::Slot; +use sp_core::crypto::Ss58Codec; use sp_core::traits::SpawnEssentialNamed; use sp_domains::ExecutorApi; +use sp_messenger::RelayerApi; use sp_offchain::OffchainWorkerApi; use sp_session::SessionKeys; use sp_transaction_pool::runtime_api::TaggedTransactionQueue; @@ -29,7 +33,6 @@ use subspace_core_primitives::Blake2b256Hash; use subspace_runtime_primitives::Index as Nonce; use substrate_frame_rpc_system::AccountNonceApi; use system_domain_runtime::opaque::Block; -use system_domain_runtime::{AccountId, Balance, Hash}; use system_runtime_primitives::SystemDomainApi; /// Domain full client. @@ -49,7 +52,7 @@ pub type FullPool = sc_transaction_pool::BasicPool /// be able to perform chain operations. #[allow(clippy::type_complexity)] fn new_partial( - config: &Configuration, + config: &ServiceConfiguration, ) -> Result< PartialComponents< FullClient, @@ -61,6 +64,7 @@ fn new_partial( Option, Option, NativeElseWasmExecutor, + SubspaceNotificationStream>, ), >, sc_service::Error, @@ -114,11 +118,12 @@ where client.clone(), ); - let import_queue = domain_client_consensus_relay_chain::import_queue( - client.clone(), - &task_manager.spawn_essential_handle(), - config.prometheus_registry(), - )?; + let (import_queue, import_block_notification_stream) = + domain_client_consensus_relay_chain::import_queue( + client.clone(), + &task_manager.spawn_essential_handle(), + config.prometheus_registry(), + )?; let params = PartialComponents { backend, @@ -128,12 +133,51 @@ where task_manager, transaction_pool, select_chain: (), - other: (telemetry, telemetry_worker_handle, executor), + other: ( + telemetry, + telemetry_worker_handle, + executor, + import_block_notification_stream, + ), }; Ok(params) } +/// Secondary chain configuration. +pub struct Configuration { + service_config: ServiceConfiguration, + maybe_relayer_id: Option, +} + +/// Configuration error for secondary chain. +#[derive(Debug)] +pub enum ConfigurationError { + /// Emits when the relayer id is invalid. + InvalidRelayerId, +} + +impl Configuration { + pub fn new( + service_config: ServiceConfiguration, + maybe_relayer_id: Option, + ) -> Result { + let maybe_relayer_id = match maybe_relayer_id { + None => None, + Some(relayer_id) => { + let relayer_id = RelayerId::from_ss58check(&relayer_id) + .map_err(|_| ConfigurationError::InvalidRelayerId)?; + Some(relayer_id) + } + }; + + Ok(Configuration { + service_config, + maybe_relayer_id, + }) + } +} + type SystemDomainExecutor = SystemExecutor< Block, PBlock, @@ -162,7 +206,8 @@ where + SystemDomainApi, PBlock::Hash> + TaggedTransactionQueue + AccountNonceApi - + TransactionPaymentRuntimeApi, + + TransactionPaymentRuntimeApi + + RelayerApi>, { /// Task manager. pub task_manager: TaskManager, @@ -228,30 +273,33 @@ where + SystemDomainApi, PBlock::Hash> + TaggedTransactionQueue + AccountNonceApi - + TransactionPaymentRuntimeApi, + + TransactionPaymentRuntimeApi + + RelayerApi>, ExecutorDispatch: NativeExecutionDispatch + 'static, { // TODO: Do we even need block announcement on secondary node? // secondary_chain_config.announce_block = false; secondary_chain_config + .service_config .network .extra_sets .push(domain_client_executor_gossip::executor_gossip_peers_set_config()); - let params = new_partial(&secondary_chain_config)?; + let params = new_partial(&secondary_chain_config.service_config)?; - let (mut telemetry, _telemetry_worker_handle, code_executor) = params.other; + let (mut telemetry, _telemetry_worker_handle, code_executor, import_block_notification_stream) = + params.other; let client = params.client.clone(); let backend = params.backend.clone(); - let validator = secondary_chain_config.role.is_authority(); + let validator = secondary_chain_config.service_config.role.is_authority(); let transaction_pool = params.transaction_pool.clone(); let mut task_manager = params.task_manager; let (network, system_rpc_tx, tx_handler_controller, network_starter) = sc_service::build_network(BuildNetworkParams { - config: &secondary_chain_config, + config: &secondary_chain_config.service_config, client: client.clone(), transaction_pool: transaction_pool.clone(), spawn_handle: task_manager.spawn_handle(), @@ -264,7 +312,10 @@ where let rpc_builder = { let client = client.clone(); let transaction_pool = transaction_pool.clone(); - let chain_spec = secondary_chain_config.chain_spec.cloned_box(); + let chain_spec = secondary_chain_config + .service_config + .chain_spec + .cloned_box(); Box::new(move |deny_unsafe, _| { let deps = crate::rpc::FullDeps { @@ -283,7 +334,7 @@ where client: client.clone(), transaction_pool: transaction_pool.clone(), task_manager: &mut task_manager, - config: secondary_chain_config, + config: secondary_chain_config.service_config, keystore: params.keystore_container.sync_keystore(), backend: backend.clone(), network: network.clone(), @@ -324,6 +375,25 @@ where }); spawn_essential.spawn_essential_blocking("domain-gossip", None, Box::pin(executor_gossip)); + if let Some(relayer_id) = secondary_chain_config.maybe_relayer_id { + tracing::info!( + "Starting system domain relayer with relayer_id[{:?}]", + relayer_id + ); + let relayer_worker = domain_client_message_relayer::worker::relay_system_domain_messages( + relayer_id, + client.clone(), + import_block_notification_stream.subscribe(), + network.clone(), + ); + + spawn_essential.spawn_essential_blocking( + "system-domain-relayer", + None, + Box::pin(relayer_worker), + ); + } + let new_full = NewFull { task_manager, client, diff --git a/domains/test/runtime/Cargo.toml b/domains/test/runtime/Cargo.toml index 944d703abb..56e95d1dd7 100644 --- a/domains/test/runtime/Cargo.toml +++ b/domains/test/runtime/Cargo.toml @@ -51,8 +51,12 @@ pallet-transaction-payment-rpc-runtime-api = { default-features = false, git = " domain-pallet-executive = { path = "../../pallets/executive", default-features = false } domain-runtime-primitives = { path = "../../primitives/runtime", default-features = false } pallet-domain-registry = { path = "../../pallets/domain-registry", default-features = false } +pallet-domain-tracker = { path = "../../pallets/domain-tracker", default-features = false } pallet-executor-registry = { path = "../../pallets/executor-registry", default-features = false } +pallet-messenger = { path = "../../pallets/messenger", default-features = false } sp-domains = { path = "../../../crates/sp-domains", default-features = false } +sp-domain-tracker = { path = "../../primitives/domain-tracker", default-features = false } +sp-messenger = { path = "../../primitives/messenger", default-features = false } subspace-runtime-primitives = { path = "../../../crates/subspace-runtime-primitives", default-features = false } system-runtime-primitives = { path = "../../primitives/system-runtime", default-features = false } @@ -84,8 +88,12 @@ std = [ "domain-pallet-executive/std", "domain-runtime-primitives/std", "pallet-domain-registry/std", + "pallet-domain-tracker/std", + "pallet-messenger/std", "pallet-executor-registry/std", "sp-domains/std", + "sp-domain-tracker/std", + "sp-messenger/std", "subspace-runtime-primitives/std", "system-runtime-primitives/std", ] diff --git a/domains/test/runtime/src/runtime.rs b/domains/test/runtime/src/runtime.rs index 2f622fffab..4e0fb126fb 100644 --- a/domains/test/runtime/src/runtime.rs +++ b/domains/test/runtime/src/runtime.rs @@ -1,3 +1,4 @@ +use domain_runtime_primitives::RelayerId; pub use domain_runtime_primitives::{ AccountId, Address, Balance, BlockNumber, Hash, Index, Signature, }; @@ -14,6 +15,8 @@ use sp_core::crypto::KeyTypeId; use sp_core::OpaqueMetadata; use sp_domains::bundle_election::BundleElectionParams; use sp_domains::{DomainId, ExecutorPublicKey, SignedOpaqueBundle}; +use sp_messenger::endpoint::{Endpoint, EndpointHandler}; +use sp_messenger::messages::{CrossDomainMessage, MessageId, RelayerMessagesWithStorageKey}; use sp_runtime::traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, NumberFor}; use sp_runtime::transaction_validity::{TransactionSource, TransactionValidity}; use sp_runtime::{create_runtime_str, generic, impl_opaque_keys, ApplyExtrinsicResult}; @@ -294,6 +297,48 @@ impl pallet_domain_registry::Config for Runtime { type MinDomainOperatorStake = MinDomainOperatorStake; type MaximumReceiptDrift = MaximumReceiptDrift; type ReceiptsPruningDepth = ReceiptsPruningDepth; + type CoreDomainTracker = DomainTracker; +} + +parameter_types! { + pub const StateRootsBound: u32 = 50; + pub const RelayConfirmationDepth: BlockNumber = 7; +} + +impl pallet_domain_tracker::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ConfirmedStateRootsBound = StateRootsBound; + type RelayerConfirmationDepth = RelayConfirmationDepth; +} + +parameter_types! { + pub const MaximumRelayers: u32 = 100; + pub const RelayerDeposit: Balance = 100 * SSC; + pub const SystemDomainId: DomainId = DomainId::SYSTEM; +} + +impl pallet_messenger::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SelfDomainId = SystemDomainId; + type DomainTracker = DomainTracker; + + fn get_endpoint_response_handler( + _endpoint: &Endpoint, + ) -> Option>> { + None + } + + type Currency = Balances; + type MaximumRelayers = MaximumRelayers; + type RelayerDeposit = RelayerDeposit; +} + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + RuntimeCall: From, +{ + type Extrinsic = UncheckedExtrinsic; + type OverarchingCall = RuntimeCall; } // Create the runtime by composing the FRAME pallets that were previously configured. @@ -318,6 +363,8 @@ construct_runtime!( // built. ExecutorRegistry: pallet_executor_registry, DomainRegistry: pallet_domain_registry, + DomainTracker: pallet_domain_tracker, + Messenger: pallet_messenger, } ); @@ -496,4 +543,43 @@ impl_runtime_apis! { MaximumReceiptDrift::get() } } + + impl sp_domain_tracker::DomainTrackerApi for Runtime { + fn storage_key_for_core_domain_state_root( + domain_id: DomainId, + block_number: BlockNumber, + ) -> Option> { + DomainTracker::storage_key_for_core_domain_state_root(domain_id, block_number) + } + } + + impl sp_messenger::RelayerApi for Runtime { + fn domain_id() -> DomainId { + SystemDomainId::get() + } + + fn relay_confirmation_depth() -> BlockNumber { + RelayConfirmationDepth::get() + } + + fn relayer_assigned_messages(relayer_id: RelayerId) -> RelayerMessagesWithStorageKey { + Messenger::relayer_assigned_messages(relayer_id) + } + + fn submit_outbox_message_unsigned(msg: CrossDomainMessage<::Hash, BlockNumber>) { + Messenger::submit_outbox_message_unsigned(msg) + } + + fn submit_inbox_response_message_unsigned(msg: CrossDomainMessage<::Hash, BlockNumber>) { + Messenger::submit_inbox_response_message_unsigned(msg) + } + + fn should_relay_outbox_message(dst_domain_id: DomainId, msg_id: MessageId) -> bool { + Messenger::should_relay_outbox_message(dst_domain_id, msg_id) + } + + fn should_relay_inbox_message_response(dst_domain_id: DomainId, msg_id: MessageId) -> bool { + Messenger::should_relay_inbox_message_response(dst_domain_id, msg_id) + } + } } diff --git a/domains/test/service/src/lib.rs b/domains/test/service/src/lib.rs index af159594f0..15735998fa 100644 --- a/domains/test/service/src/lib.rs +++ b/domains/test/service/src/lib.rs @@ -32,8 +32,8 @@ use sc_service::config::{ OffchainWorkerConfig, PruningMode, WasmExecutionMethod, }; use sc_service::{ - BasePath, BlocksPruning, Configuration, Error as ServiceError, NetworkStarter, Role, - RpcHandlers, TFullBackend, TFullClient, TaskManager, + BasePath, BlocksPruning, Configuration as ServiceConfiguration, Error as ServiceError, + NetworkStarter, Role, RpcHandlers, TFullBackend, TFullClient, TaskManager, }; use sp_arithmetic::traits::SaturatedConversion; use sp_blockchain::HeaderBackend; @@ -48,6 +48,7 @@ use substrate_test_client::{ BlockchainEventsExt, RpcHandlersExt, RpcTransactionError, RpcTransactionOutput, }; +use domain_service::Configuration; pub use domain_test_runtime as runtime; pub use sp_keyring::Sr25519Keyring as Keyring; @@ -96,8 +97,8 @@ pub type Client = /// the production. #[sc_tracing::logging::prefix_logs_with(secondary_chain_config.network.node_name.as_str())] async fn run_executor( - secondary_chain_config: Configuration, - primary_chain_config: Configuration, + secondary_chain_config: ServiceConfiguration, + primary_chain_config: ServiceConfiguration, ) -> sc_service::error::Result<( TaskManager, Arc, @@ -135,6 +136,9 @@ async fn run_executor( })? }; + let secondary_chain_config = Configuration::new(secondary_chain_config, None).map_err(|e| { + sc_service::Error::Other(format!("Failed to build a full subspace node: {e:?}")) + })?; let block_import_throttling_buffer_size = 10; let secondary_chain_node = domain_service::new_full::< _, @@ -372,7 +376,7 @@ pub fn node_config( nodes_exclusive: bool, role: Role, base_path: BasePath, -) -> Result { +) -> Result { let root = base_path.path().to_path_buf(); let key_seed = key.to_seed(); @@ -400,7 +404,7 @@ pub fn node_config( network_config.transport = TransportConfig::MemoryOnly; - Ok(Configuration { + Ok(ServiceConfiguration { impl_name: "domain-test-node".to_string(), impl_version: "0.1".to_string(), role,