cargo_tangle/command/debug/
spawn.rs

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