cargo_tangle/command/deploy/
tangle.rs1use std::path::PathBuf;
2
3use blueprint_chain_setup::tangle::deploy::{Opts, deploy_to_tangle};
4use blueprint_chain_setup::tangle::transactions;
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 println!(
34 "{}",
35 style("Starting local Tangle testnet...").cyan().bold()
36 );
37
38 let temp_dir = TempDir::new()?;
39 let temp_path = temp_dir.path().to_path_buf();
40 let deploy_dir = temp_path.join("deploy_dir");
41 fs::create_dir_all(&deploy_dir).await?;
42
43 let node = blueprint_chain_setup::tangle::run(
45 blueprint_chain_setup::tangle::NodeConfig::new(false).with_log_target("evm", "trace"),
46 )
47 .await?;
48 let http_endpoint = Url::parse(&format!("http://127.0.0.1:{}", node.ws_port()))?;
49 let ws_endpoint = Url::parse(&format!("ws://127.0.0.1:{}", node.ws_port()))?;
50
51 let test_keystore_path = PathBuf::from("./test-keystore");
53 fs::create_dir_all(&test_keystore_path).await?;
54 inject_tangle_key(&test_keystore_path, "//Alice")
55 .map_err(|e| color_eyre::Report::msg(format!("Failed to inject Alice's key: {}", e)))?;
56
57 let deploy_keystore_path = PathBuf::from("./deploy-keystore");
59 fs::create_dir_all(&deploy_keystore_path).await?;
60 inject_tangle_key(&deploy_keystore_path, "//Bob")
61 .map_err(|e| color_eyre::Report::msg(format!("Failed to inject Bob's key: {}", e)))?;
62
63 let alice_env = generate_env_from_node_id(
65 ENDOWED_TEST_NAMES[0],
66 http_endpoint.clone(),
67 ws_endpoint.clone(),
68 deploy_dir.as_path(),
69 )
70 .await?;
71
72 let alice_client = alice_env.tangle_client().await?;
73
74 println!(
75 "{}",
76 style("Checking if MBSM needs to be deployed...").cyan()
77 );
78
79 let alice_keystore_config =
81 KeystoreConfig::new().fs_root(test_keystore_path.to_string_lossy().to_string());
82 let alice_keystore = Keystore::new(alice_keystore_config)?;
83
84 let alice_sr25519_public = alice_keystore.first_local::<SpSr25519>()?;
85 let alice_sr25519_pair = alice_keystore.get_secret::<SpSr25519>(&alice_sr25519_public)?;
86 let alice_sr25519_signer = TanglePairSigner::new(alice_sr25519_pair.0);
87
88 let alice_ecdsa_public = alice_keystore.first_local::<SpEcdsa>()?;
89 let alice_ecdsa_pair = alice_keystore.get_secret::<SpEcdsa>(&alice_ecdsa_public)?;
90 let alice_ecdsa_signer = TanglePairSigner::new(alice_ecdsa_pair.0);
91 let alice_alloy_key = alice_ecdsa_signer.alloy_key().map_err(|e| {
92 color_eyre::Report::msg(format!("Failed to get Alice's Alloy key: {}", e))
93 })?;
94
95 let deploy_keystore_uri = deploy_keystore_path.to_string_lossy().to_string();
97 let bob_keystore_config = KeystoreConfig::new().fs_root(deploy_keystore_uri.clone());
98 let bob_keystore = Keystore::new(bob_keystore_config)?;
99
100 let bob_sr25519_public = bob_keystore.first_local::<SpSr25519>()?;
101 let bob_sr25519_pair = bob_keystore.get_secret::<SpSr25519>(&bob_sr25519_public)?;
102 let bob_sr25519_signer = TanglePairSigner::new(bob_sr25519_pair.0);
103
104 let bob_ecdsa_public = bob_keystore.first_local::<SpEcdsa>()?;
105 let bob_ecdsa_pair = bob_keystore.get_secret::<SpEcdsa>(&bob_ecdsa_public)?;
106 let bob_ecdsa_signer = TanglePairSigner::new(bob_ecdsa_pair.0);
107 let bob_alloy_key = bob_ecdsa_signer.alloy_key().map_err(|e| {
108 color_eyre::Report::msg(format!("Failed to get Bob's Alloy key: {}", e))
109 })?;
110
111 let latest_revision = transactions::get_latest_mbsm_revision(&alice_client)
113 .await
114 .map_err(|e| {
115 color_eyre::Report::msg(format!("Failed to get latest MBSM revision: {}", e))
116 })?;
117
118 if let Some((rev, addr)) = latest_revision {
119 println!(
120 "{}",
121 style(format!(
122 "MBSM is already deployed at revision #{} at address {}",
123 rev, addr
124 ))
125 .green()
126 );
127 } else {
128 println!(
129 "{}",
130 style("MBSM is not deployed, deploying now with Alice's account...").cyan()
131 );
132
133 let bytecode = tnt_core_bytecode::bytecode::MASTER_BLUEPRINT_SERVICE_MANAGER;
134 transactions::deploy_new_mbsm_revision(
135 ws_endpoint.as_str(),
136 &alice_client,
137 &alice_sr25519_signer,
138 alice_alloy_key.clone(),
139 bytecode,
140 alloy_primitives::address!("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"),
141 )
142 .await
143 .map_err(|e| color_eyre::Report::msg(format!("Failed to deploy MBSM: {}", e)))?;
144
145 println!("{}", style("MBSM deployed successfully").green());
146 }
147
148 println!(
149 "{}",
150 style(format!(
151 "Tangle node running at HTTP: {}, WS: {}",
152 http_endpoint, ws_endpoint
153 ))
154 .green()
155 );
156
157 println!(
158 "{}",
159 style("Deploying blueprint to local Tangle testnet using Bob's account...").cyan()
160 );
161 let blueprint_id = deploy_to_tangle(Opts {
162 pkg_name: package,
163 http_rpc_url: http_endpoint.to_string(),
164 ws_rpc_url: ws_endpoint.to_string(),
165 manifest_path,
166 signer: Some(bob_sr25519_signer),
167 signer_evm: Some(bob_alloy_key),
168 })
169 .await?;
170
171 let alice_account_id = alice_sr25519_signer.account_id();
173
174 println!("\n{}", style("Local Tangle Testnet Active").green().bold());
175 println!(
176 "{}",
177 style(format!("Blueprint ID: {}", blueprint_id)).green()
178 );
179 println!(
180 "{}",
181 style(format!("Alice Account ID: {}", alice_account_id)).green()
182 );
183 println!(
184 "\n{}",
185 style("If your blueprint was just generated from the template, you can follow the demo below:").cyan().bold()
186 );
187 println!(
188 "{}",
189 style("1. Open a new terminal window, leaving this one running").dim()
190 );
191 println!(
192 "\n{}",
193 style("2. Register to your blueprint with Alice's account:").dim()
194 );
195 println!(
196 " {}",
197 style(format!(
198 "cargo tangle blueprint register --blueprint-id {} --keystore-uri ./test-keystore",
199 blueprint_id
200 ))
201 .yellow()
202 );
203 println!("\n{}", style("3. Request service with Bob's account (we will use Alice's account as the target operator):").dim());
204 println!(" {}", style(format!("cargo tangle blueprint request-service --blueprint-id {} --target-operators {} --value 0 --keystore-uri ./deploy-keystore", blueprint_id, alice_account_id)).yellow());
205 println!("\n{}", style("4. List all service requests:").dim());
206 println!(
207 " {}",
208 style("cargo tangle blueprint ls".to_string()).yellow()
209 );
210 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());
211 println!(
212 " {}",
213 style("cargo tangle blueprint accept --request-id 0 --keystore-uri ./test-keystore")
214 .yellow()
215 );
216 println!("\n{}", style("6. Run the blueprint:").dim());
217 println!(
218 " {}",
219 style(
220 "cargo tangle blueprint run --protocol tangle --keystore-path ./test-keystore"
221 .to_string()
222 )
223 .yellow()
224 );
225 println!(
226 "\n{}",
227 style("7. Open another terminal window, leaving this one running").dim()
228 );
229 println!(
230 "\n{}",
231 style("8. Submit a job for the Running Blueprint to process").dim()
232 );
233 println!(" {}", style(format!("cargo tangle blueprint submit --job 0 --blueprint-id {} --service-id 0 --watcher --keystore-uri ./deploy-keystore", blueprint_id)).yellow());
234
235 println!(
236 "\n{}",
237 style("The local Tangle testnet will continue running for testing purposes.")
238 .cyan()
239 .bold()
240 );
241 println!(
242 "{}",
243 style("It will remain active so you can interact with your deployed blueprint.").cyan()
244 );
245 println!(
246 "{}",
247 style("Press Ctrl+C to stop the testnet when you're done testing...").dim()
248 );
249
250 signal::ctrl_c().await?;
252 println!("{}", style("\nShutting down devnet...").yellow());
253
254 println!("{}", style("Cleaning up keystores...").dim());
256 if let Err(e) = fs::remove_dir_all(&test_keystore_path).await {
257 println!("Warning: Failed to remove test keystore: {}", e);
258 }
259 if let Err(e) = fs::remove_dir_all(&deploy_keystore_path).await {
260 println!("Warning: Failed to remove deploy keystore: {}", e);
261 }
262 } else {
263 let keystore_path = if let Some(keystore_uri) = keystore_path {
264 Some(keystore_uri)
265 } else if PathBuf::from("./keystore").exists() {
266 Some(PathBuf::from("./keystore"))
267 } else {
268 None
269 };
270
271 let (signer, signer_evm) = if let Some(keystore_uri) = keystore_path {
272 let keystore_config = KeystoreConfig::new().fs_root(keystore_uri.clone());
273 let keystore = Keystore::new(keystore_config)?;
274
275 let sr25519_public = keystore.first_local::<SpSr25519>()?;
276 let sr25519_pair = keystore.get_secret::<SpSr25519>(&sr25519_public)?;
277 let sr25519_signer = TanglePairSigner::new(sr25519_pair.0);
278
279 let ecdsa_public = keystore.first_local::<SpEcdsa>()?;
280 let ecdsa_pair = keystore.get_secret::<SpEcdsa>(&ecdsa_public)?;
281 let ecdsa_signer = TanglePairSigner::new(ecdsa_pair.0);
282 let alloy_key = ecdsa_signer
283 .alloy_key()
284 .map_err(|e| color_eyre::Report::msg(format!("Failed to get Alloy key: {}", e)))?;
285 (Some(sr25519_signer), Some(alloy_key))
286 } else {
287 (None, None)
288 };
289
290 let _ = deploy_to_tangle(Opts {
291 pkg_name: package,
292 http_rpc_url,
293 ws_rpc_url,
294 manifest_path,
295 signer,
296 signer_evm,
297 })
298 .await?;
299 }
300 Ok(())
301}