diff --git a/Cargo.lock b/Cargo.lock index d37c21cf66..000658dc6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11552,6 +11552,7 @@ dependencies = [ "sp-consensus", "sp-consensus-subspace", "sp-core", + "sp-domain-digests", "sp-domains", "sp-messenger", "sp-runtime", diff --git a/crates/subspace-node/Cargo.toml b/crates/subspace-node/Cargo.toml index 636b11d463..058561ec02 100644 --- a/crates/subspace-node/Cargo.toml +++ b/crates/subspace-node/Cargo.toml @@ -62,6 +62,7 @@ sp-consensus = { version = "0.10.0-dev", git = "https://github.com/subspace/subs sp-consensus-subspace = { version = "0.1.0", path = "../sp-consensus-subspace" } sp-core = { version = "21.0.0", git = "https://github.com/subspace/substrate", rev = "55c157cff49b638a59d81a9f971f0f9a66829c71" } sp-domains = { version = "0.1.0", path = "../sp-domains" } +sp-domain-digests = { version = "0.1.0", path = "../../domains/primitives/digests" } sp-messenger = { version = "0.1.0", path = "../../domains/primitives/messenger" } sp-runtime = { version = "24.0.0", git = "https://github.com/subspace/substrate", rev = "55c157cff49b638a59d81a9f971f0f9a66829c71" } subspace-archiving = { version = "0.1.0", path = "../subspace-archiving" } diff --git a/crates/subspace-node/src/bin/subspace-node.rs b/crates/subspace-node/src/bin/subspace-node.rs index db45cd6ae7..603029adf4 100644 --- a/crates/subspace-node/src/bin/subspace-node.rs +++ b/crates/subspace-node/src/bin/subspace-node.rs @@ -399,6 +399,35 @@ fn main() -> Result<(), Error> { })?; } DomainSubcommand::BuildGenesisConfig(cmd) => cmd.run()?, + DomainSubcommand::ExportExecutionReceipt(cmd) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|consensus_chain_config| { + let domain_cli = DomainCli::new( + cli.run + .base_path()? + .map(|base_path| base_path.path().to_path_buf()), + cmd.domain_args.clone().into_iter(), + ); + let domain_config = domain_cli + .create_domain_configuration(consensus_chain_config.tokio_handle) + .map_err(|error| { + sc_service::Error::Other(format!( + "Failed to create domain configuration: {error:?}" + )) + })?; + + let executor: sc_executor::NativeElseWasmExecutor = + sc_service::new_native_or_wasm_executor(&domain_config); + + let (client, _, _, _) = sc_service::new_full_parts::< + DomainBlock, + evm_domain_runtime::RuntimeApi, + _, + >(&domain_config, None, executor)?; + + cmd.run(&client, &client) + })?; + } _ => unimplemented!("Domain subcommand"), }, None => { diff --git a/crates/subspace-node/src/domain/cli.rs b/crates/subspace-node/src/domain/cli.rs index 9c1f6e33cf..a4452c2713 100644 --- a/crates/subspace-node/src/domain/cli.rs +++ b/crates/subspace-node/src/domain/cli.rs @@ -16,18 +16,26 @@ use crate::domain::evm_chain_spec::{self, SpecId}; use clap::Parser; +use domain_runtime_primitives::opaque::Block as DomainBlock; use sc_cli::{ - ChainSpec, CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams, - NetworkParams, Result, Role, RunCmd as SubstrateRunCmd, RuntimeVersion, SharedParams, - SubstrateCli, + BlockNumberOrHash, ChainSpec, CliConfiguration, DefaultConfigurationValues, ImportParams, + KeystoreParams, NetworkParams, Result, Role, RunCmd as SubstrateRunCmd, RuntimeVersion, + SharedParams, SubstrateCli, }; +use sc_client_api::backend::AuxStore; use sc_service::config::PrometheusConfig; use sc_service::{BasePath, Configuration}; +use sp_blockchain::HeaderBackend; +use sp_domain_digests::AsPredigest; use sp_domains::DomainId; +use sp_runtime::generic::BlockId; +use sp_runtime::traits::Header; +use sp_runtime::DigestItem; use std::io::Write; use std::net::SocketAddr; use std::num::ParseIntError; use std::path::PathBuf; +use subspace_runtime::Block; /// Sub-commands supported by the executor. #[derive(Debug, clap::Subcommand)] @@ -45,6 +53,9 @@ pub enum Subcommand { /// Build the genesis config of the evm domain chain in json format BuildGenesisConfig(BuildGenesisConfigCmd), + + /// The `export-execution-receipt` command used to get the ER from the auxiliary storage of the operator client + ExportExecutionReceipt(ExportExecutionReceiptCmd), } fn parse_domain_id(s: &str) -> std::result::Result { @@ -320,3 +331,104 @@ impl BuildGenesisConfigCmd { Ok(()) } } + +/// The `export-execution-receipt` command used to get the ER from the auxiliary storage of the operator client +#[derive(Debug, Clone, Parser)] +pub struct ExportExecutionReceiptCmd { + /// Get the `ExecutionReceipt` by domain block number or hash + #[arg(long, conflicts_with_all = &["consensus_block_hash"])] + pub domain_block: Option, + + /// Get the `ExecutionReceipt` by consensus block hash + #[arg(long, conflicts_with_all = &["domain_block"])] + pub consensus_block_hash: Option, + + /// The base struct of the export-execution-receipt command. + #[clap(flatten)] + pub shared_params: SharedParams, + + /// Domain arguments + /// + /// The command-line arguments provided first will be passed to the embedded consensus node, + /// while the arguments provided after `--` will be passed to the domain node. + /// + /// subspace-node export-execution-receipt [consensus-chain-args] -- [domain-args] + #[arg(raw = true)] + pub domain_args: Vec, +} + +impl CliConfiguration for ExportExecutionReceiptCmd { + fn shared_params(&self) -> &SharedParams { + &self.shared_params + } +} + +impl ExportExecutionReceiptCmd { + /// Run the export-execution-receipt command + pub fn run( + &self, + domain_client: &Client, + domain_backend: &Backend, + ) -> sc_cli::Result<()> + where + Backend: AuxStore, + Client: HeaderBackend, + { + let consensus_block_hash = match (&self.consensus_block_hash, &self.domain_block) { + // Get ER by consensus block hash + (Some(raw_consensus_block_hash), None) => { + match raw_consensus_block_hash.parse::()? { + BlockId::Hash(h) => h, + BlockId::Number(_) => { + eprintln!( + "unexpected input {raw_consensus_block_hash:?}, expected consensus block hash", + ); + return Ok(()); + } + } + } + // Get ER by domain block hash or number + (None, Some(raw_domain_block)) => { + let domain_block_hash = match raw_domain_block.parse::()? { + BlockId::Hash(h) => h, + BlockId::Number(number) => domain_client.hash(number)?.ok_or_else(|| { + sp_blockchain::Error::Backend(format!( + "Domain block hash for #{number:?} not found", + )) + })?, + }; + let domain_header = domain_client.header(domain_block_hash)?.ok_or_else(|| { + sp_blockchain::Error::Backend(format!( + "Header for domain block {domain_block_hash:?} not found" + )) + })?; + + domain_header + .digest() + .convert_first(DigestItem::as_consensus_block_info) + .ok_or_else(|| { + sp_blockchain::Error::Application(Box::from( + "Domain block header for {domain_hash:?} must have consensus block info predigest" + )) + })? + } + _ => { + eprintln!("Expect the domain-block or consensus-block-hash argument",); + return Ok(()); + } + }; + + match domain_client_operator::load_execution_receipt::( + domain_backend, + consensus_block_hash, + )? { + Some(er) => { + println!("ExecutionReceipt of consensus block {consensus_block_hash:?}:\n{er:?}",); + } + None => { + println!("ExecutionReceipt of consensus block {consensus_block_hash:?} not found",); + } + } + Ok(()) + } +} diff --git a/domains/client/domain-operator/src/aux_schema.rs b/domains/client/domain-operator/src/aux_schema.rs index da1438e73b..01bfb41f48 100644 --- a/domains/client/domain-operator/src/aux_schema.rs +++ b/domains/client/domain-operator/src/aux_schema.rs @@ -145,7 +145,7 @@ where } /// Load the execution receipt for given consensus block hash. -pub(super) fn load_execution_receipt( +pub fn load_execution_receipt( backend: &Backend, consensus_block_hash: CBlock::Hash, ) -> ClientResult>> diff --git a/domains/client/domain-operator/src/lib.rs b/domains/client/domain-operator/src/lib.rs index 42032858f6..d4013e0b2e 100644 --- a/domains/client/domain-operator/src/lib.rs +++ b/domains/client/domain-operator/src/lib.rs @@ -77,6 +77,7 @@ mod parent_chain; mod tests; mod utils; +pub use self::aux_schema::load_execution_receipt; pub use self::bootstrapper::{BootstrapResult, Bootstrapper}; pub use self::operator::Operator; pub use self::parent_chain::DomainParentChain;