cargo_tangle/command/deploy/
tangle.rs

1use 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
19/// Deploy a blueprint to the Tangle
20///
21/// # Errors
22///
23/// Returns a `color_eyre::Report` if an error occurs during deployment.
24pub 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        // Start Local Tangle Node
44        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        // Create test keystore for Alice (to act as the user)
52        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        // Create deploy keystore for Bob (for deployment)
58        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        // Set up Alice's environment for MBSM deployment
64        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        // Set up Alice's signers for MBSM deployment
80        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        // Set up Bob's signers for blueprint deployment
96        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        // Check if MBSM is already deployed
112        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        // Get Alice's account ID for display
172        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        // Wait for Ctrl+C to keep the testnet running
251        signal::ctrl_c().await?;
252        println!("{}", style("\nShutting down devnet...").yellow());
253
254        // Clean up keystores
255        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}