cargo_tangle/command/deploy/
tangle.rs

1use 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
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        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    // Start Local Tangle Node
84    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    // Create test keystore for Alice (to act as the user)
92    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    // Create deploy keystore for Bob (for deployment)
98    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    // Set up Alice's environment for MBSM deployment
104    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    // Set up Alice's signers for MBSM deployment
120    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    // Set up Bob's signers for blueprint deployment
136    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    // Get Alice's account ID for display
183    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    // Wait for Ctrl+C to keep the testnet running
266    signal::ctrl_c().await?;
267    println!("{}", style("\nShutting down devnet...").yellow());
268
269    // Clean up keystores
270    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}