diff --git a/integration-tests/chain-signatures/Cargo.lock b/integration-tests/chain-signatures/Cargo.lock index 443456bb..e64182c0 100644 --- a/integration-tests/chain-signatures/Cargo.lock +++ b/integration-tests/chain-signatures/Cargo.lock @@ -3276,6 +3276,7 @@ dependencies = [ "secp256k1 0.28.2", "serde", "serde_json", + "shell-escape", "test-log", "testcontainers", "thiserror", diff --git a/integration-tests/chain-signatures/Cargo.toml b/integration-tests/chain-signatures/Cargo.toml index 8666f63f..0712fba4 100644 --- a/integration-tests/chain-signatures/Cargo.toml +++ b/integration-tests/chain-signatures/Cargo.toml @@ -21,6 +21,7 @@ rand = "0.7" reqwest = "0.11.16" serde = "1" serde_json = "1" +shell-escape = "0.1.5" testcontainers = { version = "0.15", features = ["experimental"] } tokio = { version = "1.28", features = ["full"] } tracing = "0.1" diff --git a/integration-tests/chain-signatures/src/execute.rs b/integration-tests/chain-signatures/src/execute.rs index 030f5ff3..1967e5d9 100644 --- a/integration-tests/chain-signatures/src/execute.rs +++ b/integration-tests/chain-signatures/src/execute.rs @@ -1,7 +1,7 @@ use anyhow::Context; use async_process::Child; -const PACKAGE_MULTICHAIN: &str = "mpc-node"; +pub(crate) const PACKAGE_MULTICHAIN: &str = "mpc-node"; pub fn target_dir() -> Option { let mut out_dir = std::path::Path::new(std::env!("OUT_DIR")); diff --git a/integration-tests/chain-signatures/src/lib.rs b/integration-tests/chain-signatures/src/lib.rs index afc4d405..dbdba4d3 100644 --- a/integration-tests/chain-signatures/src/lib.rs +++ b/integration-tests/chain-signatures/src/lib.rs @@ -19,6 +19,7 @@ use mpc_node::storage; use mpc_node::storage::triple_storage::TripleNodeStorageBox; use near_crypto::KeyFile; use near_workspaces::network::{Sandbox, ValidatorKey}; +use near_workspaces::types::{KeyType, SecretKey}; use near_workspaces::{Account, AccountId, Contract, Worker}; use serde_json::json; use testcontainers::{Container, GenericImage}; @@ -298,6 +299,62 @@ pub async fn docker(cfg: MultichainConfig, docker_client: &DockerClient) -> anyh Ok(Nodes::Docker { ctx, nodes }) } +pub async fn dry_host( + cfg: MultichainConfig, + docker_client: &DockerClient, +) -> anyhow::Result { + let ctx = setup(docker_client).await?; + + let accounts = + futures::future::join_all((0..cfg.nodes).map(|_| ctx.worker.dev_create_account())) + .await + .into_iter() + .collect::, _>>()?; + let mut node_cfgs = Vec::new(); + for account in accounts.iter().take(cfg.nodes) { + node_cfgs.push(local::Node::dry_run(&ctx, account, &cfg).await?); + } + + let candidates: HashMap = accounts + .iter() + .cloned() + .zip(&node_cfgs) + .map(|(account, node_cfg)| { + ( + account.id().clone(), + CandidateInfo { + account_id: account.id().as_str().parse().unwrap(), + url: format!("http://127.0.0.1:{0}", node_cfg.web_port), + cipher_pk: node_cfg.cipher_pk.to_bytes(), + sign_pk: node_cfg.sign_sk.public_key().to_string().parse().unwrap(), + }, + ) + }) + .collect(); + + println!("\nPlease call below to update localnet:\n"); + let near_rpc = ctx.lake_indexer.rpc_host_address.clone(); + println!("near config add-connection --network-name local --connection-name local --rpc-url {} --wallet-url http://127.0.0.1/ --explorer-transaction-url http://127.0.0.1:6666/", near_rpc); + println!("\nAfter run the nodes, please call the following command to init contract: "); + let args = json!({ + "threshold": cfg.threshold, + "candidates": candidates + }) + .to_string(); + let sk = SecretKey::from_seed(KeyType::ED25519, "testificate"); + + println!("near contract call-function as-transaction {} init json-args '{}' prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' sign-as {} network-config local sign-with-plaintext-private-key --signer-public-key {} --signer-private-key {} send", + ctx.mpc_contract.id(), + args, + ctx.mpc_contract.id(), + sk.public_key(), + sk + ); + println!(); + + Ok(ctx) +} + pub async fn host(cfg: MultichainConfig, docker_client: &DockerClient) -> anyhow::Result { let ctx = setup(docker_client).await?; @@ -351,6 +408,17 @@ pub async fn run(cfg: MultichainConfig, docker_client: &DockerClient) -> anyhow: return host(cfg, docker_client).await; } +pub async fn dry_run( + cfg: MultichainConfig, + docker_client: &DockerClient, +) -> anyhow::Result { + #[cfg(feature = "docker-test")] + unimplemented!("dry_run only works with native node"); + + #[cfg(not(feature = "docker-test"))] + return dry_host(cfg, docker_client).await; +} + async fn fetch_from_validator( docker_client: &containers::DockerClient, container: &Container<'_, GenericImage>, diff --git a/integration-tests/chain-signatures/src/local.rs b/integration-tests/chain-signatures/src/local.rs index e9d26b2d..a3051b6b 100644 --- a/integration-tests/chain-signatures/src/local.rs +++ b/integration-tests/chain-signatures/src/local.rs @@ -1,10 +1,13 @@ use crate::{execute, utils, MultichainConfig}; use crate::containers::LakeIndexer; +use crate::execute::executable; +use anyhow::Context; use async_process::Child; use mpc_keys::hpke; use mpc_node::config::OverrideConfig; use near_workspaces::Account; +use shell_escape::escape; pub struct Node { pub address: String, @@ -33,6 +36,71 @@ pub struct NodeConfig { } impl Node { + pub async fn dry_run( + ctx: &super::Context<'_>, + account: &Account, + cfg: &MultichainConfig, + ) -> anyhow::Result { + let account_id = account.id(); + let account_sk = account.secret_key(); + let web_port = utils::pick_unused_port().await?; + let (cipher_sk, cipher_pk) = hpke::generate(); + let sign_sk = + near_crypto::SecretKey::from_seed(near_crypto::KeyType::ED25519, "integration-test"); + + let indexer_options = mpc_node::indexer::Options { + s3_bucket: ctx.localstack.s3_bucket.clone(), + s3_region: ctx.localstack.s3_region.clone(), + s3_url: Some(ctx.localstack.s3_host_address.clone()), + start_block_height: 0, + running_threshold: 120, + behind_threshold: 120, + }; + let near_rpc = ctx.lake_indexer.rpc_host_address.clone(); + let mpc_contract_id = ctx.mpc_contract.id().clone(); + let cli = mpc_node::cli::Cli::Start { + near_rpc: near_rpc.clone(), + mpc_contract_id: mpc_contract_id.clone(), + account_id: account_id.clone(), + account_sk: account_sk.to_string().parse()?, + web_port, + cipher_pk: hex::encode(cipher_pk.to_bytes()), + cipher_sk: hex::encode(cipher_sk.to_bytes()), + sign_sk: Some(sign_sk.clone()), + indexer_options, + my_address: None, + storage_options: ctx.storage_options.clone(), + override_config: Some(OverrideConfig::new(serde_json::to_value( + cfg.protocol.clone(), + )?)), + client_header_referer: None, + }; + + let cmd = executable(ctx.release, crate::execute::PACKAGE_MULTICHAIN) + .with_context(|| "could not find target dir for mpc-node")?; + let args = cli.into_str_args(); + let escaped_args: Vec<_> = args + .iter() + .map(|arg| escape(arg.clone().into()).to_string()) + .collect(); + println!( + "\nCommand to run node {}:\n {} {}", + account_id, + cmd.to_str().unwrap(), + escaped_args.join(" ") + ); + let node_config = NodeConfig { + web_port, + account: account.clone(), + cipher_pk, + cipher_sk, + sign_sk, + cfg: cfg.clone(), + near_rpc, + }; + Ok(node_config) + } + pub async fn run( ctx: &super::Context<'_>, cfg: &MultichainConfig, diff --git a/integration-tests/chain-signatures/src/main.rs b/integration-tests/chain-signatures/src/main.rs index 3a81d548..5d8f919a 100644 --- a/integration-tests/chain-signatures/src/main.rs +++ b/integration-tests/chain-signatures/src/main.rs @@ -1,6 +1,6 @@ use clap::Parser; use integration_tests_chain_signatures::containers::DockerClient; -use integration_tests_chain_signatures::{run, setup, utils, MultichainConfig}; +use integration_tests_chain_signatures::{dry_run, run, utils, MultichainConfig}; use tokio::signal; use tracing_subscriber::EnvFilter; @@ -69,8 +69,14 @@ async fn main() -> anyhow::Result<()> { println!("Clean up finished"); } Cli::DepServices => { - println!("Settting up dependency services"); - let _ctx = setup(&docker_client).await?; + println!("Setting up dependency services"); + let config = MultichainConfig::default(); + let _ctx = dry_run(config.clone(), &docker_client).await?; + + println!("Press Ctrl-C to stop dependency services"); + signal::ctrl_c().await.expect("Failed to listen for event"); + println!("Received Ctrl-C"); + println!("Stopped dependency services"); } }