cargo_tangle/command/debug/
spawn.rs1#[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#[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 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 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}