cargo_tangle/command/deploy/
tangle.rs1use std::path::PathBuf;
2
3use crate::command::deploy::mbsm::deploy_mbsm_if_needed;
4use blueprint_chain_setup::tangle::deploy::{Opts, deploy_to_tangle};
5use blueprint_contexts::tangle::TangleClientContext;
6use blueprint_crypto::sp_core::{SpEcdsa, SpSr25519};
7use blueprint_crypto::tangle_pair_signer::TanglePairSigner;
8use blueprint_keystore::backends::Backend;
9use blueprint_keystore::{Keystore, KeystoreConfig};
10use blueprint_testing_utils::tangle::harness::{ENDOWED_TEST_NAMES, generate_env_from_node_id};
11use blueprint_testing_utils::tangle::keys::inject_tangle_key;
12use dialoguer::console::style;
13use tangle_subxt::subxt::tx::Signer;
14use tempfile::TempDir;
15use tokio::fs;
16use tokio::signal;
17use url::Url;
18
19pub async fn deploy_tangle(
25 http_rpc_url: String,
26 ws_rpc_url: String,
27 package: Option<String>,
28 devnet: bool,
29 keystore_path: Option<PathBuf>,
30 manifest_path: PathBuf,
31) -> Result<(), color_eyre::Report> {
32 if !devnet {
33 let keystore_path = if let Some(keystore_uri) = keystore_path {
34 Some(keystore_uri)
35 } else if PathBuf::from("./keystore").exists() {
36 Some(PathBuf::from("./keystore"))
37 } else {
38 None
39 };
40
41 let (signer, signer_evm) = if let Some(keystore_uri) = keystore_path {
42 let keystore_config = KeystoreConfig::new().fs_root(keystore_uri.clone());
43 let keystore = Keystore::new(keystore_config)?;
44
45 let sr25519_public = keystore.first_local::<SpSr25519>()?;
46 let sr25519_pair = keystore.get_secret::<SpSr25519>(&sr25519_public)?;
47 let sr25519_signer = TanglePairSigner::new(sr25519_pair.0);
48
49 let ecdsa_public = keystore.first_local::<SpEcdsa>()?;
50 let ecdsa_pair = keystore.get_secret::<SpEcdsa>(&ecdsa_public)?;
51 let ecdsa_signer = TanglePairSigner::new(ecdsa_pair.0);
52 let alloy_key = ecdsa_signer
53 .alloy_key()
54 .map_err(|e| color_eyre::Report::msg(format!("Failed to get Alloy key: {}", e)))?;
55 (Some(sr25519_signer), Some(alloy_key))
56 } else {
57 (None, None)
58 };
59
60 let _ = deploy_to_tangle(Opts {
61 pkg_name: package,
62 http_rpc_url,
63 ws_rpc_url,
64 manifest_path,
65 signer,
66 signer_evm,
67 })
68 .await?;
69
70 return Ok(());
71 }
72
73 println!(
74 "{}",
75 style("Starting local Tangle testnet...").cyan().bold()
76 );
77
78 let temp_dir = TempDir::new()?;
79 let temp_path = temp_dir.path().to_path_buf();
80 let deploy_dir = temp_path.join("deploy_dir");
81 fs::create_dir_all(&deploy_dir).await?;
82
83 let node = blueprint_chain_setup::tangle::run(
85 blueprint_chain_setup::tangle::NodeConfig::new(false).with_log_target("evm", "trace"),
86 )
87 .await?;
88 let http_endpoint = Url::parse(&format!("http://127.0.0.1:{}", node.ws_port()))?;
89 let ws_endpoint = Url::parse(&format!("ws://127.0.0.1:{}", node.ws_port()))?;
90
91 let test_keystore_path = PathBuf::from("./test-keystore");
93 fs::create_dir_all(&test_keystore_path).await?;
94 inject_tangle_key(&test_keystore_path, "//Alice")
95 .map_err(|e| color_eyre::Report::msg(format!("Failed to inject Alice's key: {}", e)))?;
96
97 let deploy_keystore_path = PathBuf::from("./deploy-keystore");
99 fs::create_dir_all(&deploy_keystore_path).await?;
100 inject_tangle_key(&deploy_keystore_path, "//Bob")
101 .map_err(|e| color_eyre::Report::msg(format!("Failed to inject Bob's key: {}", e)))?;
102
103 let alice_env = generate_env_from_node_id(
105 ENDOWED_TEST_NAMES[0],
106 http_endpoint.clone(),
107 ws_endpoint.clone(),
108 deploy_dir.as_path(),
109 )
110 .await?;
111
112 let alice_client = alice_env.tangle_client().await?;
113
114 println!(
115 "{}",
116 style("Checking if MBSM needs to be deployed...").cyan()
117 );
118
119 let alice_keystore_config =
121 KeystoreConfig::new().fs_root(test_keystore_path.to_string_lossy().to_string());
122 let alice_keystore = Keystore::new(alice_keystore_config)?;
123
124 let alice_sr25519_public = alice_keystore.first_local::<SpSr25519>()?;
125 let alice_sr25519_pair = alice_keystore.get_secret::<SpSr25519>(&alice_sr25519_public)?;
126 let alice_sr25519_signer = TanglePairSigner::new(alice_sr25519_pair.0);
127
128 let alice_ecdsa_public = alice_keystore.first_local::<SpEcdsa>()?;
129 let alice_ecdsa_pair = alice_keystore.get_secret::<SpEcdsa>(&alice_ecdsa_public)?;
130 let alice_ecdsa_signer = TanglePairSigner::new(alice_ecdsa_pair.0);
131 let alice_alloy_key = alice_ecdsa_signer
132 .alloy_key()
133 .map_err(|e| color_eyre::Report::msg(format!("Failed to get Alice's Alloy key: {}", e)))?;
134
135 let deploy_keystore_uri = deploy_keystore_path.to_string_lossy().to_string();
137 let bob_keystore_config = KeystoreConfig::new().fs_root(deploy_keystore_uri.clone());
138 let bob_keystore = Keystore::new(bob_keystore_config)?;
139
140 let bob_sr25519_public = bob_keystore.first_local::<SpSr25519>()?;
141 let bob_sr25519_pair = bob_keystore.get_secret::<SpSr25519>(&bob_sr25519_public)?;
142 let bob_sr25519_signer = TanglePairSigner::new(bob_sr25519_pair.0);
143
144 let bob_ecdsa_public = bob_keystore.first_local::<SpEcdsa>()?;
145 let bob_ecdsa_pair = bob_keystore.get_secret::<SpEcdsa>(&bob_ecdsa_public)?;
146 let bob_ecdsa_signer = TanglePairSigner::new(bob_ecdsa_pair.0);
147 let bob_alloy_key = bob_ecdsa_signer
148 .alloy_key()
149 .map_err(|e| color_eyre::Report::msg(format!("Failed to get Bob's Alloy key: {}", e)))?;
150
151 deploy_mbsm_if_needed(
152 ws_endpoint.clone(),
153 &alice_client,
154 &alice_sr25519_signer,
155 alice_alloy_key.clone(),
156 )
157 .await?;
158
159 println!(
160 "{}",
161 style(format!(
162 "Tangle node running at HTTP: {}, WS: {}",
163 http_endpoint, ws_endpoint
164 ))
165 .green()
166 );
167
168 println!(
169 "{}",
170 style("Deploying blueprint to local Tangle testnet using Bob's account...").cyan()
171 );
172 let blueprint_id = deploy_to_tangle(Opts {
173 pkg_name: package,
174 http_rpc_url: http_endpoint.to_string(),
175 ws_rpc_url: ws_endpoint.to_string(),
176 manifest_path,
177 signer: Some(bob_sr25519_signer),
178 signer_evm: Some(bob_alloy_key),
179 })
180 .await?;
181
182 let alice_account_id = alice_sr25519_signer.account_id();
184
185 println!("\n{}", style("Local Tangle Testnet Active").green().bold());
186 println!(
187 "{}",
188 style(format!("Blueprint ID: {}", blueprint_id)).green()
189 );
190 println!(
191 "{}",
192 style(format!("Alice Account ID: {}", alice_account_id)).green()
193 );
194 println!(
195 "\n{}",
196 style(
197 "If your blueprint was just generated from the template, you can follow the demo below:"
198 )
199 .cyan()
200 .bold()
201 );
202 println!(
203 "{}",
204 style("1. Open a new terminal window, leaving this one running").dim()
205 );
206 println!(
207 "\n{}",
208 style("2. Register to your blueprint with Alice's account:").dim()
209 );
210 println!(
211 " {}",
212 style(format!(
213 "cargo tangle blueprint register --blueprint-id {} --keystore-uri ./test-keystore",
214 blueprint_id
215 ))
216 .yellow()
217 );
218 println!("\n{}", style("3. Request service with Bob's account (we will use Alice's account as the target operator):").dim());
219 println!(" {}", style(format!("cargo tangle blueprint request-service --blueprint-id {} --target-operators {} --value 0 --keystore-uri ./deploy-keystore", blueprint_id, alice_account_id)).yellow());
220 println!("\n{}", style("4. List all service requests:").dim());
221 println!(
222 " {}",
223 style("cargo tangle blueprint ls".to_string()).yellow()
224 );
225 println!("\n{}", style("5. Accept the service request (request ID is 0 if you are following this demo, otherwise it will be from the request you want to accept):").dim());
226 println!(
227 " {}",
228 style("cargo tangle blueprint accept --request-id 0 --keystore-uri ./test-keystore")
229 .yellow()
230 );
231 println!("\n{}", style("6. Run the blueprint:").dim());
232 println!(
233 " {}",
234 style(
235 "cargo tangle blueprint run --protocol tangle --keystore-path ./test-keystore"
236 .to_string()
237 )
238 .yellow()
239 );
240 println!(
241 "\n{}",
242 style("7. Open another terminal window, leaving this one running").dim()
243 );
244 println!(
245 "\n{}",
246 style("8. Submit a job for the Running Blueprint to process").dim()
247 );
248 println!(" {}", style(format!("cargo tangle blueprint submit --job 0 --blueprint-id {} --service-id 0 --watcher --keystore-uri ./deploy-keystore", blueprint_id)).yellow());
249
250 println!(
251 "\n{}",
252 style("The local Tangle testnet will continue running for testing purposes.")
253 .cyan()
254 .bold()
255 );
256 println!(
257 "{}",
258 style("It will remain active so you can interact with your deployed blueprint.").cyan()
259 );
260 println!(
261 "{}",
262 style("Press Ctrl+C to stop the testnet when you're done testing...").dim()
263 );
264
265 signal::ctrl_c().await?;
267 println!("{}", style("\nShutting down devnet...").yellow());
268
269 println!("{}", style("Cleaning up keystores...").dim());
271 if let Err(e) = fs::remove_dir_all(&test_keystore_path).await {
272 println!("Warning: Failed to remove test keystore: {}", e);
273 }
274 if let Err(e) = fs::remove_dir_all(&deploy_keystore_path).await {
275 println!("Warning: Failed to remove deploy keystore: {}", e);
276 }
277
278 Ok(())
279}