use crate::error::Error;
use crate::testnet::{SubstrateNode, TANGLE_NODE_ENV};
use blueprint_std::fs;
use blueprint_std::io::Write;
use blueprint_std::path::PathBuf;
use tangle_subxt::subxt::utils::AccountId32;
pub mod deploy;
pub mod error;
pub mod foundry;
pub mod testnet;
pub mod transactions;
pub use testnet::NodeConfig;
pub type InputValue = tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field<AccountId32>;
pub type OutputValue = tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::Field<AccountId32>;
const TANGLE_RELEASE_MAC: &str = "https://github.com/tangle-network/tangle/releases/download/v1.4.3/tangle-testnet-manual-seal-testnet-darwin-amd64";
const TANGLE_RELEASE_LINUX: &str = "https://github.com/tangle-network/tangle/releases/download/v1.4.3/tangle-testnet-manual-seal-testnet-linux-amd64";
pub async fn download_tangle_binary() -> Result<PathBuf, Box<dyn std::error::Error>> {
let download_url = if cfg!(target_os = "macos") {
TANGLE_RELEASE_MAC
} else if cfg!(target_os = "linux") {
TANGLE_RELEASE_LINUX
} else {
return Err("Unsupported platform".into());
};
let cache_dir = dirs::cache_dir()
.ok_or("Could not determine cache directory")?
.join("tangle-binary");
fs::create_dir_all(&cache_dir)?;
let binary_path = cache_dir.join("tangle");
let version_path = cache_dir.join("version.txt");
let commit_hash = download_url.split('/').nth(7).unwrap_or_default();
let should_download = if binary_path.exists() && version_path.exists() {
let stored_version = fs::read_to_string(&version_path)?;
stored_version.trim() != commit_hash
} else {
true
};
if should_download {
blueprint_core::info!("Downloading Tangle binary...");
let response = reqwest::get(download_url).await?;
let bytes = response.bytes().await?;
let mut file = fs::File::create(&binary_path)?;
file.write_all(&bytes)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&binary_path)?.permissions();
perms.set_mode(0o755);
fs::set_permissions(&binary_path, perms)?;
}
fs::write(&version_path, commit_hash)?;
}
Ok(binary_path)
}
pub async fn run(config: NodeConfig) -> Result<SubstrateNode, Error> {
let mut builder = SubstrateNode::builder();
if config.use_local_tangle {
if let Ok(tangle_from_env) = std::env::var(TANGLE_NODE_ENV) {
builder.add_binary_path(tangle_from_env);
}
builder
.add_binary_path("../tangle/target/release/tangle")
.add_binary_path("../../tangle/target/release/tangle")
.add_binary_path("../../../tangle/target/release/tangle");
} else {
let binary_path = download_tangle_binary()
.await
.map_err(|e| Error::Io(std::io::Error::other(e.to_string())))?;
builder.add_binary_path(binary_path.to_string_lossy().to_string());
}
builder
.arg("validator")
.arg_val("rpc-cors", "all")
.arg_val("rpc-methods", "unsafe")
.arg("rpc-external")
.arg_val("sealing", "manual");
let log_string = config.to_log_string();
if !log_string.is_empty() {
builder.arg_val("log", log_string);
}
builder.spawn()
}
#[macro_export]
macro_rules! tangle_blueprint_test_template {
($N:tt, $test_logic:expr, $node_config:expr,) => {
use $crate::test_ext::new_test_ext_blueprint_manager;
#[::blueprint_sdk::tokio::test(flavor = "multi_thread", crate = "::blueprint_sdk::tokio")]
async fn test_blueprint() {
::blueprint_test_utils::setup_log();
let tmp_dir = $crate::tempfile::TempDir::new().unwrap();
let tmp_dir_path = tmp_dir.path().to_string_lossy().into_owned();
::blueprint_test_utils::test_ext::new_test_ext_blueprint_manager::<$N, 1, String, _, _>(
tmp_dir_path,
::blueprint_test_utils::run_test_blueprint_manager,
$node_config,
)
.await
.execute_with_async($test_logic)
.await
}
};
}
#[macro_export]
macro_rules! test_tangle_blueprint {
(
$N:tt,
$T:tt,
$job_id:tt,
[$($inputs:expr),*],
[$($expected_output:expr),*],
$call_id:expr,
$node_config:expr,
) => {
::blueprint_test_utils::tangle_blueprint_test_template!(
$N,
|client, handles, blueprint, _| async move {
let keypair = handles[0].sr25519_id().clone();
let selected_service = &blueprint.services[0];
let service_id = selected_service.id;
::blueprint_sdk::info!(
"Submitting job {} with service ID {service_id}", $job_id
);
let job_args = vec![$($inputs),*];
let job = ::blueprint_test_utils::submit_job(
client,
&keypair,
service_id,
$job_id as ::blueprint_test_utils::Job,
job_args,
$call_id,
)
.await
.expect("Failed to submit job");
let call_id = job.call_id;
::blueprint_sdk::info!(
"Submitted job {} with service ID {service_id} has call id {call_id}", $job_id
);
let job_results = ::blueprint_test_utils::wait_for_completion_of_tangle_job(client, service_id, call_id, $T)
.await
.expect("Failed to wait for job completion");
assert_eq!(job_results.service_id, service_id);
assert_eq!(job_results.call_id, call_id);
let expected_outputs = vec![$($expected_output),*];
if expected_outputs.is_empty() {
::blueprint_sdk::info!("No expected outputs specified, skipping verification");
return
}
assert_eq!(job_results.result.len(), expected_outputs.len(), "Number of outputs doesn't match expected");
for (result, expected) in job_results.result.into_iter().zip(expected_outputs.into_iter()) {
assert_eq!(result, expected);
}
},
$node_config,
);
};
(
$N:tt,
$job_id:tt,
[$($input:expr),*],
[$($expected_output:expr),*]
$call_id:expr,
) => {
::blueprint_test_utils::test_tangle_blueprint!($N, $N, $job_id, [$($input),+], [$($expected_output),+], $call_id);
};
}