Skip to content

Commit

Permalink
Call ID Insertion and Resolution For #520 (#533)
Browse files Browse the repository at this point in the history
* feat(macros): call_id with TangleClientContext

fix: issue 520

* fix(macros): integration test passes

* fix(docs): get sdk doc tests passing

* fix: update incredible squaring contract

* chore: remove dead code

* chore: update tangle-subxt

* fix!: verify call ID in job submission

* fix: bugs in code

* fix: add hack to test_tangle_blueprint! macro

* fix: bugs in code

* refactor: address review comment

---------

Co-authored-by: Serial <[email protected]>
  • Loading branch information
tbraun96 and Serial-ATA authored Dec 9, 2024
1 parent 7b76af5 commit 4471000
Show file tree
Hide file tree
Showing 33 changed files with 442 additions and 150 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ cargo-tangle = { path = "./cli", version = "0.3.2" }
cargo_metadata = { version = "0.18.1" }

# Tangle-related dependencies
tangle-subxt = { version = "0.7.0", default-features = false }
tangle-subxt = { git = "https://github.com/tangle-network/tangle", default-features = false }
subxt-signer = { version = "0.37.0", default-features = false }
subxt = { version = "0.37.0", default-features = false }
subxt-core = { version = "0.37.0", default-features = false }
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ passing `--clean` as an argument to reset the chain and any keys.
Then, you can run:

```bash
cargo test --package blueprint-test-utils tests_standard::test_externalities_gadget_starts -- --nocapture
cargo test --package blueprint-test-utils test_incredible_squaring -- --nocapture
```

Since testing is in beta stage, each time the blueprint is run, you
Expand Down
5 changes: 2 additions & 3 deletions blueprint-manager/src/executor/event_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,11 +266,10 @@ pub(crate) async fn handle_tangle_event(

// Ensure that we have a test fetcher if we are in test mode
if gadget_manager_opts.test_mode && test_fetcher_idx.is_none() {
warn!(
return Err(color_eyre::Report::msg(format!(
"No testing fetcher found for blueprint `{}` despite operating in TEST MODE",
blueprint.name,
);
continue;
)));
}

// Ensure that we have only one fetcher if we are in test mode
Expand Down
46 changes: 34 additions & 12 deletions blueprint-metadata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,22 +316,44 @@ fn generate_gadget(package: &Package) -> Gadget<'static> {
let root = Path::new(&root)
.canonicalize()
.expect("Failed to canonicalize root dir");
let Some(gadget) = package.metadata.get("gadget") else {
let mut sources = vec![];
if let Some(gadget) = package.metadata.get("gadget") {
let gadget: Gadget<'static> =
serde_json::from_value(gadget.clone()).expect("Failed to deserialize gadget.");
if let Gadget::Native(NativeGadget { sources: fetchers }) = gadget {
sources.extend(fetchers);
} else {
panic!("Currently unsupported gadget type has been parsed")
}
} else {
eprintln!("No gadget metadata found in the Cargo.toml.");
eprintln!("For more information, see:");
eprintln!("<TODO>");
// For now, we just return an empty gadget
return Gadget::Native(NativeGadget {
sources: vec![GadgetSource {
fetcher: GadgetSourceFetcher::Testing(TestFetcher {
cargo_package: package.name.clone().into(),
cargo_bin: "main".into(),
base_path: format!("{}", root.display()).into(),
}),
}],
});
};
serde_json::from_value(gadget.clone()).expect("Failed to deserialize gadget.")

let has_test_fetcher = sources.iter().any(|fetcher| {
matches!(
fetcher,
GadgetSource {
fetcher: GadgetSourceFetcher::Testing(..)
}
)
});

if !has_test_fetcher {
println!("Adding test fetcher since none exists");
sources.push(GadgetSource {
fetcher: GadgetSourceFetcher::Testing(TestFetcher {
cargo_package: package.name.clone().into(),
cargo_bin: "main".into(),
base_path: format!("{}", root.display()).into(),
}),
})
}

assert_ne!(sources.len(), 0, "No sources found for the gadget");

Gadget::Native(NativeGadget { sources })
}

fn generate_rustdoc() -> Crate {
Expand Down
8 changes: 6 additions & 2 deletions blueprint-test-utils/src/tangle/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub fn run() -> Result<SubstrateNode, Error> {
&tangle_from_env,
"../tangle/target/release/tangle",
"../../tangle/target/release/tangle",
"../../../tangle/target/release/tangle",
])
.arg("validator")
.arg_val("rpc-cors", "all")
Expand Down Expand Up @@ -56,7 +57,8 @@ macro_rules! test_tangle_blueprint {
$T:tt,
$job_id:tt,
[$($inputs:expr),*],
[$($expected_output:expr),*]
[$($expected_output:expr),*],
$call_id:expr,
) => {
::blueprint_test_utils::tangle_blueprint_test_template!(
$N,
Expand All @@ -77,6 +79,7 @@ macro_rules! test_tangle_blueprint {
service_id,
$job_id as ::blueprint_test_utils::Job,
job_args,
$call_id,
)
.await
.expect("Failed to submit job");
Expand Down Expand Up @@ -113,7 +116,8 @@ macro_rules! test_tangle_blueprint {
$job_id:tt,
[$($input:expr),*],
[$($expected_output:expr),*]
$call_id:expr,
) => {
::blueprint_test_utils::test_tangle_blueprint!($N, $N, $job_id, [$($input),+], [$($expected_output),+]);
::blueprint_test_utils::test_tangle_blueprint!($N, $N, $job_id, [$($input),+], [$($expected_output),+], $call_id);
};
}
7 changes: 6 additions & 1 deletion blueprint-test-utils/src/tangle/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::services::events::{Jo
use subxt::tx::TxProgress;
use gadget_sdk::clients::tangle::runtime::TangleConfig;
use gadget_sdk::subxt_core::tx::signer::Signer;
use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::Asset;
use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::services::calls::types::request::{Assets, PaymentAsset};
use crate::TestClient;

/// Deploy a new MBSM revision and returns the result.
Expand Down Expand Up @@ -138,6 +140,7 @@ pub async fn submit_job(
service_id: u64,
job_id: Job,
job_params: Args,
call_id: u64,
) -> Result<JobCalled, Box<dyn Error>> {
let call = api::tx().services().call(service_id, job_id, job_params);
let events = client
Expand All @@ -153,6 +156,7 @@ pub async fn submit_job(
if job_called.service_id == service_id
&& job_called.job == job_id
&& user.account_id() == job_called.caller
&& job_called.call_id == call_id
{
return Ok(job_called);
}
Expand All @@ -175,8 +179,9 @@ pub async fn request_service(
test_nodes.clone(),
test_nodes,
Default::default(),
vec![0],
Assets::from([0]),
1000,
PaymentAsset::from(Asset::Custom(0)),
value,
);
let res = client
Expand Down

Large diffs are not rendered by default.

22 changes: 17 additions & 5 deletions blueprints/examples/src/raw_tangle_events.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
use gadget_sdk::config::StdGadgetConfiguration;
use gadget_sdk::contexts::TangleClientContext;
use gadget_sdk::event_listener::tangle::{TangleEvent, TangleEventListener};
use gadget_sdk::event_utils::InitializableEventHandler;
use gadget_sdk::job;
use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api;

#[derive(Clone)]
pub struct MyContext;
#[derive(Clone, TangleClientContext)]
pub struct MyContext {
#[config]
sdk_config: StdGadgetConfiguration,
#[call_id]
call_id: Option<u64>,
}

pub async fn constructor(
env: StdGadgetConfiguration,
Expand All @@ -17,9 +23,15 @@ pub async fn constructor(
.map_err(|e| color_eyre::eyre::eyre!(e))?;

gadget_sdk::info!("Starting the event watcher for {} ...", signer.account_id());
RawEventHandler::new(&env, MyContext)
.await
.map_err(|e| color_eyre::eyre::eyre!(e))
RawEventHandler::new(
&env,
MyContext {
sdk_config: env.clone(),
call_id: None,
},
)
.await
.map_err(|e| color_eyre::eyre::eyre!(e))
}

#[job(
Expand Down
16 changes: 12 additions & 4 deletions blueprints/examples/src/services_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use gadget_sdk::tangle_subxt::tangle_testnet_runtime::api::services::events::Job
pub struct ExampleServiceContext {
#[config]
sdk_config: StdGadgetConfiguration,
#[call_id]
call_id: Option<u64>,
}

pub async fn constructor(
Expand All @@ -24,9 +26,15 @@ pub async fn constructor(
.map_err(|e| color_eyre::eyre::eyre!(e))?;

gadget_sdk::info!("Starting the event watcher for {} ...", signer.account_id());
HandleJobEventHandler::new(&env.clone(), ExampleServiceContext { sdk_config: env })
.await
.map_err(|e| color_eyre::eyre::eyre!(e))
HandleJobEventHandler::new(
&env.clone(),
ExampleServiceContext {
sdk_config: env,
call_id: None,
},
)
.await
.map_err(|e| color_eyre::eyre::eyre!(e))
}

#[job(
Expand All @@ -39,8 +47,8 @@ pub async fn constructor(
),
)]
pub async fn handle_job(
job_details: Vec<u8>,
context: ExampleServiceContext,
job_details: Vec<u8>,
) -> Result<u64, gadget_sdk::Error> {
let client = context.tangle_client().await.unwrap();
let blueprint_owner = context.current_blueprint_owner(&client).await.unwrap();
Expand Down
2 changes: 1 addition & 1 deletion blueprints/incredible-squaring/contracts/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.20"
solc_version = "0.8.20"
2 changes: 1 addition & 1 deletion blueprints/incredible-squaring/contracts/remappings.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ds-test/=lib/ds-test/src/
forge-std/=lib/forge-std/src/
incredible-squaring/=src/
tnt-core/=lib/tnt-core/src/
incredible-squaring/=src/
Original file line number Diff line number Diff line change
Expand Up @@ -23,46 +23,62 @@ contract IncredibleSquaringBlueprint is BlueprintServiceManagerBase {
mapping(uint64 => address) public serviceInstances;

/**
* @dev Hook for service instance requests. Called when a user requests a service
* instance from the blueprint.
* @param serviceId The ID of the requested service.
* @param operatorsOfService The operators involved in the service in bytes array format.
* @param requestInputs Inputs required for the service request in bytes format.
* @dev Hook for service initialization. Called when a service is initialized.
* This hook is called after the service is approved from all of the operators.
*
* @param requestId The ID of the request.
* @param serviceId The ID of the service.
* @param owner The owner of the service.
* @param permittedCallers The list of permitted callers for the service.
* @param ttl The time-to-live for the service.
*/
function onRequest(uint64 serviceId, bytes[] calldata operatorsOfService, bytes calldata requestInputs)
public
payable
virtual
override
onlyFromRootChain
function onServiceInitialized(
uint64 requestId,
uint64 serviceId,
address owner,
address[] calldata permittedCallers,
uint64 ttl
)
external
virtual
override
onlyFromMaster
{
IncredibleSquaringInstance deployed = new IncredibleSquaringInstance(serviceId);
serviceInstances[serviceId] = address(deployed);
}

/**
* @dev Verifies the result of a job call. This function is used to validate the
* outputs of a job execution against the expected results.
* @dev Hook for handling job result. Called when operators send the result
* of a job execution.
* @param serviceId The ID of the service related to the job.
* @param job The job identifier.
* @param jobCallId The unique ID for the job call.
* @param participant The participant (operator) whose result is being verified.
* @param inputs Inputs used for the job execution.
* @param outputs Outputs resulting from the job execution.
* @param operator The operator sending the result in bytes format.
* @param inputs Inputs used for the job execution in bytes format.
* @param outputs Outputs resulting from the job execution in bytes format.
*/
function onJobResult(
uint64 serviceId,
uint8 job,
uint64 jobCallId,
bytes calldata participant,
ServiceOperators.OperatorPreferences calldata operator,
bytes calldata inputs,
bytes calldata outputs
) public payable override {
// Decode the inputs and outputs
uint256 input = abi.decode(inputs, (uint256));
uint256 output = abi.decode(outputs, (uint256));
// Check if the output is the square of the input
bool isValid = output == input * input;
require(isValid, "Invalid result");
)
external
payable
virtual
override
onlyFromMaster
{
if (jobCallId == 0) {
// Decode the inputs and outputs
uint256 input = abi.decode(inputs, (uint256));
uint256 output = abi.decode(outputs, (uint256));
// Check if the output is the square of the input
bool isValid = output == input * input;
require(isValid, "Invalid result");
}
}
}
Loading

0 comments on commit 4471000

Please sign in to comment.