cargo_tangle/command/debug/
spawn.rs1mod 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#[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 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 bridge_socket_path: None,
195 };
196
197 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 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}