cargo_tangle/command/debug/
spawn.rs

1mod container;
2mod native;
3#[cfg(feature = "vm-debug")]
4mod vm;
5
6use crate::command::deploy::mbsm::deploy_mbsm_if_needed;
7use crate::command::deploy::tangle::deploy_tangle;
8use crate::command::register::register;
9use blueprint_chain_setup::tangle::testnet::SubstrateNode;
10use blueprint_clients::tangle::client::TangleClient;
11use blueprint_core::{error, info};
12use blueprint_crypto::sp_core::{SpEcdsa, SpSr25519};
13use blueprint_crypto::tangle_pair_signer::TanglePairSigner;
14use blueprint_keystore::backends::Backend;
15use blueprint_keystore::{Keystore, KeystoreConfig};
16use blueprint_manager::config::{AuthProxyOpts, BlueprintManagerConfig, BlueprintManagerContext};
17use blueprint_manager::executor::run_auth_proxy;
18use blueprint_manager::rt::ResourceLimits;
19use blueprint_manager::sources::{BlueprintArgs, BlueprintEnvVars};
20use blueprint_runner::config::{BlueprintEnvironment, Protocol, SupportedChains};
21use clap::ValueEnum;
22use std::future;
23use std::io;
24use std::path::{Path, PathBuf};
25use std::pin::Pin;
26use tokio::task::{JoinError, JoinHandle};
27use url::Url;
28
29async fn setup_tangle_node(
30    tmp_path: &Path,
31    package: Option<String>,
32    manifest_path: &Path,
33    keystore_uri: String,
34    mut http_rpc_url: Option<Url>,
35    mut ws_rpc_url: Option<Url>,
36) -> color_eyre::Result<(Option<SubstrateNode>, Url, Url)> {
37    let mut node = None;
38    if http_rpc_url.is_none() || ws_rpc_url.is_none() {
39        info!("Spawning Tangle node");
40
41        match blueprint_chain_setup::tangle::run(blueprint_chain_setup::tangle::NodeConfig::new(
42            false,
43        ))
44        .await
45        {
46            Ok(tangle) => {
47                let http = format!("http://127.0.0.1:{}", tangle.ws_port());
48                let ws = format!("ws://127.0.0.1:{}", tangle.ws_port());
49
50                info!("Tangle node running on {http} / {ws}");
51
52                http_rpc_url = Some(Url::parse(&http)?);
53                ws_rpc_url = Some(Url::parse(&ws)?);
54
55                node = Some(tangle);
56            }
57            Err(e) => {
58                error!("Failed to setup local Tangle node: {e}");
59                return Err(e.into());
60            }
61        }
62    }
63
64    let http = http_rpc_url.unwrap();
65    let ws = ws_rpc_url.unwrap();
66
67    let alice_keystore_config = KeystoreConfig::new().fs_root(tmp_path.join("keystore"));
68    let alice_keystore = Keystore::new(alice_keystore_config)?;
69
70    let alice_sr25519_public = alice_keystore.first_local::<SpSr25519>()?;
71    let alice_sr25519_pair = alice_keystore.get_secret::<SpSr25519>(&alice_sr25519_public)?;
72    let alice_sr25519_signer = TanglePairSigner::new(alice_sr25519_pair.0);
73
74    let alice_ecdsa_public = alice_keystore.first_local::<SpEcdsa>()?;
75    let alice_ecdsa_pair = alice_keystore.get_secret::<SpEcdsa>(&alice_ecdsa_public)?;
76    let alice_ecdsa_signer = TanglePairSigner::new(alice_ecdsa_pair.0);
77    let alice_alloy_key = alice_ecdsa_signer
78        .alloy_key()
79        .map_err(|e| color_eyre::Report::msg(format!("Failed to get Alice's Alloy key: {}", e)))?;
80
81    let mut env = BlueprintEnvironment::default();
82    env.http_rpc_endpoint = http.clone();
83    env.ws_rpc_endpoint = ws.clone();
84    let alice_client = TangleClient::with_keystore(env, alice_keystore).await?;
85
86    deploy_mbsm_if_needed(
87        ws.clone(),
88        &alice_client,
89        &alice_sr25519_signer,
90        alice_alloy_key.clone(),
91    )
92    .await?;
93
94    Box::pin(deploy_tangle(
95        http.to_string(),
96        ws.to_string(),
97        package,
98        false,
99        Some(PathBuf::from(keystore_uri.clone())),
100        manifest_path.to_path_buf(),
101    ))
102    .await?;
103    register(ws.to_string(), 0, keystore_uri, "").await?;
104
105    Ok((node, http, ws))
106}
107
108pub struct PtyIo {
109    pub stdin_to_pty: JoinHandle<io::Result<()>>,
110    pub pty_to_stdout: JoinHandle<io::Result<()>>,
111}
112
113#[derive(ValueEnum, Debug, Copy, Clone)]
114pub enum ServiceSpawnMethod {
115    Native,
116    #[cfg(feature = "vm-debug")]
117    Vm,
118    Container,
119}
120
121/// Spawns a Tangle testnet and virtual machine for the given blueprint
122///
123/// # Errors
124///
125/// * Unable to spawn/connect to the Tangle node
126///
127/// See also:
128///
129/// * [`deploy_mbsm_if_needed()`]
130/// * [`NetworkManager::new()`]
131/// * [`run_auth_proxy()`]
132/// * [`register()`]
133/// * [`Service::new()`]
134/// * [`Service::start()`]
135#[allow(
136    clippy::too_many_arguments,
137    clippy::missing_panics_doc,
138    clippy::large_futures
139)]
140pub async fn execute(
141    http_rpc_url: Option<Url>,
142    ws_rpc_url: Option<Url>,
143    manifest_path: PathBuf,
144    package: Option<String>,
145    #[allow(unused_variables)] id: u32,
146    service_name: String,
147    binary: Option<PathBuf>,
148    image: Option<String>,
149    protocol: Protocol,
150    method: ServiceSpawnMethod,
151    #[cfg(feature = "vm-debug")] _verify_network_connection: bool,
152) -> color_eyre::Result<()> {
153    let mut manager_config = BlueprintManagerConfig::default();
154
155    let tmp = tempfile::tempdir()?;
156    manager_config.paths.data_dir = tmp.path().join("data");
157    manager_config.paths.cache_dir = tmp.path().join("cache");
158    manager_config.paths.runtime_dir = tmp.path().join("runtime");
159    manager_config.paths.keystore_uri = tmp.path().join("keystore").to_string_lossy().into();
160
161    let ctx = BlueprintManagerContext::new(manager_config).await?;
162
163    blueprint_testing_utils::tangle::keys::inject_tangle_key(ctx.keystore_uri(), "//Alice")?;
164
165    let (_node, http, ws) = setup_tangle_node(
166        tmp.path(),
167        package,
168        &manifest_path,
169        ctx.keystore_uri().to_string(),
170        http_rpc_url,
171        ws_rpc_url,
172    )
173    .await?;
174
175    let (db, auth_proxy_task) =
176        run_auth_proxy(ctx.data_dir().to_path_buf(), AuthProxyOpts::default()).await?;
177    ctx.set_db(db).await;
178
179    let args = BlueprintArgs::new(&ctx);
180    let env = BlueprintEnvVars {
181        http_rpc_endpoint: http,
182        ws_rpc_endpoint: ws,
183        // TODO
184        kms_endpoint: "https://127.0.0.1:19821".parse().unwrap(),
185        keystore_uri: ctx.keystore_uri().to_string(),
186        data_dir: ctx.data_dir().to_path_buf(),
187        blueprint_id: 0,
188        service_id: 0,
189        protocol,
190        chain: Some(SupportedChains::LocalTestnet),
191        bootnodes: String::new(),
192        registration_mode: false,
193        // Set later
194        bridge_socket_path: None,
195    };
196
197    // TODO: Allow setting resource limits on the CLI?
198    let limits = ResourceLimits::default();
199
200    let (mut service, pty_io) = match method {
201        ServiceSpawnMethod::Native => {
202            let service =
203                native::setup_native(&ctx, limits, &service_name, binary.unwrap(), env, args)
204                    .await?;
205            (service, None)
206        }
207        #[cfg(feature = "vm-debug")]
208        ServiceSpawnMethod::Vm => {
209            let (service, pty) =
210                vm::setup_with_vm(&ctx, limits, &service_name, id, binary.unwrap(), env, args)
211                    .await?;
212
213            (service, Some(pty))
214        }
215        ServiceSpawnMethod::Container => {
216            let service = container::setup_with_container(
217                &ctx,
218                limits,
219                &service_name,
220                image.unwrap(),
221                env,
222                args,
223            )
224            .await?;
225            (service, None)
226        }
227    };
228
229    // TODO: Check is_alive
230    let _is_alive = Box::pin(service.start().await?.unwrap());
231
232    let stdin_task: Pin<Box<dyn Future<Output = Result<io::Result<()>, JoinError>>>>;
233    let stdout_task: Pin<Box<dyn Future<Output = Result<io::Result<()>, JoinError>>>>;
234    if let Some(PtyIo {
235        stdin_to_pty,
236        pty_to_stdout,
237    }) = pty_io
238    {
239        stdin_task = Box::pin(stdin_to_pty);
240        stdout_task = Box::pin(pty_to_stdout);
241    } else {
242        stdin_task = Box::pin(future::pending());
243        stdout_task = Box::pin(future::pending());
244    }
245
246    tokio::select! {
247        _ = tokio::signal::ctrl_c() => {}
248        _ = auth_proxy_task => {
249            error!("Auth proxy shutdown");
250        },
251        res = stdin_task => {
252            error!("stdin task died: {res:?}");
253        },
254        res = stdout_task => {
255            error!("stdout task died: {res:?}");
256        },
257    }
258
259    let shutdown_res = service.shutdown().await;
260
261    #[cfg(feature = "vm-debug")]
262    if method == ServiceSpawnMethod::Vm {
263        vm::vm_shutdown(&ctx.vm.network_interface).await?;
264    }
265
266    shutdown_res.map_err(Into::into)
267}