Skip to content

Commit

Permalink
chore: improve blueprint-manager and blueprint-test-utils (#421)
Browse files Browse the repository at this point in the history
  • Loading branch information
shekohex authored Oct 30, 2024
1 parent 78a89f3 commit 33a95cf
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 62 deletions.
9 changes: 6 additions & 3 deletions blueprint-manager/src/sources/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,11 @@ pub fn generate_process_arguments(
arguments.push("run".to_string());

if opt.test_mode {
arguments.push("--test-mode=true".to_string());
arguments.push("--test-mode".to_string());
}

if opt.pretty {
arguments.push("--pretty".to_string());
}

for bootnode in &gadget_config.bootnodes {
Expand All @@ -152,8 +156,7 @@ pub fn generate_process_arguments(
format!("--ws-rpc-url={}", gadget_config.ws_rpc_url),
format!("--keystore-uri={}", gadget_config.keystore_uri),
format!("--chain={}", gadget_config.chain),
format!("--verbose={}", opt.verbose),
format!("--pretty={}", opt.pretty),
format!("-{}", "v".repeat(gadget_config.verbose as usize)),
format!("--blueprint-id={}", blueprint_id),
format!("--service-id={}", service_id),
format!("--protocol={}", protocol),
Expand Down
53 changes: 11 additions & 42 deletions blueprint-manager/src/sources/testing.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::sources::BinarySourceFetcher;
use async_trait::async_trait;
use color_eyre::Report;
use gadget_sdk::{info, trace};
use gadget_sdk::trace;
use std::path::PathBuf;
use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::TestFetcher;

Expand All @@ -26,52 +26,21 @@ impl BinarySourceFetcher for TestSourceFetcher {
.map_err(|err| Report::msg(format!("Failed to parse `base_path`: {:?}", err)))?;
let git_repo_root = get_git_repo_root_path().await?;

let profile = if cfg!(debug_assertions) {
"debug"
} else {
"release"
};
let base_path = std::path::absolute(git_repo_root.join(&base_path_str))?;
let binary_path = git_repo_root.join(&base_path).join("bin").join(&cargo_bin);
let binary_path = git_repo_root
.join(&base_path)
.join("target")
.join(profile)
.join(&cargo_bin);
let binary_path = std::path::absolute(&binary_path)?;

trace!("Base Path: {}", base_path.display());
trace!("Binary Path: {}", binary_path.display());
info!("Building binary...");

let env = std::env::vars().collect::<Vec<(String, String)>>();

// Note: even if multiple gadgets are built, only the leader will actually build
// while the followers will just hang on the Cargo.lock file and then instantly
// finish compilation
let tokio_build_process = tokio::process::Command::new("cargo")
.arg("install")
.arg("--path")
.arg(&base_path)
// .arg("--bin")
// .arg(cargo_bin)
.arg("--root")
.arg(&base_path)
.stdout(std::process::Stdio::inherit()) // Inherit the stdout of this process
.stderr(std::process::Stdio::inherit()) // Inherit the stderr of this process
.stdin(std::process::Stdio::null())
.current_dir(&std::env::current_dir()?)
.envs(env)
.output()
.await
.map_err(|err| Report::msg(format!("Failed to run `cargo install`: {:?}", err)))?;

if !tokio_build_process.status.success() {
return Err(Report::msg(format!(
"Failed to build binary: {:?}",
tokio_build_process
)));
}

if !binary_path.exists() {
return Err(Report::msg(format!(
"Binary not found at path: {}",
binary_path.display()
)));
}

info!("Successfully built binary to {}", binary_path.display());

Ok(binary_path)
}

Expand Down
68 changes: 58 additions & 10 deletions blueprint-test-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ use api::services::events::JobResultSubmitted;
use blueprint_manager::config::BlueprintManagerConfig;
use blueprint_manager::executor::BlueprintManagerHandle;
use gadget_io::{GadgetConfig, SupportedChains};
use gadget_sdk::clients::tangle::runtime::TangleClient;
use gadget_sdk::clients::tangle::runtime::{TangleClient, TangleConfig};
use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api;
use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types;
use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::sp_arithmetic::per_things::Percent;
use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::services::calls::types::call::{Args, Job};
use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::services::calls::types::create_blueprint::Blueprint;
use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::services::calls::types::register::{Preferences, RegistrationArgs};
Expand All @@ -20,7 +21,7 @@ use std::error::Error;
use std::net::IpAddr;
use std::path::{Path, PathBuf};
use std::time::Duration;
use subxt::tx::Signer;
use subxt::tx::{Signer, TxProgress};
use subxt::utils::AccountId32;
use url::Url;
use uuid::Uuid;
Expand Down Expand Up @@ -150,6 +151,10 @@ pub async fn inject_test_keys<P: AsRef<Path>>(
// using Pair::from_string is the exact same as TPublic::from_string in the chainspec
let sr_seed = &sr.as_ref().secret.to_bytes();

let ed = sp_core::ed25519::Pair::from_string(&suri, None).expect("Should be valid ED keypair");
// using Pair::from_string is the exact same as TPublic::from_string in the chainspec
let ed_seed = &ed.seed();

let ecdsa =
sp_core::ecdsa::Pair::from_string(&suri, None).expect("Should be valid ECDSA keypair");
// using Pair::from_string is the exact same as TPublic::from_string in the chainspec
Expand All @@ -158,6 +163,9 @@ pub async fn inject_test_keys<P: AsRef<Path>>(
keystore
.sr25519_generate_new(Some(sr_seed))
.expect("Should be valid SR25519 seed");
keystore
.ed25519_generate_new(Some(ed_seed))
.expect("Should be valid ED25519 seed");
keystore
.ecdsa_generate_new(Some(&ecdsa_seed))
.expect("Should be valid ECDSA seed");
Expand Down Expand Up @@ -202,6 +210,16 @@ pub async fn inject_test_keys<P: AsRef<Path>>(
}
}

match keystore.ed25519_key() {
Ok(ed25519_key) => {
assert_eq!(ed25519_key.signer().public().0, ed.public().0);
}
Err(err) => {
log::error!(target: "gadget", "Failed to load ed25519 key: {err}");
panic!("Failed to load ed25519 key: {err}");
}
}

Ok(())
}

Expand All @@ -215,7 +233,7 @@ pub async fn create_blueprint(
.tx()
.sign_and_submit_then_watch_default(&call, account_id)
.await?;
res.wait_for_finalized_success().await?;
wait_for_in_block_success(res).await?;
Ok(())
}

Expand All @@ -232,7 +250,7 @@ pub async fn join_delegators(
.sign_and_submit_then_watch_default(&call_pre, account_id)
.await?;

res_pre.wait_for_finalized_success().await?;
wait_for_in_block_success(res_pre).await?;
Ok(())
}

Expand All @@ -251,7 +269,7 @@ pub async fn register_blueprint(
.tx()
.sign_and_submit_then_watch_default(&call, account_id)
.await?;
res.wait_for_finalized_success().await?;
wait_for_in_block_success(res).await?;
Ok(())
}

Expand All @@ -267,13 +285,13 @@ pub async fn submit_job(
.tx()
.sign_and_submit_then_watch_default(&call, user)
.await?;
let _res = res.wait_for_finalized_success().await?;
wait_for_in_block_success(res).await?;
Ok(())
}

/// Registers a service for a given blueprint. This is meant for testing, and will allow any node
/// Requests a service with a given blueprint. This is meant for testing, and will allow any node
/// to make a call to run a service, and will have all nodes running the service.
pub async fn register_service(
pub async fn request_service(
client: &TestClient,
user: &TanglePairSigner<sp_core::sr25519::Pair>,
blueprint_id: u64,
Expand All @@ -284,14 +302,44 @@ pub async fn register_service(
test_nodes.clone(),
test_nodes,
Default::default(),
Default::default(),
vec![0],
1000,
);
let res = client
.tx()
.sign_and_submit_then_watch_default(&call, user)
.await?;
res.wait_for_finalized_success().await?;
wait_for_in_block_success(res).await?;
Ok(())
}

/// Approves a service request. This is meant for testing, and will always approve the request.
pub async fn approve_service(
client: &TestClient,
caller: &TanglePairSigner<sp_core::sr25519::Pair>,
request_id: u64,
restaking_percent: u8,
) -> Result<(), Box<dyn Error>> {
let call = api::tx()
.services()
.approve(request_id, Percent(restaking_percent));
let res = client
.tx()
.sign_and_submit_then_watch_default(&call, caller)
.await?;
wait_for_in_block_success(res).await?;
Ok(())
}

pub async fn wait_for_in_block_success(
mut res: TxProgress<TangleConfig, TestClient>,
) -> Result<(), Box<dyn Error>> {
while let Some(Ok(event)) = res.next().await {
let Some(block) = event.as_in_block() else {
continue;
};
block.wait_for_success().await?;
}
Ok(())
}

Expand Down
41 changes: 35 additions & 6 deletions blueprint-test-utils/src/test_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use gadget_sdk::clients::tangle::runtime::TangleClient;
use gadget_sdk::tangle_subxt::subxt::OnlineClient;
use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::PriceTargets;
use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::services::calls::types::register::{Preferences, RegistrationArgs};
use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api as api;
use libp2p::Multiaddr;
use std::collections::HashSet;
use std::future::Future;
Expand All @@ -39,7 +40,6 @@ use tracing::Instrument;
use gadget_sdk::{error, info, warn};

const LOCAL_BIND_ADDR: &str = "127.0.0.1";
const LOCAL_TANGLE_NODE_HTTP: &str = "http://127.0.0.1:9944";
pub const NAME_IDS: [&str; 5] = ["Alice", "Bob", "Charlie", "Dave", "Eve"];

/// - `N`: number of nodes
Expand Down Expand Up @@ -129,15 +129,15 @@ pub async fn new_test_ext_blueprint_manager<
}

// Step 1: Create the blueprint using alice's identity
let blueprint_id = match cargo_tangle::deploy::deploy_to_tangle(opts).await {
let blueprint_id = match cargo_tangle::deploy::deploy_to_tangle(opts.clone()).await {
Ok(id) => id,
Err(err) => {
error!("Failed to deploy blueprint: {err}");
panic!("Failed to deploy blueprint: {err}");
}
};

let client = OnlineClient::from_url(LOCAL_TANGLE_NODE_HTTP)
let client = OnlineClient::from_url(&opts.ws_rpc_url)
.await
.expect("Failed to create an account-based localhost client");

Expand All @@ -147,7 +147,7 @@ pub async fn new_test_ext_blueprint_manager<
// TODO: allow the function called to specify the registration args

for handle in handles {
let client = OnlineClient::from_url(LOCAL_TANGLE_NODE_HTTP)
let client = OnlineClient::from_url(&opts.ws_rpc_url)
.await
.expect("Failed to create an account-based localhost client");
let registration_args = registration_args.clone();
Expand Down Expand Up @@ -209,14 +209,43 @@ pub async fn new_test_ext_blueprint_manager<
.collect();

// Use Alice's account to register the service
info!("Registering service for blueprint ID {blueprint_id} using Alice's keys ...");
info!("Requesting service for blueprint ID {blueprint_id} using Alice's keys ...");
let next_request_id_addr = api::storage().services().next_service_request_id();
let next_request_id = client
.storage()
.at_latest()
.await
.expect("Failed to fetch latest block")
.fetch_or_default(&next_request_id_addr)
.await
.expect("Failed to fetch next request ID");

if let Err(err) =
super::register_service(&client, handles[0].sr25519_id(), blueprint_id, all_nodes).await
super::request_service(&client, handles[0].sr25519_id(), blueprint_id, all_nodes).await
{
error!("Failed to register service: {err}");
panic!("Failed to register service: {err}");
}

// Approve the service request for each node
let futures = handles.iter().map(|handle| async {
let keypair = handle.sr25519_id().clone();
let percentage = 50;
info!(
"Approving service request {next_request_id} for {} with {percentage}%",
keypair.account_id()
);
if let Err(err) =
super::approve_service(&client, &keypair, next_request_id, percentage).await
{
error!("Failed to approve service request: {err}");
panic!("Failed to approve service request: {err}");
}
});

let futures_ordered = FuturesOrdered::from_iter(futures);
let _ = futures_ordered.collect::<Vec<_>>().await;

// Now, start every blueprint manager. With the blueprint submitted and every operator registered
// to the blueprint, we can now start the blueprint manager, expecting that the blueprint manager
// will start the services associated with the blueprint as gadgets.
Expand Down
2 changes: 1 addition & 1 deletion macros/context-derive/src/subxt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub fn generate_context_impl(
match CLIENT.get() {
Some(client) => Ok(client.clone()),
None => {
let rpc_url = #field_access.http_rpc_endpoint.as_str();
let rpc_url = #field_access.ws_rpc_endpoint.as_str();
let client = subxt::OnlineClient::from_url(rpc_url).await?;
CLIENT.set(client.clone()).map(|_| client).map_err(|_| {
subxt::Error::Io(std::io::Error::new(
Expand Down

0 comments on commit 33a95cf

Please sign in to comment.