Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: filtered queries in contracts #4544

Merged
merged 2 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

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

36 changes: 15 additions & 21 deletions client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,11 +361,11 @@ pub struct Client {
}

/// Query request
#[derive(Debug, Clone, serde::Serialize)]
#[derive(Debug, Clone)]
pub struct QueryRequest {
torii_url: Url,
headers: HashMap<String, String>,
request: crate::data_model::query::QueryRequest<Vec<u8>>,
request: crate::data_model::query::QueryRequest<SignedQuery>,
}

impl QueryRequest {
Expand All @@ -377,12 +377,8 @@ impl QueryRequest {
torii_url: format!("http://{torii_url}").parse().unwrap(),
headers: HashMap::new(),
request: crate::data_model::query::QueryRequest::Query(
crate::data_model::query::QueryWithParameters {
query: Vec::default(),
sorting: Sorting::default(),
pagination: Pagination::default(),
fetch_size: FetchSize::default(),
},
ClientQueryBuilder::new(FindAllAccounts, test_samples::ALICE_ID.clone())
.sign(&test_samples::ALICE_KEYPAIR),
),
}
}
Expand All @@ -395,11 +391,9 @@ impl QueryRequest {
.headers(self.headers);

match self.request {
crate::data_model::query::QueryRequest::Query(query_with_params) => builder
.params(query_with_params.sorting().clone().into_query_parameters())
.params(query_with_params.pagination().into_query_parameters())
.params(query_with_params.fetch_size().into_query_parameters())
.body(query_with_params.query().clone()),
crate::data_model::query::QueryRequest::Query(signed_query) => {
builder.body(signed_query.encode())
}
crate::data_model::query::QueryRequest::Cursor(cursor) => {
builder.params(Vec::from(cursor))
}
Expand Down Expand Up @@ -490,7 +484,7 @@ impl Client {
///
/// # Errors
/// Fails if signature generation fails
pub fn sign_query(&self, query: QueryBuilder) -> SignedQuery {
pub fn sign_query(&self, query: ClientQueryBuilder) -> SignedQuery {
query.sign(&self.key_pair)
}

Expand Down Expand Up @@ -822,17 +816,17 @@ impl Client {
where
<R::Output as TryFrom<QueryOutputBox>>::Error: Into<eyre::Error>,
{
let query_builder = QueryBuilder::new(request, self.account_id.clone()).with_filter(filter);
let request = self.sign_query(query_builder).encode_versioned();
let query_builder = ClientQueryBuilder::new(request, self.account_id.clone())
.with_filter(filter)
.with_pagination(pagination)
.with_sorting(sorting)
.with_fetch_size(fetch_size);
let request = self.sign_query(query_builder);

let query_request = QueryRequest {
torii_url: self.torii_url.clone(),
headers: self.headers.clone(),
request: crate::data_model::query::QueryRequest::Query(
crate::data_model::query::QueryWithParameters::new(
request, sorting, pagination, fetch_size,
),
),
request: crate::data_model::query::QueryRequest::Query(request),
};

(
Expand Down
46 changes: 2 additions & 44 deletions client/tests/integration/queries/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
use std::str::FromStr as _;

use eyre::Result;
use iroha_client::{
client::{self, ClientQueryError},
data_model::{
prelude::*,
query::{cursor::ForwardCursor, error::QueryExecutionFail, MAX_FETCH_SIZE},
query::{error::QueryExecutionFail, MAX_FETCH_SIZE},
},
};
use test_network::*;
Expand All @@ -14,6 +11,7 @@ mod account;
mod asset;
mod query_errors;
mod role;
mod smart_contract;

#[test]
fn too_big_fetch_size_is_not_allowed() {
Expand All @@ -33,43 +31,3 @@ fn too_big_fetch_size_is_not_allowed() {
))
));
}

#[test]
fn live_query_is_dropped_after_smart_contract_end() -> Result<()> {
let (_rt, _peer, client) = <PeerBuilder>::new().with_port(11_140).start_with_runtime();
wait_for_genesis_committed(&[client.clone()], 0);

let wasm = iroha_wasm_builder::Builder::new(
"tests/integration/smartcontracts/query_assets_and_save_cursor",
)
.show_output()
.build()?
.optimize()?
.into_bytes()?;

let transaction = client.build_transaction(
WasmSmartContract::from_compiled(wasm),
UnlimitedMetadata::default(),
);
client.submit_transaction_blocking(&transaction)?;

let metadata_value = client.request(FindAccountKeyValueByIdAndKey::new(
client.account_id.clone(),
Name::from_str("cursor").unwrap(),
))?;
let cursor: String = metadata_value.try_into()?;
let asset_cursor = serde_json::from_str::<ForwardCursor>(&cursor)?;

let err = client
.request_with_cursor::<Vec<Asset>>(asset_cursor)
.expect_err("Request with cursor from smart contract should fail");

assert!(matches!(
err,
ClientQueryError::Validation(ValidationFail::QueryFailed(
QueryExecutionFail::UnknownCursor
))
));

Ok(())
}
73 changes: 73 additions & 0 deletions client/tests/integration/queries/smart_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use std::str::FromStr as _;

use eyre::Result;
use iroha_client::{
client::ClientQueryError,
data_model::{
prelude::*,
query::{cursor::ForwardCursor, error::QueryExecutionFail},
},
};
use test_network::*;

#[test]
fn live_query_is_dropped_after_smart_contract_end() -> Result<()> {
let (_rt, _peer, client) = <PeerBuilder>::new().with_port(11_140).start_with_runtime();
wait_for_genesis_committed(&[client.clone()], 0);

let wasm = iroha_wasm_builder::Builder::new(
"tests/integration/smartcontracts/query_assets_and_save_cursor",
)
.show_output()
.build()?
.optimize()?
.into_bytes()?;

let transaction = client.build_transaction(
WasmSmartContract::from_compiled(wasm),
UnlimitedMetadata::default(),
);
client.submit_transaction_blocking(&transaction)?;

let metadata_value = client.request(FindAccountKeyValueByIdAndKey::new(
client.account_id.clone(),
Name::from_str("cursor").unwrap(),
))?;
let cursor: String = metadata_value.try_into()?;
let asset_cursor = serde_json::from_str::<ForwardCursor>(&cursor)?;

let err = client
.request_with_cursor::<Vec<Asset>>(asset_cursor)
.expect_err("Request with cursor from smart contract should fail");

assert!(matches!(
err,
ClientQueryError::Validation(ValidationFail::QueryFailed(
QueryExecutionFail::UnknownCursor
))
));

Ok(())
}

#[test]
fn smart_contract_can_filter_queries() -> Result<()> {
let (_rt, _peer, client) = <PeerBuilder>::new().with_port(11_260).start_with_runtime();
wait_for_genesis_committed(&[client.clone()], 0);

let wasm = iroha_wasm_builder::Builder::new(
"tests/integration/smartcontracts/smart_contract_can_filter_queries",
)
.show_output()
.build()?
.optimize()?
.into_bytes()?;

let transaction = client.build_transaction(
WasmSmartContract::from_compiled(wasm),
UnlimitedMetadata::default(),
);
client.submit_transaction_blocking(&transaction)?;

Ok(())
}
5 changes: 3 additions & 2 deletions client/tests/integration/smartcontracts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ members = [
"executor_remove_token",
"executor_with_migration_fail",
"query_assets_and_save_cursor",
"smart_contract_can_filter_queries",
]

[profile.dev]
Expand All @@ -29,8 +30,8 @@ opt-level = "z" # Optimize for size vs speed with "s"/"z"(removes vectorizat
codegen-units = 1 # Further reduces binary size but increases compilation time

[workspace.dependencies]
iroha_smart_contract = { version = "=2.0.0-pre-rc.21", path = "../../../../smart_contract", features = ["debug"]}
iroha_trigger = { version = "=2.0.0-pre-rc.21", path = "../../../../smart_contract/trigger", features = ["debug"]}
iroha_smart_contract = { version = "=2.0.0-pre-rc.21", path = "../../../../smart_contract", features = ["debug"] }
iroha_trigger = { version = "=2.0.0-pre-rc.21", path = "../../../../smart_contract/trigger", features = ["debug"] }
iroha_executor = { version = "=2.0.0-pre-rc.21", path = "../../../../smart_contract/executor" }
iroha_schema = { version = "=2.0.0-pre-rc.21", path = "../../../../schema" }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "smart_contract_can_filter_queries"

edition.workspace = true
version.workspace = true
authors.workspace = true

license.workspace = true

[lib]
crate-type = ['cdylib']

[dependencies]
iroha_smart_contract.workspace = true

panic-halt.workspace = true
lol_alloc.workspace = true
getrandom.workspace = true
parity-scale-codec.workspace = true
nonzero_ext.workspace = true
serde_json = { workspace = true, default-features = false }
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! Smart contract which executes [`FindAllAssets`] and saves cursor to the owner's metadata.

#![no_std]

#[cfg(not(test))]
extern crate panic_halt;

extern crate alloc;

use alloc::{collections::BTreeSet, string::ToString, vec::Vec};

use iroha_smart_contract::{
data_model::query::predicate::{string::StringPredicate, value::QueryOutputPredicate},
prelude::*,
QueryOutputCursor,
};
use lol_alloc::{FreeListAllocator, LockedAllocator};

#[global_allocator]
static ALLOC: LockedAllocator<FreeListAllocator> = LockedAllocator::new(FreeListAllocator::new());

getrandom::register_custom_getrandom!(iroha_smart_contract::stub_getrandom);

/// Create two asset definitions in the looking_glass domain, query all asset definitions, filter them to only be in the looking_glass domain, check that the results are consistent
#[iroha_smart_contract::main]
fn main(_owner: AccountId) {
// create the "looking_glass" domain
Register::domain(Domain::new("looking_glass".parse().unwrap()))
.execute()
.dbg_unwrap();

// create two asset definitions inside the `looking_glass` domain
let time_id: AssetDefinitionId = "time#looking_glass".parse().dbg_unwrap();
let space_id: AssetDefinitionId = "space#looking_glass".parse().dbg_unwrap();

Register::asset_definition(AssetDefinition::new(
time_id.clone(),
AssetValueType::Numeric(NumericSpec::default()),
))
.execute()
.dbg_unwrap();

Register::asset_definition(AssetDefinition::new(
space_id.clone(),
AssetValueType::Numeric(NumericSpec::default()),
))
.execute()
.dbg_unwrap();

// genesis registers some more asset definitions, but we apply a filter to find only the ones from the `looking_glass` domain
let cursor: QueryOutputCursor<Vec<AssetDefinition>> = FindAllAssetsDefinitions
.filter(QueryOutputPredicate::Identifiable(
StringPredicate::EndsWith("#looking_glass".to_string()),
))
.execute()
.dbg_unwrap();

let mut asset_definition_ids = BTreeSet::new();

for asset_definition in cursor {
let asset_definition = asset_definition.dbg_unwrap();
asset_definition_ids.insert(asset_definition.id().clone());
}

assert_eq!(
asset_definition_ids,
[time_id, space_id].into_iter().collect()
);
}
Binary file modified configs/swarm/executor.wasm
100644 → 100755
Binary file not shown.
Loading
Loading