From bfb2f31c9346b437554c1609fe49197015427d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=AD=90=EF=B8=8FNINIKA=E2=AD=90=EF=B8=8F?= Date: Tue, 9 Jul 2024 16:23:30 +0300 Subject: [PATCH] feat!: remove the old query system entirely, fix the schema and some other remaining tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: ⭐️NINIKA⭐️ --- client/src/client.rs | 55 +- client/src/query.rs | 28 +- client/tests/integration/pagination.rs | 2 +- client/tests/integration/queries/mod.rs | 6 +- .../src/complex_isi.rs | 11 +- .../src/lib.rs | 6 +- .../mint_rose_trigger_args/src/lib.rs | 1 - .../query_assets_and_save_cursor/src/lib.rs | 22 +- client/tests/integration/sorting.rs | 6 +- client/tests/integration/tx_history.rs | 2 +- client_cli/src/main.rs | 8 +- configs/swarm/executor.wasm | Bin 507000 -> 506830 bytes core/src/executor.rs | 14 +- core/src/lib.rs | 2 +- core/src/query/pagination.rs | 4 +- core/src/query/store.rs | 4 +- core/src/smartcontracts/isi/account.rs | 103 +- core/src/smartcontracts/isi/asset.rs | 151 +- core/src/smartcontracts/isi/block.rs | 31 +- core/src/smartcontracts/isi/domain.rs | 23 +- core/src/smartcontracts/isi/query.rs | 161 +- core/src/smartcontracts/isi/triggers/mod.rs | 70 +- core/src/smartcontracts/isi/tx.rs | 41 +- core/src/smartcontracts/isi/world.rs | 67 +- core/src/smartcontracts/mod.rs | 11 +- core/src/smartcontracts/wasm.rs | 93 +- data_model/src/lib.rs | 162 +- data_model/src/query/builder.rs | 16 +- data_model/src/query/cursor.rs | 44 - data_model/src/query/mod.rs | 1372 +++--------- data_model/src/query/pagination.rs | 53 - data_model/src/query/parameters.rs | 137 ++ data_model/src/query/predicate/mod.rs | 1080 +--------- .../predicate/predicate_atoms/account.rs | 7 +- .../query/predicate/predicate_atoms/asset.rs | 10 +- .../query/predicate/predicate_atoms/block.rs | 10 +- .../query/predicate/predicate_atoms/domain.rs | 8 +- .../query/predicate/predicate_atoms/mod.rs | 13 +- .../predicate/predicate_atoms/parameter.rs | 8 +- .../query/predicate/predicate_atoms/peer.rs | 8 +- .../predicate/predicate_atoms/permission.rs | 8 +- .../query/predicate/predicate_atoms/role.rs | 9 +- .../predicate/predicate_atoms/trigger.rs | 9 +- .../src/query/predicate/prototypes/account.rs | 3 - data_model/src/query/sorting.rs | 45 - data_model/src/visit.rs | 10 +- docs/source/references/schema.json | 1896 ++++++++++------- schema/gen/src/lib.rs | 184 +- smart_contract/executor/derive/src/default.rs | 2 +- smart_contract/executor/src/lib.rs | 4 +- smart_contract/src/lib.rs | 72 +- torii/src/routing.rs | 8 +- 52 files changed, 1969 insertions(+), 4131 deletions(-) delete mode 100644 data_model/src/query/cursor.rs delete mode 100644 data_model/src/query/pagination.rs create mode 100644 data_model/src/query/parameters.rs delete mode 100644 data_model/src/query/sorting.rs diff --git a/client/src/client.rs b/client/src/client.rs index 64bfc17ef6d..3ad1b022029 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -225,14 +225,6 @@ impl Client { transaction.sign(self.key_pair.private_key()) } - /// Signs query - /// - /// # Errors - /// Fails if signature generation fails - pub fn sign_query(&self, query: ClientQueryBuilder) -> SignedQuery { - query.sign(&self.key_pair) - } - /// Instructions API entry point. Submits one Iroha Special Instruction to `Iroha` peers. /// Returns submitted transaction's hash or error string. /// @@ -980,11 +972,6 @@ pub mod account { FindAllAccounts } - /// Construct a query to get account by id - pub fn by_id(account_id: AccountId) -> FindAccountById { - FindAccountById::new(account_id) - } - /// Construct a query to get all accounts containing specified asset pub fn all_with_asset(asset_definition_id: AssetDefinitionId) -> FindAccountsWithAsset { FindAccountsWithAsset::new(asset_definition_id) @@ -1004,21 +991,6 @@ pub mod asset { pub const fn all_definitions() -> FindAllAssetsDefinitions { FindAllAssetsDefinitions } - - /// Construct a query to get asset definition by its id - pub fn definition_by_id(asset_definition_id: AssetDefinitionId) -> FindAssetDefinitionById { - FindAssetDefinitionById::new(asset_definition_id) - } - - /// Construct a query to get all assets by account id - pub fn by_account_id(account_id: AccountId) -> FindAssetsByAccountId { - FindAssetsByAccountId::new(account_id) - } - - /// Construct a query to get an asset by its id - pub fn by_id(asset_id: AssetId) -> FindAssetById { - FindAssetById::new(asset_id) - } } pub mod block { @@ -1050,11 +1022,6 @@ pub mod domain { pub const fn all() -> FindAllDomains { FindAllDomains } - - /// Construct a query to get all domain by id - pub fn by_id(domain_id: DomainId) -> FindDomainById { - FindDomainById::new(domain_id) - } } pub mod transaction { @@ -1082,22 +1049,15 @@ pub mod trigger { //! Module with queries for triggers use super::*; + /// Construct a query to get all active trigger ids + pub fn all_ids() -> FindAllActiveTriggerIds { + FindAllActiveTriggerIds + } + /// Construct a query to get a trigger by its id pub fn by_id(trigger_id: TriggerId) -> FindTriggerById { FindTriggerById::new(trigger_id) } - - /// Construct a query to find all triggers executable - /// on behalf of the given account. - pub fn by_authority(account_id: AccountId) -> FindTriggersByAuthorityId { - FindTriggersByAuthorityId::new(account_id) - } - - /// Construct a query to find all triggers executable - /// on behalf of any account belonging to the given domain. - pub fn by_domain_of_authority(domain_id: DomainId) -> FindTriggersByAuthorityDomainId { - FindTriggersByAuthorityDomainId::new(domain_id) - } } pub mod permission { @@ -1125,11 +1085,6 @@ pub mod role { FindAllRoleIds } - /// Construct a query to retrieve a role by its id - pub fn by_id(role_id: RoleId) -> FindRoleByRoleId { - FindRoleByRoleId::new(role_id) - } - /// Construct a query to retrieve all roles for an account pub fn by_account_id(account_id: AccountId) -> FindRolesByAccountId { FindRolesByAccountId::new(account_id) diff --git a/client/src/query.rs b/client/src/query.rs index f2bfe24cf27..413fecb4530 100644 --- a/client/src/query.rs +++ b/client/src/query.rs @@ -1,4 +1,4 @@ -#![warn(unused, missing_docs)] +#![warn(missing_docs)] use std::{collections::HashMap, fmt::Debug}; @@ -9,10 +9,10 @@ use iroha_data_model::{ account::AccountId, query::{ builder::{IterableQueryBuilder, QueryExecutor, SingleQueryError}, - error::QueryExecutionFail, + parameters::ForwardCursor, predicate::HasPredicateBox, - ForwardCursor, IterableQueryOutput, IterableQueryOutputBatchBox, IterableQueryWithParams, - QueryRequest2, QueryResponse2, SingularQuery, SingularQueryBox, SingularQueryOutputBox, + IterableQueryOutput, IterableQueryOutputBatchBox, IterableQueryWithParams, QueryRequest, + QueryResponse, SingularQuery, SingularQueryBox, SingularQueryOutputBox, }, ValidationFail, }; @@ -36,7 +36,7 @@ struct ClientQueryRequestHead { } impl ClientQueryRequestHead { - pub fn assemble(&self, query: QueryRequest2) -> DefaultRequestBuilder { + pub fn assemble(&self, query: QueryRequest) -> DefaultRequestBuilder { // authorize and sign the query let query = query .with_authority(self.account_id.clone()) @@ -52,10 +52,10 @@ impl ClientQueryRequestHead { } /// Decode a raw response from the node's query endpoint -fn decode_query_response(resp: &http::Response>) -> QueryResult { +fn decode_query_response(resp: &http::Response>) -> QueryResult { match resp.status() { StatusCode::OK => { - let res = QueryResponse2::decode_all(&mut resp.body().as_slice()); + let res = QueryResponse::decode_all(&mut resp.body().as_slice()); res.wrap_err( "Failed to decode response from Iroha. \ You are likely using a version of the client library \ @@ -93,7 +93,7 @@ fn decode_query_response(resp: &http::Response>) -> QueryResult>, ) -> QueryResult { - let QueryResponse2::Singular(resp) = decode_query_response(resp)? else { + let QueryResponse::Singular(resp) = decode_query_response(resp)? else { return Err(eyre!( "Got unexpected type of query response from the node (expected singular)" ) @@ -105,7 +105,7 @@ fn decode_singular_query_response( fn decode_iterable_query_response( resp: &http::Response>, ) -> QueryResult { - let QueryResponse2::Iterable(resp) = decode_query_response(resp)? else { + let QueryResponse::Iterable(resp) = decode_query_response(resp)? else { return Err(eyre!( "Got unexpected type of query response from the node (expected iterable)" ) @@ -153,7 +153,7 @@ impl QueryExecutor for Client { ) -> Result { let request_head = self.get_query_request_head(); - let request = QueryRequest2::Singular(query); + let request = QueryRequest::Singular(query); let response = request_head.assemble(request).build()?.send()?; let response = decode_singular_query_response(&response)?; @@ -167,7 +167,7 @@ impl QueryExecutor for Client { ) -> Result<(IterableQueryOutputBatchBox, Option), Self::Error> { let request_head = self.get_query_request_head(); - let request = QueryRequest2::StartIterable(query); + let request = QueryRequest::StartIterable(query); let response = request_head.assemble(request).build()?.send()?; let response = decode_iterable_query_response(&response)?; @@ -190,7 +190,7 @@ impl QueryExecutor for Client { cursor, } = cursor; - let request = QueryRequest2::ContinueIterable(cursor); + let request = QueryRequest::ContinueIterable(cursor); let response = request_head.assemble(request).build()?.send()?; let response = decode_iterable_query_response(&response)?; @@ -249,10 +249,10 @@ impl Client { pub fn raw_continue_iterable_query( &self, cursor: ForwardCursor, - ) -> Result { + ) -> Result { let request_head = self.get_query_request_head(); - let request = QueryRequest2::ContinueIterable(cursor); + let request = QueryRequest::ContinueIterable(cursor); let response = request_head.assemble(request).build()?.send()?; let response = decode_query_response(&response)?; diff --git a/client/tests/integration/pagination.rs b/client/tests/integration/pagination.rs index 7001ad8d772..e86a6bd33d6 100644 --- a/client/tests/integration/pagination.rs +++ b/client/tests/integration/pagination.rs @@ -1,7 +1,7 @@ use eyre::Result; use iroha::{ client::{asset, Client}, - data_model::{asset::AssetDefinition, prelude::*, query::Pagination}, + data_model::{asset::AssetDefinition, prelude::*}, }; use nonzero_ext::nonzero; use test_network::*; diff --git a/client/tests/integration/queries/mod.rs b/client/tests/integration/queries/mod.rs index 31b6c612bd4..62f0698497c 100644 --- a/client/tests/integration/queries/mod.rs +++ b/client/tests/integration/queries/mod.rs @@ -1,10 +1,8 @@ use iroha::{ client::{self, ClientQueryError}, - data_model::{ - prelude::*, - query::{error::QueryExecutionFail, MAX_FETCH_SIZE}, - }, + data_model::{prelude::*, query::error::QueryExecutionFail}, }; +use iroha_data_model::query::parameters::MAX_FETCH_SIZE; use test_network::*; mod account; diff --git a/client/tests/integration/smartcontracts/executor_custom_data_model/src/complex_isi.rs b/client/tests/integration/smartcontracts/executor_custom_data_model/src/complex_isi.rs index 125903d3616..1edd6d8cb05 100644 --- a/client/tests/integration/smartcontracts/executor_custom_data_model/src/complex_isi.rs +++ b/client/tests/integration/smartcontracts/executor_custom_data_model/src/complex_isi.rs @@ -125,9 +125,7 @@ mod expression { use iroha_data_model::{ isi::InstructionBox, - prelude::{ - FindAssetQuantityById, FindTotalAssetQuantityByAssetDefinitionId, Numeric, QueryBox, - }, + prelude::{FindAssetQuantityById, FindTotalAssetQuantityByAssetDefinitionId, Numeric}, }; use iroha_schema::{IntoSchema, TypeId}; use serde::{Deserialize, Serialize}; @@ -295,12 +293,7 @@ mod expression { mod evaluate { use alloc::string::ToString; - use iroha_data_model::{ - isi::error::InstructionExecutionError, - prelude::Numeric, - query::{QueryBox, SingularQuery, SingularQueryBox}, - ValidationFail, - }; + use iroha_data_model::{isi::error::InstructionExecutionError, ValidationFail}; use crate::complex_isi::{ expression::{EvaluatesTo, Expression, Greater, Value}, diff --git a/client/tests/integration/smartcontracts/executor_custom_instructions_simple/src/lib.rs b/client/tests/integration/smartcontracts/executor_custom_instructions_simple/src/lib.rs index f065c8a245b..2ec158c67ca 100644 --- a/client/tests/integration/smartcontracts/executor_custom_instructions_simple/src/lib.rs +++ b/client/tests/integration/smartcontracts/executor_custom_instructions_simple/src/lib.rs @@ -10,10 +10,8 @@ extern crate panic_halt; use executor_custom_data_model::simple_isi::{CustomInstructionBox, MintAssetForAllAccounts}; use iroha_executor::{ - data_model::isi::CustomInstruction, - debug::DebugExpectExt, - prelude::*, - smart_contract::{iter_query, SmartContractSingularQueryError}, + data_model::isi::CustomInstruction, debug::DebugExpectExt, prelude::*, + smart_contract::iter_query, }; use lol_alloc::{FreeListAllocator, LockedAllocator}; diff --git a/client/tests/integration/smartcontracts/mint_rose_trigger_args/src/lib.rs b/client/tests/integration/smartcontracts/mint_rose_trigger_args/src/lib.rs index eda3d291902..41a35db8a6b 100644 --- a/client/tests/integration/smartcontracts/mint_rose_trigger_args/src/lib.rs +++ b/client/tests/integration/smartcontracts/mint_rose_trigger_args/src/lib.rs @@ -10,7 +10,6 @@ use core::str::FromStr as _; use executor_custom_data_model::mint_rose_args::MintRoseArgs; use iroha_trigger::{debug::dbg_panic, prelude::*}; use lol_alloc::{FreeListAllocator, LockedAllocator}; -use serde::{Deserialize, Serialize}; #[global_allocator] static ALLOC: LockedAllocator = LockedAllocator::new(FreeListAllocator::new()); diff --git a/client/tests/integration/smartcontracts/query_assets_and_save_cursor/src/lib.rs b/client/tests/integration/smartcontracts/query_assets_and_save_cursor/src/lib.rs index bd573c800a2..ca9c20e0d77 100644 --- a/client/tests/integration/smartcontracts/query_assets_and_save_cursor/src/lib.rs +++ b/client/tests/integration/smartcontracts/query_assets_and_save_cursor/src/lib.rs @@ -9,8 +9,10 @@ extern crate alloc; use iroha_smart_contract::{ data_model::query::{ - builder::QueryExecutor, cursor::ForwardCursor, predicate::CompoundPredicate, - IterableQueryParams, IterableQueryWithFilter, IterableQueryWithParams, + builder::QueryExecutor, + parameters::{ForwardCursor, IterableQueryParams}, + predicate::CompoundPredicate, + IterableQueryWithFilter, IterableQueryWithParams, }, prelude::*, SmartContractQueryExecutor, @@ -34,14 +36,14 @@ fn main(owner: AccountId) { } let (_batch, cursor) = SmartContractQueryExecutor - .start_iterable_query(IterableQueryWithParams { - query: IterableQueryWithFilter::new(FindAllAssets, CompoundPredicate::PASS).into(), - params: IterableQueryParams { - pagination: Default::default(), - sorting: Default::default(), - fetch_size: FetchSize::new(Some(nonzero!(1_u32))), - }, - }) + .start_iterable_query(IterableQueryWithParams::new( + IterableQueryWithFilter::new(FindAllAssets, CompoundPredicate::PASS).into(), + IterableQueryParams::new( + Default::default(), + Default::default(), + FetchSize::new(Some(nonzero!(1_u32))), + ), + )) .dbg_unwrap(); // break encapsulation by serializing and deserializing into a compatible type diff --git a/client/tests/integration/sorting.rs b/client/tests/integration/sorting.rs index 6bdafcde5c3..12282eec38e 100644 --- a/client/tests/integration/sorting.rs +++ b/client/tests/integration/sorting.rs @@ -4,11 +4,7 @@ use eyre::{Result, WrapErr as _}; use iroha::{ client::{self, QueryResult}, crypto::KeyPair, - data_model::{ - account::Account, - prelude::*, - query::{Pagination, Sorting}, - }, + data_model::{account::Account, prelude::*}, }; use iroha_data_model::query::predicate::predicate_atoms::asset::AssetPredicateBox; use nonzero_ext::nonzero; diff --git a/client/tests/integration/tx_history.rs b/client/tests/integration/tx_history.rs index 9c5956a1134..50b6f81217e 100644 --- a/client/tests/integration/tx_history.rs +++ b/client/tests/integration/tx_history.rs @@ -3,7 +3,7 @@ use std::{str::FromStr as _, thread}; use eyre::Result; use iroha::{ client::transaction, - data_model::{prelude::*, query::Pagination}, + data_model::{prelude::*, query::parameters::Pagination}, }; use iroha_config::parameters::actual::Root as Config; use nonzero_ext::nonzero; diff --git a/client_cli/src/main.rs b/client_cli/src/main.rs index c6f89220241..9e2bf21b2d5 100644 --- a/client_cli/src/main.rs +++ b/client_cli/src/main.rs @@ -1066,7 +1066,7 @@ mod json { use std::io::{BufReader, Read as _}; use clap::Subcommand; - use iroha::data_model::query::QueryBox2; + use iroha::data_model::query::QueryBox; use super::*; @@ -1099,16 +1099,16 @@ mod json { } Variant::Query => { let client = Client::new(context.configuration().clone()); - let query: QueryBox2 = json5::from_str(&string_content)?; + let query: QueryBox = json5::from_str(&string_content)?; match query { - QueryBox2::Singular(query) => { + QueryBox::Singular(query) => { let result = client.query(query).wrap_err("Failed to query response")?; context.print_data(&result)?; } - QueryBox2::Iterable(query) => { + QueryBox::Iterable(query) => { // we can't really do type-erased iterable queries in a nice way right now... use iroha::data_model::query::builder::QueryExecutor; diff --git a/configs/swarm/executor.wasm b/configs/swarm/executor.wasm index f9df77c478facf42c0f469b17a4e1097b545cf83..37c2df4acbcba9f8daa2f7593f37f37cb3563d28 100644 GIT binary patch delta 94903 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 delta 94635 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 diff --git a/core/src/executor.rs b/core/src/executor.rs index 13cdf254f06..7d58506860b 100644 --- a/core/src/executor.rs +++ b/core/src/executor.rs @@ -5,7 +5,7 @@ use iroha_data_model::{ account::AccountId, executor as data_model_executor, isi::InstructionBox, - query::{QueryBox2, QueryRequest2}, + query::{QueryBox, QueryRequest}, transaction::{Executable, SignedTransaction}, ValidationFail, }; @@ -201,18 +201,14 @@ impl Executor { &self, state_ro: &S, authority: &AccountId, - query: &QueryRequest2, + query: &QueryRequest, ) -> Result<(), ValidationFail> { trace!("Running query validation"); - // TODO: actually validate the query request - - // Ok(()) - let query = match query { - QueryRequest2::Singular(singular) => QueryBox2::Singular(singular.clone()), - QueryRequest2::StartIterable(iterable) => QueryBox2::Iterable(iterable.clone()), - QueryRequest2::ContinueIterable(_) => { + QueryRequest::Singular(singular) => QueryBox::Singular(singular.clone()), + QueryRequest::StartIterable(iterable) => QueryBox::Iterable(iterable.clone()), + QueryRequest::ContinueIterable(_) => { // The iterable query was already validated when it started return Ok(()); } diff --git a/core/src/lib.rs b/core/src/lib.rs index 0d25bf98ca7..94241f507c3 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -157,7 +157,7 @@ pub mod prelude { #[doc(inline)] pub use crate::{ - smartcontracts::ValidQuery, + smartcontracts::ValidSingularQuery, state::{StateReadOnly, StateView, World, WorldReadOnly}, tx::AcceptedTransaction, }; diff --git a/core/src/query/pagination.rs b/core/src/query/pagination.rs index a42b7ecd3bf..fb83c485098 100644 --- a/core/src/query/pagination.rs +++ b/core/src/query/pagination.rs @@ -1,6 +1,6 @@ //! Module with [`Paginate`] iterator adaptor which provides [`paginate`] function. -use iroha_data_model::query::Pagination; +use iroha_data_model::query::parameters::Pagination; /// Describes a collection to which pagination can be applied. /// Implemented for the [`Iterator`] implementors. @@ -45,7 +45,7 @@ impl Iterator for Paginated { #[cfg(test)] mod tests { - use iroha_data_model::query::pagination::Pagination; + use iroha_data_model::query::parameters::Pagination; use nonzero_ext::nonzero; use super::*; diff --git a/core/src/query/store.rs b/core/src/query/store.rs index 345c2ec78c5..e1bc3a559a0 100644 --- a/core/src/query/store.rs +++ b/core/src/query/store.rs @@ -5,8 +5,8 @@ use std::time::{Duration, Instant}; use indexmap::IndexMap; use iroha_config::parameters::actual::LiveQueryStore as Config; use iroha_data_model::query::{ - cursor::{ForwardCursor, QueryId}, error::QueryExecutionFail, + parameters::{ForwardCursor, QueryId}, IterableQueryOutput, }; use iroha_logger::trace; @@ -225,7 +225,7 @@ impl LiveQueryStoreHandle { mod tests { use iroha_data_model::{ permission::Permission, - query::{FetchSize, IterableQueryParams, Pagination, Sorting}, + query::parameters::{FetchSize, IterableQueryParams, Pagination, Sorting}, }; use iroha_primitives::json::JsonString; use nonzero_ext::nonzero; diff --git a/core/src/smartcontracts/isi/account.rs b/core/src/smartcontracts/isi/account.rs index 381c1f1e2f4..e22a21b1c4a 100644 --- a/core/src/smartcontracts/isi/account.rs +++ b/core/src/smartcontracts/isi/account.rs @@ -5,7 +5,7 @@ use iroha_data_model::{prelude::*, query::error::FindError}; use iroha_telemetry::metrics; use super::prelude::*; -use crate::ValidQuery; +use crate::ValidSingularQuery; impl Registrable for iroha_data_model::account::NewAccount { type Target = Account; @@ -496,20 +496,6 @@ pub mod query { use super::*; use crate::{smartcontracts::ValidIterableQuery, state::StateReadOnly}; - impl ValidQuery for FindRolesByAccountId { - #[metrics(+"find_roles_by_account_id")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, Error> { - let account_id = &self.id; - state_ro.world().account(account_id)?; - Ok(Box::new( - state_ro.world().account_roles_iter(account_id).cloned(), - )) - } - } - impl ValidIterableQuery for FindRolesByAccountId { #[metrics(+"find_roles_by_account_id")] fn execute<'state>( @@ -527,22 +513,6 @@ pub mod query { } } - impl ValidQuery for FindPermissionsByAccountId { - #[metrics(+"find_permissions_by_account_id")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, Error> { - let account_id = &self.id; - Ok(Box::new( - state_ro - .world() - .account_permissions_iter(account_id)? - .cloned(), - )) - } - } - impl ValidIterableQuery for FindPermissionsByAccountId { #[metrics(+"find_permissions_by_account_id")] fn execute<'state>( @@ -559,16 +529,6 @@ pub mod query { } } - impl ValidQuery for FindAllAccounts { - #[metrics(+"find_all_accounts")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, Error> { - Ok(Box::new(state_ro.world().accounts_iter().cloned())) - } - } - impl ValidIterableQuery for FindAllAccounts { #[metrics(+"find_all_accounts")] fn execute<'state>( @@ -584,38 +544,7 @@ pub mod query { } } - impl ValidQuery for FindAccountById { - #[metrics(+"find_account_by_id")] - fn execute(&self, state_ro: &impl StateReadOnly) -> Result { - let id = &self.id; - iroha_logger::trace!(%id); - state_ro - .world() - .domain(id.domain()) - .map_err(|_| FindError::Domain(id.domain().clone()))?; - state_ro - .world() - .map_account(id, Clone::clone) - .map_err(Into::into) - } - } - - impl ValidQuery for FindAccountsByDomainId { - #[metrics(+"find_accounts_by_domain_id")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, Error> { - let id = &self.domain; - - iroha_logger::trace!(%id); - Ok(Box::new( - state_ro.world().accounts_in_domain_iter(id).cloned(), - )) - } - } - - impl ValidQuery for FindAccountKeyValueByIdAndKey { + impl ValidSingularQuery for FindAccountKeyValueByIdAndKey { #[metrics(+"find_account_key_value_by_id_and_key")] fn execute(&self, state_ro: &impl StateReadOnly) -> Result { let id = &self.id; @@ -629,34 +558,6 @@ pub mod query { } } - impl ValidQuery for FindAccountsWithAsset { - #[metrics(+"find_accounts_with_asset")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, Error> { - let asset_definition_id = self.asset_definition.clone(); - iroha_logger::trace!(%asset_definition_id); - - Ok(Box::new( - state_ro - .world() - .accounts_iter() - .filter(move |account| { - state_ro - .world() - .assets() - .get(&AssetId::new( - asset_definition_id.clone(), - account.id().clone(), - )) - .is_some() - }) - .cloned(), - )) - } - } - impl ValidIterableQuery for FindAccountsWithAsset { #[metrics(+"find_accounts_with_asset")] fn execute<'state>( diff --git a/core/src/smartcontracts/isi/asset.rs b/core/src/smartcontracts/isi/asset.rs index cc1d10b6d5a..835d8c8020d 100644 --- a/core/src/smartcontracts/isi/asset.rs +++ b/core/src/smartcontracts/isi/asset.rs @@ -425,7 +425,6 @@ pub mod query { use iroha_data_model::{ asset::{Asset, AssetDefinition, AssetValue}, query::{ - asset::FindAssetDefinitionById, error::QueryExecutionFail as Error, predicate::{ predicate_atoms::asset::{AssetDefinitionPredicateBox, AssetPredicateBox}, @@ -438,16 +437,6 @@ pub mod query { use super::*; use crate::{smartcontracts::ValidIterableQuery, state::StateReadOnly}; - impl ValidQuery for FindAllAssets { - #[metrics(+"find_all_assets")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, Error> { - Ok(Box::new(state_ro.world().assets_iter().cloned())) - } - } - impl ValidIterableQuery for FindAllAssets { #[metrics(+"find_all_assets")] fn execute<'state>( @@ -462,17 +451,6 @@ pub mod query { .cloned()) } } - - impl ValidQuery for FindAllAssetsDefinitions { - #[metrics(+"find_all_asset_definitions")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, Error> { - Ok(Box::new(state_ro.world().asset_definitions_iter().cloned())) - } - } - impl ValidIterableQuery for FindAllAssetsDefinitions { #[metrics(+"find_all_asset_definitions")] fn execute<'state>( @@ -488,130 +466,7 @@ pub mod query { } } - impl ValidQuery for FindAssetById { - #[metrics(+"find_asset_by_id")] - fn execute(&self, state_ro: &impl StateReadOnly) -> Result { - let id = &self.id; - iroha_logger::trace!(%id); - state_ro.world().asset(id).map_err(|asset_err| { - if let Err(definition_err) = state_ro.world().asset_definition(&id.definition) { - definition_err.into() - } else { - asset_err - } - }) - } - } - - impl ValidQuery for FindAssetDefinitionById { - #[metrics(+"find_asset_defintion_by_id")] - fn execute(&self, state_ro: &impl StateReadOnly) -> Result { - let id = &self.id; - - let entry = state_ro.world().asset_definition(id).map_err(Error::from)?; - - Ok(entry) - } - } - - impl ValidQuery for FindAssetsByName { - #[metrics(+"find_assets_by_name")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, Error> { - let name = self.name.clone(); - iroha_logger::trace!(%name); - Ok(Box::new( - state_ro - .world() - .assets_iter() - .filter(move |asset| asset.id().definition.name == name) - .cloned(), - )) - } - } - - impl ValidQuery for FindAssetsByAccountId { - #[metrics(+"find_assets_by_account_id")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, Error> { - let id = self.account.clone(); - iroha_logger::trace!(%id); - let _ = state_ro.world().map_account(&id, |_| ())?; - Ok(Box::new( - state_ro - .world() - .assets_iter() - .filter(move |asset| asset.id().account == id) - .cloned(), - )) - } - } - - impl ValidQuery for FindAssetsByAssetDefinitionId { - #[metrics(+"find_assets_by_asset_definition_id")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, Error> { - let id = self.asset_definition.clone(); - iroha_logger::trace!(%id); - Ok(Box::new( - state_ro - .world() - .assets_iter() - .filter(move |asset| asset.id().definition == id) - .cloned(), - )) - } - } - - impl ValidQuery for FindAssetsByDomainId { - #[metrics(+"find_assets_by_domain_id")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, Error> { - let id = self.domain.clone(); - iroha_logger::trace!(%id); - Ok(Box::new( - state_ro - .world() - .assets_iter() - .filter(move |asset| asset.id().account.domain() == &id) - .cloned(), - )) - } - } - - impl ValidQuery for FindAssetsByDomainIdAndAssetDefinitionId { - #[metrics(+"find_assets_by_domain_id_and_asset_definition_id")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, Error> { - let domain_id = self.domain.clone(); - let asset_definition_id = self.asset_definition.clone(); - let _ = state_ro.world().domain(&domain_id)?; - let _ = state_ro.world().asset_definition(&asset_definition_id)?; - iroha_logger::trace!(%domain_id, %asset_definition_id); - Ok(Box::new( - state_ro - .world() - .assets_iter() - .filter(move |asset| { - asset.id().definition == asset_definition_id - && asset.id().account.domain() == &domain_id - }) - .cloned(), - )) - } - } - - impl ValidQuery for FindAssetQuantityById { + impl ValidSingularQuery for FindAssetQuantityById { #[metrics(+"find_asset_quantity_by_id")] fn execute(&self, state_ro: &impl StateReadOnly) -> Result { let id = &self.id; @@ -637,7 +492,7 @@ pub mod query { } } - impl ValidQuery for FindTotalAssetQuantityByAssetDefinitionId { + impl ValidSingularQuery for FindTotalAssetQuantityByAssetDefinitionId { #[metrics(+"find_total_asset_quantity_by_asset_definition_id")] fn execute(&self, state_ro: &impl StateReadOnly) -> Result { let id = &self.id; @@ -647,7 +502,7 @@ pub mod query { } } - impl ValidQuery for FindAssetKeyValueByIdAndKey { + impl ValidSingularQuery for FindAssetKeyValueByIdAndKey { #[metrics(+"find_asset_key_value_by_id_and_key")] fn execute(&self, state_ro: &impl StateReadOnly) -> Result { let id = &self.id; diff --git a/core/src/smartcontracts/isi/block.rs b/core/src/smartcontracts/isi/block.rs index 1d5cac1d6e4..fed4431ee22 100644 --- a/core/src/smartcontracts/isi/block.rs +++ b/core/src/smartcontracts/isi/block.rs @@ -1,7 +1,7 @@ //! This module contains trait implementations related to block queries use eyre::Result; use iroha_data_model::{ - block::{BlockHeader, SignedBlock}, + block::BlockHeader, query::{ block::FindBlockHeaderByHash, error::{FindError, QueryExecutionFail}, @@ -16,18 +16,6 @@ use iroha_telemetry::metrics; use super::*; use crate::{smartcontracts::ValidIterableQuery, state::StateReadOnly}; -impl ValidQuery for FindAllBlocks { - #[metrics(+"find_all_blocks")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, QueryExecutionFail> { - Ok(Box::new( - state_ro.all_blocks().rev().map(|block| (*block).clone()), - )) - } -} - impl ValidIterableQuery for FindAllBlocks { #[metrics(+"find_all_blocks")] fn execute<'state>( @@ -43,21 +31,6 @@ impl ValidIterableQuery for FindAllBlocks { } } -impl ValidQuery for FindAllBlockHeaders { - #[metrics(+"find_all_block_headers")] - fn execute<'state>( - &self, - staete_snapshot: &'state impl StateReadOnly, - ) -> Result + 'state>, QueryExecutionFail> { - Ok(Box::new( - staete_snapshot - .all_blocks() - .rev() - .map(|block| block.header().clone()), - )) - } -} - impl ValidIterableQuery for FindAllBlockHeaders { #[metrics(+"find_all_block_headers")] fn execute<'state>( @@ -73,7 +46,7 @@ impl ValidIterableQuery for FindAllBlockHeaders { } } -impl ValidQuery for FindBlockHeaderByHash { +impl ValidSingularQuery for FindBlockHeaderByHash { #[metrics(+"find_block_header")] fn execute(&self, state_ro: &impl StateReadOnly) -> Result { let hash = self.hash; diff --git a/core/src/smartcontracts/isi/domain.rs b/core/src/smartcontracts/isi/domain.rs index ad0ba12483b..1910146827d 100644 --- a/core/src/smartcontracts/isi/domain.rs +++ b/core/src/smartcontracts/isi/domain.rs @@ -403,16 +403,6 @@ pub mod query { use super::*; use crate::{smartcontracts::ValidIterableQuery, state::StateReadOnly}; - impl ValidQuery for FindAllDomains { - #[metrics(+"find_all_domains")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, Error> { - Ok(Box::new(state_ro.world().domains_iter().cloned())) - } - } - impl ValidIterableQuery for FindAllDomains { #[metrics(+"find_all_domains")] fn execute<'state>( @@ -428,16 +418,7 @@ pub mod query { } } - impl ValidQuery for FindDomainById { - #[metrics(+"find_domain_by_id")] - fn execute(&self, state_ro: &impl StateReadOnly) -> Result { - let id = &self.id; - iroha_logger::trace!(%id); - Ok(state_ro.world().domain(id)?.clone()) - } - } - - impl ValidQuery for FindDomainKeyValueByIdAndKey { + impl ValidSingularQuery for FindDomainKeyValueByIdAndKey { #[metrics(+"find_domain_key_value_by_id_and_key")] fn execute(&self, state_ro: &impl StateReadOnly) -> Result { let id = &self.id; @@ -451,7 +432,7 @@ pub mod query { } } - impl ValidQuery for FindAssetDefinitionKeyValueByIdAndKey { + impl ValidSingularQuery for FindAssetDefinitionKeyValueByIdAndKey { #[metrics(+"find_asset_definition_key_value_by_id_and_key")] fn execute(&self, state_ro: &impl StateReadOnly) -> Result { let id = &self.id; diff --git a/core/src/smartcontracts/isi/query.rs b/core/src/smartcontracts/isi/query.rs index 3d0647d8cee..794956dc442 100644 --- a/core/src/smartcontracts/isi/query.rs +++ b/core/src/smartcontracts/isi/query.rs @@ -6,14 +6,14 @@ use eyre::Result; use iroha_data_model::{ prelude::*, query::{ - error::QueryExecutionFail as Error, IterableQueryBox, IterableQueryOutputBatchBox, - IterableQueryParams, QueryOutputBox, QueryRequest2, QueryRequestWithAuthority, - QueryResponse2, SingularQueryBox, SingularQueryOutputBox, + error::QueryExecutionFail as Error, parameters::IterableQueryParams, IterableQueryBox, + IterableQueryOutputBatchBox, QueryRequest, QueryRequestWithAuthority, QueryResponse, + SingularQueryBox, SingularQueryOutputBox, }, }; use crate::{ - prelude::ValidQuery, + prelude::ValidSingularQuery, query::{ cursor::QueryBatchedErasedIterator, pagination::Paginate as _, store::LiveQueryStoreHandle, }, @@ -127,8 +127,8 @@ where // validate the fetch (aka batch) size let fetch_size = fetch_size .fetch_size - .unwrap_or(iroha_data_model::query::DEFAULT_FETCH_SIZE); - if fetch_size > iroha_data_model::query::MAX_FETCH_SIZE { + .unwrap_or(iroha_data_model::query::parameters::DEFAULT_FETCH_SIZE); + if fetch_size > iroha_data_model::query::parameters::MAX_FETCH_SIZE { return Err(Error::FetchSizeTooBig); } @@ -174,54 +174,9 @@ where Ok(output) } -/// Represents lazy evaluated query output -pub trait Lazy { - /// Type of the lazy evaluated query output - type Lazy<'a>; -} - -/// Lazily evaluated equivalent of [`Query::Output`] -pub enum LazyQueryOutput<'a> { - /// Concrete computed [`Query::Output`] - QueryOutput(QueryOutputBox), - /// Iterator over a set of [`Query::Output`]s - Iter(Box + 'a>), -} - -impl Lazy for QueryOutputBox { - type Lazy<'a> = LazyQueryOutput<'a>; -} - -impl Lazy for Vec { - type Lazy<'a> = Box + 'a>; -} - -macro_rules! impl_lazy { - ( $($ident:ty),+ $(,)? ) => { $( - impl Lazy for $ident { - type Lazy<'a> = Self; - } )+ - }; -} -impl_lazy! { - bool, - iroha_data_model::prelude::Numeric, - iroha_data_model::role::Role, - iroha_data_model::asset::Asset, - iroha_data_model::asset::AssetDefinition, - iroha_data_model::account::Account, - iroha_data_model::domain::Domain, - iroha_data_model::block::BlockHeader, - iroha_primitives::json::JsonString, - iroha_data_model::query::TransactionQueryOutput, - iroha_data_model::executor::ExecutorDataModel, - iroha_data_model::trigger::Trigger, - iroha_data_model::parameter::Parameters, -} - /// Query Request statefully validated on the Iroha node side. #[derive(Debug, Clone)] -pub struct ValidQueryRequest(QueryRequest2); +pub struct ValidQueryRequest(QueryRequest); impl ValidQueryRequest { /// Validate a query for an API client by calling the executor. @@ -240,7 +195,7 @@ impl ValidQueryRequest { /// /// The validation logic is defined by the implementation of the [`ValidateQueryOperation`] trait. pub fn validate_for_wasm( - query: QueryRequest2, + query: QueryRequest, state: &mut wasm::state::CommonState, ) -> Result where @@ -258,9 +213,9 @@ impl ValidQueryRequest { self, live_query_store: &LiveQueryStoreHandle, state: &impl StateReadOnly, - ) -> Result { + ) -> Result { match self.0 { - QueryRequest2::Singular(singular_query) => { + QueryRequest::Singular(singular_query) => { let output = match singular_query { SingularQueryBox::FindAssetQuantityById(q) => { SingularQueryOutputBox::from(q.execute(state)?) @@ -300,9 +255,9 @@ impl ValidQueryRequest { } }; - Ok(QueryResponse2::Singular(output)) + Ok(QueryResponse::Singular(output)) } - QueryRequest2::StartIterable(iter_query) => { + QueryRequest::StartIterable(iter_query) => { let output = match iter_query.query { // dispatch on a concrete query type, erasing the type with `QueryBatchedErasedIterator` in the end IterableQueryBox::FindAllDomains(q) => apply_query_postprocessing( @@ -367,87 +322,26 @@ impl ValidQueryRequest { )?, }; - Ok(QueryResponse2::Iterable( + Ok(QueryResponse::Iterable( live_query_store.handle_iter_start(output)?, )) } - QueryRequest2::ContinueIterable(cursor) => Ok(QueryResponse2::Iterable( + QueryRequest::ContinueIterable(cursor) => Ok(QueryResponse::Iterable( live_query_store.handle_iter_continue(cursor)?, )), } } } -impl ValidQuery for QueryBox { - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result, Error> { - iroha_logger::debug!(query=%self, "Executing"); - - macro_rules! match_all { - ( non_iter: {$( $non_iter_query:ident ),+ $(,)?} $( $query:ident, )+ ) => { - match self { $( - QueryBox::$non_iter_query(query) => query.execute(state_ro).map(QueryOutputBox::from).map(LazyQueryOutput::QueryOutput), )+ $( - QueryBox::$query(query) => query.execute(state_ro).map(|i| i.map(QueryOutputBox::from)).map(|iter| LazyQueryOutput::Iter(Box::new(iter))), )+ - } - }; - } - - match_all! { - non_iter: { - FindAccountById, - FindAssetById, - FindAssetDefinitionById, - FindAssetQuantityById, - FindTotalAssetQuantityByAssetDefinitionId, - FindDomainById, - FindBlockHeaderByHash, - FindTransactionByHash, - FindTriggerById, - FindRoleByRoleId, - FindDomainKeyValueByIdAndKey, - FindAssetKeyValueByIdAndKey, - FindAccountKeyValueByIdAndKey, - FindAssetDefinitionKeyValueByIdAndKey, - FindTriggerKeyValueByIdAndKey, - FindExecutorDataModel, - FindAllParameters, - } - - FindAllAccounts, - FindAccountsByDomainId, - FindAccountsWithAsset, - FindAllAssets, - FindAllAssetsDefinitions, - FindAssetsByName, - FindAssetsByAccountId, - FindAssetsByAssetDefinitionId, - FindAssetsByDomainId, - FindAssetsByDomainIdAndAssetDefinitionId, - FindAllDomains, - FindAllPeers, - FindAllBlocks, - FindAllBlockHeaders, - FindAllTransactions, - FindTransactionsByAccountId, - FindPermissionsByAccountId, - FindAllActiveTriggerIds, - FindTriggersByAuthorityId, - FindTriggersByAuthorityDomainId, - FindAllRoles, - FindAllRoleIds, - FindRolesByAccountId, - } - } -} - #[cfg(test)] mod tests { use std::str::FromStr as _; use iroha_crypto::{Hash, HashOf, KeyPair}; - use iroha_data_model::{parameter::TransactionParameters, query::error::FindError}; + use iroha_data_model::{ + parameter::TransactionParameters, + query::{error::FindError, predicate::CompoundPredicate}, + }; use iroha_primitives::json::JsonString; use nonzero_ext::nonzero; use test_samples::{gen_account_in, ALICE_ID, ALICE_KEYPAIR}; @@ -619,7 +513,9 @@ mod tests { let num_blocks = 100; let state = state_with_test_blocks_and_transactions(num_blocks, 1, 1)?; - let blocks = ValidQuery::execute(&FindAllBlocks, &state.view())?.collect::>(); + let blocks = + ValidIterableQuery::execute(FindAllBlocks, CompoundPredicate::PASS, &state.view())? + .collect::>(); assert_eq!(blocks.len() as u64, num_blocks); assert!(blocks @@ -634,8 +530,12 @@ mod tests { let num_blocks = 100; let state = state_with_test_blocks_and_transactions(num_blocks, 1, 1)?; - let block_headers = - ValidQuery::execute(&FindAllBlockHeaders, &state.view())?.collect::>(); + let block_headers = ValidIterableQuery::execute( + FindAllBlockHeaders, + CompoundPredicate::PASS, + &state.view(), + )? + .collect::>(); assert_eq!(block_headers.len() as u64, num_blocks); assert!(block_headers.windows(2).all(|wnd| wnd[0] >= wnd[1])); @@ -668,7 +568,12 @@ mod tests { let num_blocks = 100; let state = state_with_test_blocks_and_transactions(num_blocks, 1, 1)?; - let txs = ValidQuery::execute(&FindAllTransactions, &state.view())?.collect::>(); + let txs = ValidIterableQuery::execute( + FindAllTransactions, + CompoundPredicate::PASS, + &state.view(), + )? + .collect::>(); assert_eq!(txs.len() as u64, num_blocks * 2); assert_eq!( diff --git a/core/src/smartcontracts/isi/triggers/mod.rs b/core/src/smartcontracts/isi/triggers/mod.rs index bebac4f97a4..41e8e793e32 100644 --- a/core/src/smartcontracts/isi/triggers/mod.rs +++ b/core/src/smartcontracts/isi/triggers/mod.rs @@ -331,16 +331,6 @@ pub mod query { state::StateReadOnly, }; - impl ValidQuery for FindAllActiveTriggerIds { - #[metrics(+"find_all_active_triggers")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, Error> { - Ok(Box::new(state_ro.world().triggers().ids_iter().cloned())) - } - } - impl ValidIterableQuery for FindAllActiveTriggerIds { #[metrics(+"find_all_active_triggers")] fn execute<'state>( @@ -357,7 +347,7 @@ pub mod query { } } - impl ValidQuery for FindTriggerById { + impl ValidSingularQuery for FindTriggerById { #[metrics(+"find_trigger_by_id")] fn execute(&self, state_ro: &impl StateReadOnly) -> Result { let id = &self.id; @@ -381,7 +371,7 @@ pub mod query { } } - impl ValidQuery for FindTriggerKeyValueByIdAndKey { + impl ValidSingularQuery for FindTriggerKeyValueByIdAndKey { #[metrics(+"find_trigger_key_value_by_id_and_key")] fn execute(&self, state_ro: &impl StateReadOnly) -> Result { let id = &self.id; @@ -401,60 +391,4 @@ pub mod query { .map(Into::into) } } - - impl ValidQuery for FindTriggersByAuthorityId { - #[metrics(+"find_triggers_by_authority_id")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> eyre::Result + 'state>, Error> { - let account_id = self.account.clone(); - - Ok(Box::new( - state_ro - .world() - .triggers() - .inspect_by_action( - move |action| action.authority() == &account_id, - |trigger_id, action| (trigger_id.clone(), action.clone_and_box()), - ) - .map(|(trigger_id, action)| { - let action = state_ro - .world() - .triggers() - .get_original_action(action) - .into(); - Trigger::new(trigger_id, action) - }), - )) - } - } - - impl ValidQuery for FindTriggersByAuthorityDomainId { - #[metrics(+"find_triggers_by_authority_domain_id")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> eyre::Result + 'state>, Error> { - let domain_id = self.domain.clone(); - - Ok(Box::new( - state_ro - .world() - .triggers() - .inspect_by_action( - move |action| action.authority().domain() == &domain_id, - |trigger_id, action| (trigger_id.clone(), action.clone_and_box()), - ) - .map(|(trigger_id, action)| { - let action = state_ro - .world() - .triggers() - .get_original_action(action) - .into(); - Trigger::new(trigger_id, action) - }), - )) - } - } } diff --git a/core/src/smartcontracts/isi/tx.rs b/core/src/smartcontracts/isi/tx.rs index 9949987f101..58dcb290e52 100644 --- a/core/src/smartcontracts/isi/tx.rs +++ b/core/src/smartcontracts/isi/tx.rs @@ -67,24 +67,6 @@ impl BlockTransactionRef { } } -impl ValidQuery for FindAllTransactions { - #[metrics(+"find_all_transactions")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, QueryExecutionFail> { - Ok(Box::new( - state_ro - .all_blocks() - .flat_map(BlockTransactionIter::new) - .map(|tx| TransactionQueryOutput { - block_hash: tx.block_hash(), - transaction: tx.value(), - }), - )) - } -} - impl ValidIterableQuery for FindAllTransactions { #[metrics(+"find_all_transactions")] fn execute<'state>( @@ -103,27 +85,6 @@ impl ValidIterableQuery for FindAllTransactions { } } -impl ValidQuery for FindTransactionsByAccountId { - #[metrics(+"find_transactions_by_account_id")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, QueryExecutionFail> { - let account_id = self.account.clone(); - - Ok(Box::new( - state_ro - .all_blocks() - .flat_map(BlockTransactionIter::new) - .filter(move |tx| *tx.authority() == account_id) - .map(|tx| TransactionQueryOutput { - block_hash: tx.block_hash(), - transaction: tx.value(), - }), - )) - } -} - impl ValidIterableQuery for FindTransactionsByAccountId { #[metrics(+"find_transactions_by_account_id")] fn execute<'state>( @@ -145,7 +106,7 @@ impl ValidIterableQuery for FindTransactionsByAccountId { } } -impl ValidQuery for FindTransactionByHash { +impl ValidSingularQuery for FindTransactionByHash { #[metrics(+"find_transaction_by_hash")] fn execute( &self, diff --git a/core/src/smartcontracts/isi/world.rs b/core/src/smartcontracts/isi/world.rs index e3244c78ee3..f7543009a53 100644 --- a/core/src/smartcontracts/isi/world.rs +++ b/core/src/smartcontracts/isi/world.rs @@ -457,7 +457,7 @@ pub mod query { peer::Peer, prelude::*, query::{ - error::{FindError, QueryExecutionFail as Error}, + error::QueryExecutionFail as Error, predicate::{ predicate_atoms::{ peer::PeerPredicateBox, @@ -466,29 +466,12 @@ pub mod query { CompoundPredicate, }, }, - role::{Role, RoleId}, + role::Role, }; use super::*; use crate::{smartcontracts::ValidIterableQuery, state::StateReadOnly}; - impl ValidQuery for FindAllRoles { - #[metrics(+"find_all_roles")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, Error> { - Ok(Box::new( - state_ro - .world() - .roles() - .iter() - .map(|(_, role)| role) - .cloned(), - )) - } - } - impl ValidIterableQuery for FindAllRoles { #[metrics(+"find_all_roles")] fn execute<'state>( @@ -506,25 +489,6 @@ pub mod query { } } - impl ValidQuery for FindAllRoleIds { - #[metrics(+"find_all_role_ids")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, Error> { - Ok(Box::new( - state_ro - .world() - .roles() - .iter() - .map(|(_, role)| role) - // To me, this should probably be a method, not a field. - .map(Role::id) - .cloned(), - )) - } - } - impl ValidIterableQuery for FindAllRoleIds { #[metrics(+"find_all_role_ids")] fn execute<'state>( @@ -543,29 +507,6 @@ pub mod query { } } - impl ValidQuery for FindRoleByRoleId { - #[metrics(+"find_role_by_role_id")] - fn execute(&self, state_ro: &impl StateReadOnly) -> Result { - let role_id = &self.id; - iroha_logger::trace!(%role_id); - - state_ro.world().roles().get(role_id).map_or_else( - || Err(Error::Find(FindError::Role(role_id.clone()))), - |role_ref| Ok(role_ref.clone()), - ) - } - } - - impl ValidQuery for FindAllPeers { - #[metrics("find_all_peers")] - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result + 'state>, Error> { - Ok(Box::new(state_ro.world().peers().cloned().map(Peer::new))) - } - } - impl ValidIterableQuery for FindAllPeers { #[metrics("find_all_peers")] fn execute<'state>( @@ -582,14 +523,14 @@ pub mod query { } } - impl ValidQuery for FindExecutorDataModel { + impl ValidSingularQuery for FindExecutorDataModel { #[metrics("find_executor_data_model")] fn execute(&self, state_ro: &impl StateReadOnly) -> Result { Ok(state_ro.world().executor_data_model().clone()) } } - impl ValidQuery for FindAllParameters { + impl ValidSingularQuery for FindAllParameters { #[metrics("find_all_parameters")] fn execute(&self, state_ro: &impl StateReadOnly) -> Result { Ok(state_ro.world().parameters().clone()) diff --git a/core/src/smartcontracts/mod.rs b/core/src/smartcontracts/mod.rs index 19a48028aee..a0b5b8a3710 100644 --- a/core/src/smartcontracts/mod.rs +++ b/core/src/smartcontracts/mod.rs @@ -17,7 +17,6 @@ use iroha_data_model::{ }; pub use isi::*; -use self::query::Lazy; use crate::state::{StateReadOnly, StateTransaction}; /// Trait implementations should provide actions to apply changes on [`StateTransaction`]. @@ -53,18 +52,12 @@ where } /// This trait should be implemented for all Iroha Queries. -pub trait ValidQuery: iroha_data_model::query::Query -where - Self::Output: Lazy, -{ +pub trait ValidSingularQuery: iroha_data_model::query::SingularQuery { /// Execute query on the [`WorldSnapshot`]. /// /// Returns Ok(QueryResult) if succeeded and Err(String) if failed. /// /// # Errors /// Concrete to each implementer - fn execute<'state>( - &self, - state_ro: &'state impl StateReadOnly, - ) -> Result<::Lazy<'state>, QueryExecutionFail>; + fn execute(&self, state_ro: &impl StateReadOnly) -> Result; } diff --git a/core/src/smartcontracts/wasm.rs b/core/src/smartcontracts/wasm.rs index d257837ada8..f02ef9e216d 100644 --- a/core/src/smartcontracts/wasm.rs +++ b/core/src/smartcontracts/wasm.rs @@ -12,10 +12,7 @@ use iroha_data_model::{ isi::InstructionBox, parameter::SmartContractParameters as Config, prelude::*, - query::{ - cursor::QueryId, IterableQueryOutput, QueryBox2, QueryRequest, QueryRequest2, - QueryResponse2, SmartContractQuery, - }, + query::{parameters::QueryId, IterableQueryOutput, QueryBox, QueryRequest, QueryResponse}, smart_contract::payloads::{self, Validate}, Level as LogLevel, ValidationFail, }; @@ -23,17 +20,14 @@ use iroha_logger::debug; // NOTE: Using error_span so that span info is logged on every event use iroha_logger::{error_span as wasm_log_span, prelude::tracing::Span}; use iroha_wasm_codec::{self as codec, WasmUsize}; -use parity_scale_codec::Decode; use wasmtime::{ Caller, Config as WasmtimeConfig, Engine, Linker, Module, Store, StoreLimits, StoreLimitsBuilder, TypedFunc, }; -use crate::smartcontracts::query::ValidQueryRequest; -#[warn(unused_imports)] // TODO use crate::{ query::store::LiveQueryStoreHandle, - smartcontracts::{wasm::state::ValidateQueryOperation, Execute}, + smartcontracts::{query::ValidQueryRequest, Execute}, state::{StateReadOnly, StateTransaction, WorldReadOnly}, }; @@ -72,7 +66,7 @@ mod import { //! Traits which some [Runtime]s should implement to import functions from Iroha to WASM use iroha_data_model::{ - query::{QueryRequest2, QueryResponse2}, + query::{QueryRequest, QueryResponse}, smart_contract::payloads::Validate, }; @@ -82,9 +76,9 @@ mod import { /// Execute `query` on host #[codec::wrap_trait_fn] fn execute_query( - query_request: QueryRequest2, + query_request: QueryRequest, state: &mut S, - ) -> Result; + ) -> Result; /// Execute `instruction` on host #[codec::wrap_trait_fn] @@ -105,7 +99,7 @@ mod import { fn get_validate_instruction_payload(state: &S) -> Validate; #[codec::wrap_trait_fn] - fn get_validate_query_payload(state: &S) -> Validate; + fn get_validate_query_payload(state: &S) -> Validate; } pub trait SetDataModel { @@ -247,11 +241,6 @@ pub mod error { /// [`Result`] type for this module pub type Result = core::result::Result; -#[cfg_attr(test, derive(parity_scale_codec::Encode))] -#[derive(Debug, derive_more::Display, Decode)] -#[repr(transparent)] -pub(crate) struct SmartContractQueryRequest(pub QueryRequest); - /// Create [`Module`] from bytes. /// /// # Errors @@ -419,7 +408,7 @@ pub mod state { fn validate_query( &self, authority: &AccountId, - query: &QueryRequest2, + query: &QueryRequest, ) -> Result<(), ValidationFail>; } @@ -487,7 +476,7 @@ pub mod state { pub mod executor { //! States related to *Executor* execution. - use iroha_data_model::query::QueryBox2; + use iroha_data_model::query::QueryBox; use super::*; @@ -501,7 +490,7 @@ pub mod state { pub type ValidateTransaction = Validate; /// State kind for executing `validate_query()` entrypoint of executor - pub type ValidateQuery = Validate; + pub type ValidateQuery = Validate; /// State kind for executing `validate_instruction()` entrypoint of executor pub type ValidateInstruction = Validate; @@ -524,7 +513,7 @@ pub mod state { fn validate_query( &self, authority: &AccountId, - query: &QueryRequest2, + query: &QueryRequest, ) -> Result<(), ValidationFail> { let state_ro = self.state.state(); state_ro @@ -538,7 +527,7 @@ pub mod state { fn validate_query( &self, authority: &AccountId, - query: &QueryRequest2, + query: &QueryRequest, ) -> Result<(), ValidationFail> { let state_ro = self.state.state(); state_ro @@ -579,7 +568,7 @@ pub mod state { fn validate_query( &self, _authority: &AccountId, - _query: &QueryRequest2, + _query: &QueryRequest, ) -> Result<(), ValidationFail> { Ok(()) } @@ -597,7 +586,7 @@ pub mod state { fn validate_query( &self, _authority: &AccountId, - _query: &QueryRequest2, + _query: &QueryRequest, ) -> Result<(), ValidationFail> { Ok(()) } @@ -806,15 +795,14 @@ where W: state::chain_state::ConstState, state::CommonState: state::ValidateQueryOperation, { - #[warn(unused)] // TODO fn default_execute_query( - query_request: QueryRequest2, + query_request: QueryRequest, state: &mut state::CommonState, - ) -> Result { + ) -> Result { iroha_logger::debug!(?query_request, "Executing"); // if the query is continuing a cursor, preserve it (but only if validation succeeds) - let continue_query_id = if let QueryRequest2::ContinueIterable(cursor) = &query_request { + let continue_query_id = if let QueryRequest::ContinueIterable(cursor) = &query_request { Some(cursor.query.clone()) } else { None @@ -836,7 +824,7 @@ where let response = query.execute(live_query_store, state_ro)?; // store the output cursor if there is one - if let QueryResponse2::Iterable(IterableQueryOutput { + if let QueryResponse::Iterable(IterableQueryOutput { continue_cursor: Some(cursor), .. }) = &response @@ -958,9 +946,9 @@ impl<'wrld, 'block, 'state> { #[codec::wrap] fn execute_query( - query_request: QueryRequest2, + query_request: QueryRequest, state: &mut state::SmartContract<'wrld, 'block, 'state>, - ) -> Result { + ) -> Result { Self::default_execute_query(query_request, state) } @@ -1031,9 +1019,9 @@ impl<'wrld, 'block, 'state> import::traits::ExecuteOperations, - ) -> Result { + ) -> Result { Self::default_execute_query(query_request, state) } @@ -1065,9 +1053,9 @@ where { #[codec::wrap] fn execute_query( - query_request: QueryRequest2, + query_request: QueryRequest, state: &mut state::CommonState, S>, - ) -> Result { + ) -> Result { debug!(?query_request, "Executing as executor"); Runtime::default_execute_query(query_request, state) @@ -1177,7 +1165,7 @@ impl<'wrld, 'block, 'state> #[codec::wrap] fn get_validate_query_payload( _state: &state::executor::ValidateTransaction<'wrld, 'block, 'state>, - ) -> Validate { + ) -> Validate { panic!("Executor `validate_transaction()` entrypoint should not query payload for `validate_query()` entrypoint") } } @@ -1257,7 +1245,7 @@ impl<'wrld, 'block, 'state> #[codec::wrap] fn get_validate_query_payload( _state: &state::executor::ValidateInstruction<'wrld, 'block, 'state>, - ) -> Validate { + ) -> Validate { panic!("Executor `validate_instruction()` entrypoint should not query payload for `validate_query()` entrypoint") } } @@ -1282,7 +1270,7 @@ impl<'wrld, S: StateReadOnly> Runtime> state_ro: &'wrld S, authority: &AccountId, module: &wasmtime::Module, - query: QueryBox2, + query: QueryBox, ) -> Result { let span = wasm_log_span!("Running `validate_query()`"); @@ -1306,9 +1294,9 @@ impl<'wrld, S: StateReadOnly> { #[codec::wrap] fn execute_query( - query_request: QueryRequest2, + query_request: QueryRequest, state: &mut state::executor::ValidateQuery<'wrld, S>, - ) -> Result { + ) -> Result { debug!(?query_request, "Executing as executor"); Runtime::default_execute_query(query_request, state) @@ -1349,7 +1337,7 @@ impl<'wrld, S: StateReadOnly> #[codec::wrap] fn get_validate_query_payload( state: &state::executor::ValidateQuery<'wrld, S>, - ) -> Validate { + ) -> Validate { Validate { authority: state.authority.clone(), block_height: state.state.0.height() as u64, @@ -1444,7 +1432,7 @@ impl<'wrld, 'block, 'state> #[codec::wrap] fn get_validate_query_payload( _state: &state::executor::Migrate<'wrld, 'block, 'state>, - ) -> Validate { + ) -> Validate { panic!("Executor `migrate()` entrypoint should not query payload for `validate_query()` entrypoint") } } @@ -1709,7 +1697,6 @@ impl GetExport for (&wasmtime::Instance, C) { #[cfg(test)] mod tests { - use iroha_data_model::query::{predicate::PredicateBox, sorting::Sorting, Pagination}; use nonzero_ext::nonzero; use parity_scale_codec::Encode; use test_samples::gen_account_in; @@ -1821,15 +1808,9 @@ mod tests { let kura = Kura::blank_kura_for_testing(); let query_handle = LiveQueryStore::test().start(); let state = State::new(world_with_test_account(&authority), kura, query_handle); - let query_hex = encode_hex(SmartContractQueryRequest(QueryRequest::Query( - SmartContractQuery::new( - FindAccountById::new(authority.clone()).into(), - PredicateBox::default(), - Sorting::default(), - Pagination::default(), - FetchSize::default(), - ), - ))); + let query_hex = encode_hex(QueryRequest::Singular( + SingularQueryBox::FindExecutorDataModel(FindExecutorDataModel), + )); let wat = format!( r#" @@ -1976,7 +1957,9 @@ mod tests { let kura = Kura::blank_kura_for_testing(); let query_handle = LiveQueryStore::test().start(); let state = State::new(world_with_test_account(&authority), kura, query_handle); - let query_hex = encode_hex(QueryBox::from(FindAccountById::new(authority.clone()))); + let query_hex = encode_hex(QueryRequest::Singular( + SingularQueryBox::FindExecutorDataModel(FindExecutorDataModel), + )); let wat = format!( r#" @@ -2023,7 +2006,6 @@ mod tests { let kura = Kura::blank_kura_for_testing(); let query_handle = LiveQueryStore::test().start(); let state = State::new(world_with_test_account(&authority), kura, query_handle); - let query_hex = encode_hex(QueryBox::from(FindAccountById::new(authority.clone()))); let wat = format!( r#" @@ -2043,7 +2025,8 @@ mod tests { "#, main_fn_name = import::SMART_CONTRACT_MAIN, get_trigger_payload_fn_name = export::GET_TRIGGER_PAYLOAD, - memory_and_alloc = memory_and_alloc(&query_hex), + // this test doesn't use the memory + memory_and_alloc = memory_and_alloc(""), ); let mut runtime = RuntimeBuilder::::new().build()?; diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index ae7b9ca742f..91fef8ef9e2 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -11,12 +11,11 @@ extern crate alloc; #[cfg(not(feature = "std"))] use alloc::{boxed::Box, format, string::String, vec::Vec}; -use derive_more::{Constructor, Display}; +use derive_more::Display; use iroha_crypto::PublicKey; use iroha_data_model_derive::{model, EnumRef}; use iroha_macro::FromVariant; use iroha_schema::IntoSchema; -use iroha_version::{declare_versioned, version_with_scale}; use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; use strum::FromRepr; @@ -114,27 +113,17 @@ mod seal { Log, // Boxed queries - QueryBox, + SingularQueryBox, FindAllAccounts, - FindAccountById, FindAccountKeyValueByIdAndKey, - FindAccountsByDomainId, FindAccountsWithAsset, FindAllAssets, FindAllAssetsDefinitions, - FindAssetById, - FindAssetDefinitionById, - FindAssetsByName, - FindAssetsByAccountId, - FindAssetsByAssetDefinitionId, - FindAssetsByDomainId, - FindAssetsByDomainIdAndAssetDefinitionId, FindAssetQuantityById, FindTotalAssetQuantityByAssetDefinitionId, FindAssetKeyValueByIdAndKey, FindAssetDefinitionKeyValueByIdAndKey, FindAllDomains, - FindDomainById, FindDomainKeyValueByIdAndKey, FindAllPeers, FindAllBlocks, @@ -148,11 +137,8 @@ mod seal { FindAllActiveTriggerIds, FindTriggerById, FindTriggerKeyValueByIdAndKey, - FindTriggersByAuthorityId, - FindTriggersByAuthorityDomainId, FindAllRoles, FindAllRoleIds, - FindRoleByRoleId, FindRolesByAccountId, FindAllParameters, } @@ -208,8 +194,6 @@ impl std::error::Error #[model] #[allow(clippy::redundant_pub_crate)] mod model { - use getset::Getters; - use super::*; /// Unique id of blockchain @@ -284,52 +268,6 @@ mod model { CustomParameterId(parameter::CustomParameterId), } - /// Sized container for all possible entities. - #[derive( - Debug, - Display, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - EnumRef, - FromVariant, - Decode, - Encode, - Deserialize, - Serialize, - IntoSchema, - )] - #[enum_ref(derive(Encode, FromVariant))] - #[ffi_type] - pub enum IdentifiableBox { - /// [`NewDomain`](`domain::NewDomain`) variant. - NewDomain(::With), - /// [`NewAccount`](`account::NewAccount`) variant. - NewAccount(::With), - /// [`NewAssetDefinition`](`asset::NewAssetDefinition`) variant. - NewAssetDefinition(::With), - /// [`NewRole`](`role::NewRole`) variant. - NewRole(::With), - /// [`Peer`](`peer::Peer`) variant. - Peer(peer::Peer), - /// [`Domain`](`domain::Domain`) variant. - Domain(domain::Domain), - /// [`Account`](`account::Account`) variant. - Account(account::Account), - /// [`AssetDefinition`](`asset::AssetDefinition`) variant. - AssetDefinition(asset::AssetDefinition), - /// [`Asset`](`asset::Asset`) variant. - Asset(asset::Asset), - /// [`Trigger`](`trigger::Trigger`) variant. - Trigger(trigger::Trigger), - /// [`Role`](`role::Role`) variant. - Role(role::Role), - /// [`CustomParameter`](`parameter::CustomParameter`) variant. - CustomParameter(parameter::CustomParameter), - } - /// Operation validation failed. /// /// # Note @@ -425,21 +363,6 @@ mod model { /// Error ERROR, } - - /// Batched response of a query sent to torii - #[derive( - Debug, Clone, Constructor, Getters, Decode, Encode, Deserialize, Serialize, IntoSchema, - )] - #[version_with_scale(version = 1, versioned_alias = "BatchedResponse")] - #[getset(get = "pub")] - #[must_use] - pub struct BatchedResponseV1 { - /// Current batch - pub batch: T, - /// Index of the next element in the result set. Client will use this value - /// in the next request to continue fetching results of the original query - pub cursor: crate::query::cursor::ForwardCursor, - } } macro_rules! impl_encode_as_id_box { @@ -455,19 +378,6 @@ macro_rules! impl_encode_as_id_box { } } -macro_rules! impl_encode_as_identifiable_box { - ($($ty:ty),+ $(,)?) => { $( - impl $ty { - /// [`Encode`] [`Self`] as [`IdentifiableBox`]. - /// - /// Used to avoid an unnecessary clone - pub fn encode_as_identifiable_box(&self) -> Vec { - IdentifiableBoxRef::from(self).encode() - } - } )+ - } -} - impl_encode_as_id_box! { peer::PeerId, domain::DomainId, @@ -479,20 +389,6 @@ impl_encode_as_id_box! { role::RoleId, } -impl_encode_as_identifiable_box! { - peer::Peer, - domain::NewDomain, - account::NewAccount, - asset::NewAssetDefinition, - role::NewRole, - domain::Domain, - account::Account, - asset::AssetDefinition, - asset::Asset, - trigger::Trigger, - role::Role, -} - impl Decode for ChainId { fn decode( input: &mut I, @@ -512,46 +408,6 @@ mod test { } } -// TODO: think of a way to `impl Identifiable for IdentifiableBox`. -// The main problem is lifetimes and conversion cost. - -impl IdentifiableBox { - fn id_box(&self) -> IdBox { - match self { - IdentifiableBox::NewDomain(a) => a.id().clone().into(), - IdentifiableBox::NewAccount(a) => a.id().clone().into(), - IdentifiableBox::NewAssetDefinition(a) => a.id().clone().into(), - IdentifiableBox::NewRole(a) => a.id().clone().into(), - IdentifiableBox::Peer(a) => a.id().clone().into(), - IdentifiableBox::Domain(a) => a.id().clone().into(), - IdentifiableBox::Account(a) => a.id().clone().into(), - IdentifiableBox::AssetDefinition(a) => a.id().clone().into(), - IdentifiableBox::Asset(a) => a.id().clone().into(), - IdentifiableBox::Trigger(a) => a.id().clone().into(), - IdentifiableBox::Role(a) => a.id().clone().into(), - IdentifiableBox::CustomParameter(a) => a.id().clone().into(), - } - } -} - -impl<'idbox> TryFrom<&'idbox IdentifiableBox> for &'idbox dyn HasMetadata { - type Error = (); - - fn try_from( - v: &'idbox IdentifiableBox, - ) -> Result<&'idbox (dyn HasMetadata + 'idbox), Self::Error> { - match v { - IdentifiableBox::NewDomain(v) => Ok(v), - IdentifiableBox::NewAccount(v) => Ok(v), - IdentifiableBox::NewAssetDefinition(v) => Ok(v), - IdentifiableBox::Domain(v) => Ok(v), - IdentifiableBox::Account(v) => Ok(v), - IdentifiableBox::AssetDefinition(v) => Ok(v), - _ => Err(()), - } - } -} - /// Uniquely identifiable entity ([`Domain`], [`Account`], etc.). /// This trait should always be derived with [`IdEqOrdHash`] pub trait Identifiable: Ord + Eq { @@ -580,18 +436,6 @@ pub trait Registered: Identifiable { type With; } -declare_versioned!( - BatchedResponse serde::Deserialize<'de>> 1..2, - Debug, Clone, iroha_macro::FromVariant, IntoSchema -); - -impl From> for (T, crate::query::cursor::ForwardCursor) { - fn from(source: BatchedResponse) -> Self { - let BatchedResponse::V1(batch) = source; - (batch.batch, batch.cursor) - } -} - mod ffi { //! Definitions and implementations of FFI related functionalities @@ -645,6 +489,6 @@ pub mod prelude { executor::prelude::*, isi::prelude::*, metadata::prelude::*, name::prelude::*, parameter::prelude::*, peer::prelude::*, permission::prelude::*, query::prelude::*, role::prelude::*, transaction::prelude::*, trigger::prelude::*, ChainId, EnumTryAsError, - HasMetadata, IdBox, Identifiable, IdentifiableBox, ValidationFail, + HasMetadata, IdBox, Identifiable, ValidationFail, }; } diff --git a/data_model/src/query/builder.rs b/data_model/src/query/builder.rs index 820c37bcae2..b477af82608 100644 --- a/data_model/src/query/builder.rs +++ b/data_model/src/query/builder.rs @@ -3,19 +3,14 @@ use alloc::vec::{self, Vec}; #[cfg(feature = "std")] use std::vec; -use iroha_schema::IntoSchema; use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; -use crate::{ - prelude::FetchSize, - query::{ - error::QueryExecutionFail, - predicate::{projectors, AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype}, - IterableQuery, IterableQueryBox, IterableQueryOutputBatchBox, IterableQueryParams, - IterableQueryWithFilter, IterableQueryWithFilterFor, IterableQueryWithParams, Pagination, - SingularQueryBox, SingularQueryOutputBox, Sorting, - }, +use crate::query::{ + parameters::{FetchSize, IterableQueryParams, Pagination, Sorting}, + predicate::{projectors, AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype}, + IterableQuery, IterableQueryBox, IterableQueryOutputBatchBox, IterableQueryWithFilter, + IterableQueryWithFilterFor, IterableQueryWithParams, SingularQueryBox, SingularQueryOutputBox, }; pub trait QueryExecutor { @@ -112,6 +107,7 @@ where #[derive( Debug, + Copy, Clone, PartialEq, Eq, diff --git a/data_model/src/query/cursor.rs b/data_model/src/query/cursor.rs deleted file mode 100644 index 683b642a70d..00000000000 --- a/data_model/src/query/cursor.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! Structures and traits related to server-side cursor. - -#[cfg(not(feature = "std"))] -use alloc::{ - format, - string::{String, ToString as _}, - vec, - vec::Vec, -}; -use core::num::NonZeroU64; - -use getset::Getters; -use iroha_data_model_derive::model; -use iroha_schema::IntoSchema; -use parity_scale_codec::{Decode, Encode, Input}; -use serde::{Deserialize, Serialize}; - -pub use self::model::*; - -/// Unique id of a query -pub type QueryId = String; - -#[model] -mod model { - use super::*; - - /// Forward-only (a.k.a non-scrollable) cursor - #[derive( - Debug, Clone, PartialEq, Eq, Getters, Encode, Decode, Serialize, Deserialize, IntoSchema, - )] - #[getset(get = "pub")] - pub struct ForwardCursor { - /// Unique ID of query. When provided in a query the query will look up if there - /// is was already a query with a matching ID and resume returning result batches - pub query: QueryId, - /// Pointer to the next element in the result set - pub cursor: NonZeroU64, - } -} - -pub mod prelude { - //! Prelude: re-export most commonly used traits, structs and macros from this module. - pub use super::*; -} diff --git a/data_model/src/query/mod.rs b/data_model/src/query/mod.rs index d3bfb2bde99..031b1182e82 100644 --- a/data_model/src/query/mod.rs +++ b/data_model/src/query/mod.rs @@ -1,31 +1,21 @@ //! Iroha Queries provides declarative API for Iroha Queries. -#![allow(clippy::missing_inline_in_public_items, unused_imports)] -#![warn(missing_docs, unused, missing_copy_implementations)] // TODO +#![allow(clippy::missing_inline_in_public_items)] +#![warn(missing_docs, missing_copy_implementations)] // TODO #[cfg(not(feature = "std"))] -use alloc::{ - boxed::Box, - format, - string::{String, ToString}, - vec, - vec::Vec, -}; -use core::num::NonZeroU32; +use alloc::{boxed::Box, format, string::String, vec::Vec}; -pub use cursor::ForwardCursor; -use derive_more::{Constructor, Display}; +use derive_more::Constructor; use iroha_crypto::{PublicKey, SignatureOf}; -use iroha_data_model_derive::{model, EnumRef}; +use iroha_data_model_derive::model; use iroha_macro::FromVariant; -use iroha_primitives::{json::JsonString, numeric::Numeric, small::SmallVec}; +use iroha_primitives::{json::JsonString, numeric::Numeric}; use iroha_schema::IntoSchema; use iroha_version::prelude::*; -use nonzero_ext::nonzero; -pub use pagination::Pagination; +use parameters::{ForwardCursor, IterableQueryParams, MAX_FETCH_SIZE}; use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; -pub use sorting::Sorting; pub use self::model::*; use self::{ @@ -41,76 +31,14 @@ use crate::{ peer::Peer, permission::Permission, role::{Role, RoleId}, - seal, seal::Sealed, transaction::{CommittedTransaction, SignedTransaction}, trigger::TriggerId, - IdBox, Identifiable, IdentifiableBox, }; pub mod builder; -pub mod cursor; -pub mod pagination; +pub mod parameters; pub mod predicate; -pub mod sorting; - -/// Default value for `fetch_size` parameter in queries. -pub const DEFAULT_FETCH_SIZE: NonZeroU32 = nonzero!(10_u32); - -/// Max value for `fetch_size` parameter in queries. -pub const MAX_FETCH_SIZE: NonZeroU32 = nonzero!(10_000_u32); - -/// Structure for query fetch size parameter encoding/decoding -#[derive( - Debug, - Default, - Clone, - Copy, - PartialEq, - Eq, - Constructor, - Decode, - Encode, - Deserialize, - Serialize, - IntoSchema, -)] -pub struct FetchSize { - /// Inner value of a fetch size. - /// - /// If not specified then [`DEFAULT_FETCH_SIZE`] is used. - pub fetch_size: Option, -} - -macro_rules! queries { - ($($($meta:meta)* $item:item)+) => { - pub use self::model::*; - - #[iroha_data_model_derive::model] - mod model{ - use super::*; $( - - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] - #[derive(parity_scale_codec::Decode, parity_scale_codec::Encode)] - #[derive(serde::Deserialize, serde::Serialize)] - #[derive(derive_more::Constructor)] - #[derive(iroha_schema::IntoSchema)] - $($meta)* - $item )+ - } - }; -} - -/// Trait for typesafe query output -pub trait Query: Into + seal::Sealed { - /// Output type of query - type Output: Into + TryFrom; - - /// [`Encode`] [`Self`] as [`QueryBox`]. - /// - /// Used to avoid an unnecessary clone - fn encode_as_query_box(&self) -> Vec; -} /// A [`Query`] that either returns a single value or errors out pub trait SingularQuery: Sealed { @@ -118,69 +46,188 @@ pub trait SingularQuery: Sealed { } /// A [`Query`] that returns an iterable collection of values -pub trait IterableQuery: Query { +pub trait IterableQuery: Sealed { /// A type of single element of the output collection type Item: HasPredicateBox; } -#[derive( - Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, Constructor, -)] -pub struct IterableQueryWithFilter { - pub query: Q, - #[serde(default = "predicate_default")] - pub predicate: CompoundPredicate

, -} +#[model] +mod model { + use getset::Getters; + use iroha_crypto::HashOf; -fn predicate_default

() -> CompoundPredicate

{ - CompoundPredicate::PASS -} + use super::*; + use crate::block::SignedBlock; -pub type IterableQueryWithFilterFor = - IterableQueryWithFilter::Item as HasPredicateBox>::PredicateBoxType>; + #[derive( + Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, Constructor, + )] + pub struct IterableQueryWithFilter { + pub query: Q, + #[serde(default = "predicate_default")] + pub predicate: CompoundPredicate

, + } -#[derive( - Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, FromVariant, -)] -pub enum IterableQueryBox { - FindAllDomains(IterableQueryWithFilterFor), - FindAllAccounts(IterableQueryWithFilterFor), - FindAllAssets(IterableQueryWithFilterFor), - FindAllAssetsDefinitions(IterableQueryWithFilterFor), - FindAllRoles(IterableQueryWithFilterFor), - - FindAllRoleIds(IterableQueryWithFilterFor), - FindPermissionsByAccountId(IterableQueryWithFilterFor), - FindRolesByAccountId(IterableQueryWithFilterFor), - FindTransactionsByAccountId(IterableQueryWithFilterFor), - FindAccountsWithAsset(IterableQueryWithFilterFor), - - FindAllPeers(IterableQueryWithFilterFor), - FindAllActiveTriggerIds(IterableQueryWithFilterFor), - FindAllTransactions(IterableQueryWithFilterFor), - FindAllBlocks(IterableQueryWithFilterFor), - FindAllBlockHeaders(IterableQueryWithFilterFor), -} + fn predicate_default

() -> CompoundPredicate

{ + CompoundPredicate::PASS + } + #[derive( + Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, FromVariant, + )] + pub enum IterableQueryBox { + FindAllDomains(IterableQueryWithFilterFor), + FindAllAccounts(IterableQueryWithFilterFor), + FindAllAssets(IterableQueryWithFilterFor), + FindAllAssetsDefinitions(IterableQueryWithFilterFor), + FindAllRoles(IterableQueryWithFilterFor), + + FindAllRoleIds(IterableQueryWithFilterFor), + FindPermissionsByAccountId(IterableQueryWithFilterFor), + FindRolesByAccountId(IterableQueryWithFilterFor), + FindTransactionsByAccountId(IterableQueryWithFilterFor), + FindAccountsWithAsset(IterableQueryWithFilterFor), + + FindAllPeers(IterableQueryWithFilterFor), + FindAllActiveTriggerIds(IterableQueryWithFilterFor), + FindAllTransactions(IterableQueryWithFilterFor), + FindAllBlocks(IterableQueryWithFilterFor), + FindAllBlockHeaders(IterableQueryWithFilterFor), + } + + #[derive( + Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, FromVariant, + )] + pub enum IterableQueryOutputBatchBox { + Domain(Vec), + Account(Vec), + Asset(Vec), + AssetDefinition(Vec), + Role(Vec), + Parameter(Vec), + Permission(Vec), + Transaction(Vec), + Peer(Vec), + RoleId(Vec), + TriggerId(Vec), + Block(Vec), + BlockHeader(Vec), + } + + #[derive( + Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, FromVariant, + )] + pub enum SingularQueryBox { + FindAssetQuantityById(FindAssetQuantityById), + FindExecutorDataModel(FindExecutorDataModel), + FindAllParameters(FindAllParameters), + FindTotalAssetQuantityByAssetDefinitionId(FindTotalAssetQuantityByAssetDefinitionId), + FindTriggerById(FindTriggerById), + + FindDomainKeyValueByIdAndKey(FindDomainKeyValueByIdAndKey), + FindAccountKeyValueByIdAndKey(FindAccountKeyValueByIdAndKey), + FindAssetKeyValueByIdAndKey(FindAssetKeyValueByIdAndKey), + FindAssetDefinitionKeyValueByIdAndKey(FindAssetDefinitionKeyValueByIdAndKey), + FindTriggerKeyValueByIdAndKey(FindTriggerKeyValueByIdAndKey), + + FindTransactionByHash(FindTransactionByHash), + FindBlockHeaderByHash(FindBlockHeaderByHash), + } + + #[derive( + Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, FromVariant, + )] + pub enum SingularQueryOutputBox { + Numeric(Numeric), + ExecutorDataModel(crate::executor::ExecutorDataModel), + JsonString(JsonString), + Trigger(crate::trigger::Trigger), + Parameters(Parameters), + Transaction(TransactionQueryOutput), + BlockHeader(BlockHeader), + } + + #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] + pub struct IterableQueryOutput { + pub batch: IterableQueryOutputBatchBox, + pub continue_cursor: Option, + } -#[derive( - Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, FromVariant, -)] -pub enum IterableQueryOutputBatchBox { - Domain(Vec), - Account(Vec), - Asset(Vec), - AssetDefinition(Vec), - Role(Vec), - Parameter(Vec), - Permission(Vec), - Transaction(Vec), - Peer(Vec), - RoleId(Vec), - TriggerId(Vec), - Block(Vec), - BlockHeader(Vec), + /// A type-erased iterable query, along with all the + #[derive( + Debug, Clone, PartialEq, Eq, Constructor, Decode, Encode, Deserialize, Serialize, IntoSchema, + )] + pub struct IterableQueryWithParams { + pub query: IterableQueryBox, + #[serde(default)] + pub params: IterableQueryParams, + } + + #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] + pub enum QueryRequest { + Singular(SingularQueryBox), + StartIterable(IterableQueryWithParams), + ContinueIterable(ForwardCursor), + } + + #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] + pub enum QueryBox { + Singular(SingularQueryBox), + Iterable(IterableQueryWithParams), + } + + #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] + pub enum QueryResponse { + Singular(SingularQueryOutputBox), + Iterable(IterableQueryOutput), + } + + #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] + pub struct QueryRequestWithAuthority { + pub authority: AccountId, + pub request: QueryRequest, + } + + #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] + pub struct QuerySignature(pub SignatureOf); + + declare_versioned!(SignedQuery 1..2, Debug, Clone, FromVariant, IntoSchema); + + #[derive(Debug, Clone, Encode, Serialize, IntoSchema)] + #[version_with_scale(version = 1, versioned_alias = "SignedQuery")] + pub struct SignedQueryV1 { + pub signature: QuerySignature, + pub payload: QueryRequestWithAuthority, + } + + /// Output of [`FindAllTransactions`] query + #[derive( + Debug, + Clone, + PartialOrd, + Ord, + PartialEq, + Eq, + Getters, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + #[getset(get = "pub")] + #[ffi_type] + pub struct TransactionQueryOutput { + /// The hash of the block to which `tx` belongs to + pub block_hash: HashOf, + /// Transaction + #[getset(skip)] + pub transaction: CommittedTransaction, + } } +pub type IterableQueryWithFilterFor = + IterableQueryWithFilter::Item as HasPredicateBox>::PredicateBoxType>; + impl IterableQueryOutputBatchBox { // this is used in client cli to do type-erased iterable queries pub fn extend(&mut self, other: IterableQueryOutputBatchBox) { @@ -221,51 +268,10 @@ impl IterableQueryOutputBatchBox { } } -#[derive( - Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, FromVariant, -)] -pub enum SingularQueryBox { - FindAssetQuantityById(FindAssetQuantityById), - FindExecutorDataModel(FindExecutorDataModel), - FindAllParameters(FindAllParameters), - FindTotalAssetQuantityByAssetDefinitionId(FindTotalAssetQuantityByAssetDefinitionId), - FindTriggerById(FindTriggerById), - - FindDomainKeyValueByIdAndKey(FindDomainKeyValueByIdAndKey), - FindAccountKeyValueByIdAndKey(FindAccountKeyValueByIdAndKey), - FindAssetKeyValueByIdAndKey(FindAssetKeyValueByIdAndKey), - FindAssetDefinitionKeyValueByIdAndKey(FindAssetDefinitionKeyValueByIdAndKey), - FindTriggerKeyValueByIdAndKey(FindTriggerKeyValueByIdAndKey), - - FindTransactionByHash(FindTransactionByHash), - FindBlockHeaderByHash(FindBlockHeaderByHash), -} - -#[derive( - Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, FromVariant, -)] -pub enum SingularQueryOutputBox { - Numeric(Numeric), - ExecutorDataModel(crate::executor::ExecutorDataModel), - JsonString(JsonString), - Trigger(crate::trigger::Trigger), - Parameters(Parameters), - Transaction(TransactionQueryOutput), - BlockHeader(BlockHeader), -} - -impl Sealed for SingularQueryBox {} - impl SingularQuery for SingularQueryBox { type Output = SingularQueryOutputBox; } -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub struct IterableQueryOutput { - pub batch: IterableQueryOutputBatchBox, - pub continue_cursor: Option, -} - impl IterableQueryOutput { pub fn new(batch: IterableQueryOutputBatchBox, continue_cursor: Option) -> Self { Self { @@ -279,37 +285,7 @@ impl IterableQueryOutput { } } -#[derive( - Debug, Clone, PartialEq, Eq, Default, Decode, Encode, Deserialize, Serialize, IntoSchema, -)] -pub struct IterableQueryParams { - pub pagination: Pagination, - pub sorting: Sorting, - pub fetch_size: FetchSize, -} - -/// A type-erased iterable query, along with all the -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub struct IterableQueryWithParams { - pub query: IterableQueryBox, - #[serde(default)] - pub params: IterableQueryParams, -} - -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum QueryRequest2 { - Singular(SingularQueryBox), - StartIterable(IterableQueryWithParams), - ContinueIterable(ForwardCursor), -} - -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum QueryBox2 { - Singular(SingularQueryBox), - Iterable(IterableQueryWithParams), -} - -impl QueryRequest2 { +impl QueryRequest { pub fn with_authority(self, authority: AccountId) -> QueryRequestWithAuthority { QueryRequestWithAuthority { authority, @@ -318,25 +294,13 @@ impl QueryRequest2 { } } -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub enum QueryResponse2 { - Singular(SingularQueryOutputBox), - Iterable(IterableQueryOutput), -} - -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub struct QueryRequestWithAuthority { - pub authority: AccountId, - pub request: QueryRequest2, -} - impl QueryRequestWithAuthority { #[inline] #[must_use] - pub fn sign(self, key_pair: &iroha_crypto::KeyPair) -> SignedQuery2 { + pub fn sign(self, key_pair: &iroha_crypto::KeyPair) -> SignedQuery { let signature = SignatureOf::new(key_pair.private_key(), &self); - SignedQuery2V1 { + SignedQueryV1 { signature: QuerySignature(signature), payload: self, } @@ -344,516 +308,127 @@ impl QueryRequestWithAuthority { } } -#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] -pub struct QuerySignature(pub SignatureOf); - -declare_versioned!(SignedQuery2 1..2, Debug, Clone, FromVariant, IntoSchema); - -impl SignedQuery2 { +impl SignedQuery { pub fn authority(&self) -> &AccountId { - let SignedQuery2::V1(query) = self; + let SignedQuery::V1(query) = self; &query.payload.authority } - pub fn request(&self) -> &QueryRequest2 { - let SignedQuery2::V1(query) = self; + pub fn request(&self) -> &QueryRequest { + let SignedQuery::V1(query) = self; &query.payload.request } } -#[derive(Debug, Clone, Encode, Serialize, IntoSchema)] -#[version_with_scale(version = 1, versioned_alias = "SignedQuery2")] -pub struct SignedQuery2V1 { - pub signature: QuerySignature, - pub payload: QueryRequestWithAuthority, -} - mod candidate { use parity_scale_codec::Input; use super::*; #[derive(Decode, Deserialize)] - struct SignedQuery2Candidate { + struct SignedQueryCandidate { signature: QuerySignature, payload: QueryRequestWithAuthority, } - impl SignedQuery2Candidate { - fn validate(self) -> Result { + impl SignedQueryCandidate { + fn validate(self) -> Result { let QuerySignature(signature) = &self.signature; signature .verify(&self.payload.authority.signatory, &self.payload) .map_err(|_| "Query signature is not valid")?; - Ok(SignedQuery2V1 { + Ok(SignedQueryV1 { payload: self.payload, signature: self.signature, }) } } - impl Decode for SignedQuery2V1 { + impl Decode for SignedQueryV1 { fn decode(input: &mut I) -> Result { - SignedQuery2Candidate::decode(input)? + SignedQueryCandidate::decode(input)? .validate() .map_err(Into::into) } } - impl<'de> Deserialize<'de> for SignedQuery2V1 { + impl<'de> Deserialize<'de> for SignedQueryV1 { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { use serde::de::Error as _; - SignedQuery2Candidate::deserialize(deserializer)? + SignedQueryCandidate::deserialize(deserializer)? .validate() .map_err(D::Error::custom) } } } -#[model] -mod model { - use getset::Getters; - use iroha_crypto::HashOf; - use iroha_macro::FromVariant; - use strum::EnumDiscriminants; - - use super::*; - use crate::block::SignedBlock; - - /// Sized container for all possible Queries. - #[allow(clippy::enum_variant_names)] - #[derive( - Debug, - Display, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - EnumRef, - EnumDiscriminants, - FromVariant, - Decode, - Encode, - Deserialize, - Serialize, - IntoSchema, - )] - #[enum_ref(derive(Encode, FromVariant))] - #[strum_discriminants( - vis(pub(super)), - name(QueryType), - derive(Encode), - allow(clippy::enum_variant_names) - )] - #[ffi_type] - #[allow(missing_docs)] - pub enum QueryBox { - FindAllAccounts(FindAllAccounts), - FindAccountById(FindAccountById), - FindAccountKeyValueByIdAndKey(FindAccountKeyValueByIdAndKey), - FindAccountsByDomainId(FindAccountsByDomainId), - FindAccountsWithAsset(FindAccountsWithAsset), - FindAllAssets(FindAllAssets), - FindAllAssetsDefinitions(FindAllAssetsDefinitions), - FindAssetById(FindAssetById), - FindAssetDefinitionById(FindAssetDefinitionById), - FindAssetsByName(FindAssetsByName), - FindAssetsByAccountId(FindAssetsByAccountId), - FindAssetsByAssetDefinitionId(FindAssetsByAssetDefinitionId), - FindAssetsByDomainId(FindAssetsByDomainId), - FindAssetsByDomainIdAndAssetDefinitionId(FindAssetsByDomainIdAndAssetDefinitionId), - FindAssetQuantityById(FindAssetQuantityById), - FindTotalAssetQuantityByAssetDefinitionId(FindTotalAssetQuantityByAssetDefinitionId), - FindAssetKeyValueByIdAndKey(FindAssetKeyValueByIdAndKey), - FindAssetDefinitionKeyValueByIdAndKey(FindAssetDefinitionKeyValueByIdAndKey), - FindAllDomains(FindAllDomains), - FindDomainById(FindDomainById), - FindDomainKeyValueByIdAndKey(FindDomainKeyValueByIdAndKey), - FindAllPeers(FindAllPeers), - FindAllBlocks(FindAllBlocks), - FindAllBlockHeaders(FindAllBlockHeaders), - FindBlockHeaderByHash(FindBlockHeaderByHash), - FindAllTransactions(FindAllTransactions), - FindTransactionsByAccountId(FindTransactionsByAccountId), - FindTransactionByHash(FindTransactionByHash), - FindPermissionsByAccountId(FindPermissionsByAccountId), - FindExecutorDataModel(FindExecutorDataModel), - FindAllActiveTriggerIds(FindAllActiveTriggerIds), - FindTriggerById(FindTriggerById), - FindTriggerKeyValueByIdAndKey(FindTriggerKeyValueByIdAndKey), - FindTriggersByAuthorityId(FindTriggersByAuthorityId), - FindTriggersByAuthorityDomainId(FindTriggersByAuthorityDomainId), - FindAllRoles(FindAllRoles), - FindAllRoleIds(FindAllRoleIds), - FindRoleByRoleId(FindRoleByRoleId), - FindRolesByAccountId(FindRolesByAccountId), - FindAllParameters(FindAllParameters), - } - - /// Sized container for all possible [`Query::Output`]s - #[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - FromVariant, - Decode, - Encode, - Deserialize, - Serialize, - IntoSchema, - )] - #[allow(missing_docs)] - pub enum QueryOutputBox { - Id(IdBox), - Identifiable(IdentifiableBox), - Transaction(TransactionQueryOutput), - Permission(crate::permission::Permission), - Parameters(crate::parameter::Parameters), - Metadata(JsonString), - Numeric(Numeric), - BlockHeader(BlockHeader), - Block(crate::block::SignedBlock), - ExecutorDataModel(crate::executor::ExecutorDataModel), - - Vec( - #[skip_from] - #[skip_try_from] - Vec, - ), - } - - /// Output of [`FindAllTransactions`] query - #[derive( - Debug, - Clone, - PartialOrd, - Ord, - PartialEq, - Eq, - Getters, - Decode, - Encode, - Deserialize, - Serialize, - IntoSchema, - )] - #[getset(get = "pub")] - #[ffi_type] - pub struct TransactionQueryOutput { - /// The hash of the block to which `tx` belongs to - pub block_hash: HashOf, - /// Transaction - #[getset(skip)] - pub transaction: CommittedTransaction, - } - - /// Request type clients (like http clients or wasm) can send to a query endpoint. - /// - /// `Q` should be either [`http::SignedQuery`] for client or [`SmartContractQuery`] for wasm smart contract. - // NOTE: if updating, also update the `iroha_smart_contract::QueryRequest` and its encoding - #[derive(Debug, Clone, Encode, Decode, Serialize, Deserialize)] - pub enum QueryRequest { - /// Query it-self. - /// Basically used for one-time queries or to get a cursor for big queries. - Query(Q), - /// Cursor over previously sent [`Query`](QueryRequest::Query). - Cursor(ForwardCursor), - } - - /// A query with parameters, as coming from a smart contract. - // NOTE: if updating, also update the `iroha_smart_contract::SmartContractQuery` and its encoding - #[derive( - Debug, - Clone, - PartialEq, - Eq, - Decode, - Encode, - Deserialize, - Serialize, - IntoSchema, - Constructor, - Getters, - )] - #[getset(get = "pub")] - pub struct SmartContractQuery { - /// Query definition. - pub query: QueryBox, - /// The filter applied to the result on the server-side. - pub filter: PredicateBox, - /// Sorting of the query results. - pub sorting: Sorting, - /// Pagination of the query results. - pub pagination: Pagination, - /// Amount of results to fetch. - pub fetch_size: FetchSize, - } -} - -impl From for QueryOutputBox { - fn from(value: u32) -> Self { - Self::Numeric(value.into()) - } -} - -impl From for QueryOutputBox { - fn from(value: u64) -> Self { - Self::Numeric(value.into()) - } -} - -impl TryFrom for u32 { - type Error = iroha_macro::error::ErrorTryFromEnum; - - fn try_from(value: QueryOutputBox) -> Result { - use iroha_macro::error::ErrorTryFromEnum; - - let QueryOutputBox::Numeric(numeric) = value else { - return Err(ErrorTryFromEnum::default()); - }; - - numeric.try_into().map_err(|_| ErrorTryFromEnum::default()) - } -} - -impl TryFrom for u64 { - type Error = iroha_macro::error::ErrorTryFromEnum; - - fn try_from(value: QueryOutputBox) -> Result { - use iroha_macro::error::ErrorTryFromEnum; - - let QueryOutputBox::Numeric(numeric) = value else { - return Err(ErrorTryFromEnum::default()); - }; - - numeric.try_into().map_err(|_| ErrorTryFromEnum::default()) - } -} - -/// Uses custom syntax to implement query-related traits on query types -/// -/// Implements [`Query`] and, additionally, either [`SingularQuery`] or [`IterableQuery`], -/// depending on whether the output type is wrapped into a Vec (purely syntactically) -macro_rules! impl_queries { - // we can't delegate matching over `Vec<$item:ty>` to an inner macro, - // as the moment a fragment is matched as `$output:ty` it becomes opaque and unmatchable to any literal - // https://doc.rust-lang.org/nightly/reference/macros-by-example.html#forwarding-a-matched-fragment - // hence we match at the top level with a tt-muncher and a use `@impl_query` inner macro to avoid duplication of the `impl Query` - ($ty:ty => Vec<$item:ty> $(, $($rest:tt)*)?) => { - impl_queries!(@impl_query $ty => Vec<$item>); - +/// Use a custom syntax to implement [`IterableQuery`] for applicable types +macro_rules! impl_iter_queries { + ($ty:ty => $item:ty $(, $($rest:tt)*)?) => { impl IterableQuery for $ty { type Item = $item; } $( - impl_queries!($($rest)*); + impl_iter_queries!($($rest)*); )? }; - ($ty:ty => $output:ty $(, $($rest:tt)*)?) => { - impl_queries!(@impl_query $ty => $output); + // allow for a trailing comma + () => {} +} +/// Use a custom syntax to implement [`SingularQueries`] for applicable types +macro_rules! impl_singular_queries { + ($ty:ty => $output:ty $(, $($rest:tt)*)?) => { impl SingularQuery for $ty { type Output = $output; } $( - impl_queries!($($rest)*); + impl_singular_queries!($($rest)*); )? }; - (@impl_query $ty:ty => $output:ty) =>{ - impl Query for $ty { - type Output = $output; - - fn encode_as_query_box(&self) -> Vec { - QueryBoxRef::from(self).encode() - } - } - }; -} - -impl_queries! { - FindAllRoles => Vec, - FindAllRoleIds => Vec, - FindRolesByAccountId => Vec, - FindRoleByRoleId => crate::role::Role, - FindPermissionsByAccountId => Vec, - FindAllAccounts => Vec, - FindAccountById => crate::account::Account, + // allow for a trailing comma + () => {} +} + +impl_iter_queries! { + FindAllRoles => crate::role::Role, + FindAllRoleIds => crate::role::RoleId, + FindRolesByAccountId => crate::role::RoleId, + FindPermissionsByAccountId => crate::permission::Permission, + FindAllAccounts => crate::account::Account, + FindAllAssets => crate::asset::Asset, + FindAllAssetsDefinitions => crate::asset::AssetDefinition, + FindAllDomains => crate::domain::Domain, + FindAllPeers => crate::peer::Peer, + FindAllActiveTriggerIds => crate::trigger::TriggerId, + FindAllTransactions => TransactionQueryOutput, + FindTransactionsByAccountId => TransactionQueryOutput, + FindAccountsWithAsset => crate::account::Account, + FindAllBlockHeaders => crate::block::BlockHeader, + FindAllBlocks => SignedBlock, +} + +impl_singular_queries! { FindAccountKeyValueByIdAndKey => JsonString, - FindAccountsByDomainId => Vec, - FindAccountsWithAsset => Vec, - FindAllAssets => Vec, - FindAllAssetsDefinitions => Vec, - FindAssetById => crate::asset::Asset, - FindAssetDefinitionById => crate::asset::AssetDefinition, - FindAssetsByName => Vec, - FindAssetsByAccountId => Vec, - FindAssetsByAssetDefinitionId => Vec, - FindAssetsByDomainId => Vec, - FindAssetsByDomainIdAndAssetDefinitionId => Vec, FindAssetQuantityById => Numeric, FindTotalAssetQuantityByAssetDefinitionId => Numeric, FindAssetKeyValueByIdAndKey => JsonString, FindAssetDefinitionKeyValueByIdAndKey => JsonString, - FindAllDomains => Vec, - FindDomainById => crate::domain::Domain, FindDomainKeyValueByIdAndKey => JsonString, - FindAllPeers => Vec, FindAllParameters => crate::parameter::Parameters, - FindAllActiveTriggerIds => Vec, FindTriggerById => crate::trigger::Trigger, FindTriggerKeyValueByIdAndKey => JsonString, - FindTriggersByAuthorityId => Vec, - FindTriggersByAuthorityDomainId => Vec, - FindAllTransactions => Vec, - FindTransactionsByAccountId => Vec, FindTransactionByHash => TransactionQueryOutput, - FindAllBlocks => Vec, - FindAllBlockHeaders => Vec, FindBlockHeaderByHash => crate::block::BlockHeader, - FindExecutorDataModel => crate::executor::ExecutorDataModel -} - -impl Query for QueryBox { - type Output = QueryOutputBox; - - fn encode_as_query_box(&self) -> Vec { - self.encode() - } -} - -impl core::fmt::Display for QueryOutputBox { - // TODO: Maybe derive - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Id(v) => core::fmt::Display::fmt(&v, f), - Self::Identifiable(v) => core::fmt::Display::fmt(&v, f), - Self::Transaction(_) => write!(f, "TransactionQueryOutput"), - Self::Permission(v) => core::fmt::Display::fmt(&v, f), - Self::Parameters(v) => core::fmt::Debug::fmt(&v, f), - Self::Block(v) => core::fmt::Display::fmt(&v, f), - Self::BlockHeader(v) => core::fmt::Display::fmt(&v, f), - Self::Numeric(v) => core::fmt::Display::fmt(&v, f), - Self::Metadata(v) => core::fmt::Display::fmt(&v, f), - Self::ExecutorDataModel(v) => core::fmt::Display::fmt(&v, f), - - Self::Vec(v) => { - // TODO: Remove so we can derive. - let list_of_display: Vec<_> = v.iter().map(ToString::to_string).collect(); - // this prints with quotation marks, which is fine 90% - // of the time, and helps delineate where a display of - // one value stops and another one begins. - write!(f, "{list_of_display:?}") - } - } - } -} - -// TODO: The following macros looks very similar. Try to generalize them under one macro -macro_rules! from_and_try_from_value_idbox { - ( $($variant:ident( $ty:ty ),)+ $(,)? ) => { $( - impl TryFrom for $ty { - type Error = iroha_macro::error::ErrorTryFromEnum; - - fn try_from(value: QueryOutputBox) -> Result { - if let QueryOutputBox::Id(IdBox::$variant(id)) = value { - Ok(id) - } else { - Err(Self::Error::default()) - } - } - } - - impl From<$ty> for QueryOutputBox { - fn from(id: $ty) -> Self { - Self::Id(IdBox::$variant(id)) - } - })+ - }; -} - -macro_rules! from_and_try_from_value_identifiable { - ( $( $variant:ident( $ty:ty ), )+ $(,)? ) => { $( - impl TryFrom for $ty { - type Error = iroha_macro::error::ErrorTryFromEnum; - - fn try_from(value: QueryOutputBox) -> Result { - if let QueryOutputBox::Identifiable(IdentifiableBox::$variant(id)) = value { - Ok(id) - } else { - Err(Self::Error::default()) - } - } - } - - impl From<$ty> for QueryOutputBox { - fn from(id: $ty) -> Self { - Self::Identifiable(IdentifiableBox::$variant(id)) - } - } )+ - }; -} - -from_and_try_from_value_idbox!( - PeerId(crate::peer::PeerId), - DomainId(crate::domain::DomainId), - AccountId(crate::account::AccountId), - AssetId(crate::asset::AssetId), - AssetDefinitionId(crate::asset::AssetDefinitionId), - TriggerId(crate::trigger::TriggerId), - RoleId(crate::role::RoleId), - // TODO: Should we wrap String with new type in order to convert like here? - //from_and_try_from_value_idbox!((DomainName(Name), ErrorValueTryFromDomainName),); -); - -from_and_try_from_value_identifiable!( - NewDomain(crate::domain::NewDomain), - NewAccount(crate::account::NewAccount), - NewAssetDefinition(crate::asset::NewAssetDefinition), - NewRole(crate::role::NewRole), - Peer(crate::peer::Peer), - Domain(crate::domain::Domain), - Account(crate::account::Account), - AssetDefinition(crate::asset::AssetDefinition), - Asset(crate::asset::Asset), - Trigger(crate::trigger::Trigger), - Role(crate::role::Role), -); - -impl> From> for QueryOutputBox { - fn from(values: Vec) -> Self { - Self::Vec(values.into_iter().map(Into::into).collect()) - } -} - -impl TryFrom for Vec -where - QueryOutputBox: TryInto, -{ - type Error = iroha_macro::error::ErrorTryFromEnum; - - fn try_from(value: QueryOutputBox) -> Result { - if let QueryOutputBox::Vec(vec) = value { - return vec - .into_iter() - .map(TryInto::try_into) - .collect::, _>>() - .map_err(|_e| Self::Error::default()); - } - - Err(Self::Error::default()) - } + FindExecutorDataModel => crate::executor::ExecutorDataModel, } impl AsRef for TransactionQueryOutput { @@ -862,25 +437,23 @@ impl AsRef for TransactionQueryOutput { } } -impl core::fmt::Display for QueryRequest { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Query(query) => write!(f, "{query}"), - Self::Cursor(cursor) => write!(f, "{cursor:?}"), - } - } -} +macro_rules! queries { + ($($($meta:meta)* $item:item)+) => { + pub use self::model::*; -impl core::fmt::Display for SmartContractQuery { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("QueryWithParameters") - .field("query", &self.query.to_string()) - .field("filter", &self.filter) - .field("sorting", &self.sorting) - .field("pagination", &self.pagination) - .field("fetch_size", &self.fetch_size) - .finish() - } + #[iroha_data_model_derive::model] + mod model{ + use super::*; $( + + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] + #[derive(parity_scale_codec::Decode, parity_scale_codec::Encode)] + #[derive(serde::Deserialize, serde::Serialize)] + #[derive(derive_more::Constructor)] + #[derive(iroha_schema::IntoSchema)] + $($meta)* + $item )+ + } + }; } pub mod role { @@ -890,9 +463,7 @@ pub mod role { use alloc::{format, string::String, vec::Vec}; use derive_more::Display; - use parity_scale_codec::Encode; - use super::Query; use crate::prelude::*; queries! { @@ -909,18 +480,6 @@ pub mod role { #[ffi_type] pub struct FindAllRoleIds; - - /// [`FindRoleByRoleId`] Iroha Query finds the [`Role`] which has the given [`Id`](crate::RoleId) - #[derive(Display)] - #[display(fmt = "Find `{id}` role")] - #[repr(transparent)] - // SAFETY: `FindRoleByRoleId` has no trap representation in `EvaluatesTo` - #[ffi_type(unsafe {robust})] - pub struct FindRoleByRoleId { - /// `Id` of the [`Role`] to find - pub id: RoleId, - } - /// [`FindRolesByAccountId`] Iroha Query finds all [`Role`]s for a specified account. #[derive(Display)] #[display(fmt = "Find all roles for `{id}` account")] @@ -935,7 +494,7 @@ pub mod role { /// The prelude re-exports most commonly used traits, structs and macros from this module. pub mod prelude { - pub use super::{FindAllRoleIds, FindAllRoles, FindRoleByRoleId, FindRolesByAccountId}; + pub use super::{FindAllRoleIds, FindAllRoles, FindRolesByAccountId}; } } @@ -946,9 +505,7 @@ pub mod permission { use alloc::{format, string::String, vec::Vec}; use derive_more::Display; - use parity_scale_codec::Encode; - use super::Query; use crate::prelude::*; queries! { @@ -978,9 +535,7 @@ pub mod account { use alloc::{format, string::String, vec::Vec}; use derive_more::Display; - use parity_scale_codec::Encode; - use super::{JsonString, Query}; use crate::prelude::*; queries! { @@ -991,17 +546,6 @@ pub mod account { #[ffi_type] pub struct FindAllAccounts; - /// [`FindAccountById`] Iroha Query finds an [`Account`] by it's identification. - #[derive(Display)] - #[display(fmt = "Find `{id}` account")] - #[repr(transparent)] - // SAFETY: `FindAccountById` has no trap representation in `EvaluatesTo` - #[ffi_type(unsafe {robust})] - pub struct FindAccountById { - /// `Id` of an account to find. - pub id: AccountId, - } - /// [`FindAccountKeyValueByIdAndKey`] Iroha Query finds an [`MetadataValue`] /// of the key-value metadata pair in the specified account. #[derive(Display)] @@ -1014,18 +558,6 @@ pub mod account { pub key: Name, } - /// [`FindAccountsByDomainId`] Iroha Query gets [`Domain`]s id as input and - /// finds all [`Account`]s under this [`Domain`]. - #[derive(Display)] - #[display(fmt = "Find accounts under `{domain}` domain")] - #[repr(transparent)] - // SAFETY: `FindAccountsByDomainId` has no trap representation in `EvaluatesTo` - #[ffi_type(unsafe {robust})] - pub struct FindAccountsByDomainId { - /// `Id` of the domain under which accounts should be found. - pub domain: DomainId, - } - /// [`FindAccountsWithAsset`] Iroha Query gets [`AssetDefinition`]s id as input and /// finds all [`Account`]s storing [`Asset`] with such definition. #[derive(Display)] @@ -1041,10 +573,7 @@ pub mod account { /// The prelude re-exports most commonly used traits, structs and macros from this crate. pub mod prelude { - pub use super::{ - FindAccountById, FindAccountKeyValueByIdAndKey, FindAccountsByDomainId, - FindAccountsWithAsset, FindAllAccounts, - }; + pub use super::{FindAccountKeyValueByIdAndKey, FindAccountsWithAsset, FindAllAccounts}; } } @@ -1057,9 +586,7 @@ pub mod asset { use alloc::{format, string::String, vec::Vec}; use derive_more::Display; - use parity_scale_codec::Encode; - use super::{JsonString, Query}; use crate::prelude::*; queries! { @@ -1076,89 +603,6 @@ pub mod asset { #[ffi_type] pub struct FindAllAssetsDefinitions; // TODO: Should it be renamed to [`FindAllAssetDefinitions`? - /// [`FindAssetById`] Iroha Query finds an [`Asset`] by it's identification in Iroha [`Peer`]. - #[derive(Display)] - #[display(fmt = "Find `{id}` asset")] - #[repr(transparent)] - // SAFETY: `FindAssetById` has no trap representation in `EvaluatesTo` - #[ffi_type(unsafe {robust})] - pub struct FindAssetById { - /// `Id` of an [`Asset`] to find. - pub id: AssetId, - } - - /// [`FindAssetDefinitionById`] Iroha Query finds an [`AssetDefinition`] by it's identification in Iroha [`Peer`]. - #[derive(Display)] - #[display(fmt = "Find `{id}` asset definition")] - #[repr(transparent)] - // SAFETY: `FindAssetDefinitionById` has no trap representation in `EvaluatesTo` - #[ffi_type(unsafe {robust})] - pub struct FindAssetDefinitionById { - /// `Id` of an [`AssetDefinition`] to find. - pub id: AssetDefinitionId, - } - - /// [`FindAssetsByName`] Iroha Query gets [`Asset`]s name as input and - /// finds all [`Asset`]s with it in Iroha [`Peer`]. - #[derive(Display)] - #[display(fmt = "Find asset with `{name}` name")] - #[repr(transparent)] - // SAFETY: `FindAssetsByName` has no trap representation in `EvaluatesTo` - #[ffi_type(unsafe {robust})] - pub struct FindAssetsByName { - /// [`Name`] of [`Asset`]s to find. - pub name: Name, - } - - /// [`FindAssetsByAccountId`] Iroha Query gets [`AccountId`] as input and find all [`Asset`]s - /// owned by the [`Account`] in Iroha Peer. - #[derive(Display)] - #[display(fmt = "Find assets owned by the `{account}` account")] - #[repr(transparent)] - // SAFETY: `FindAssetsByAccountId` has no trap representation in `EvaluatesTo` - #[ffi_type(unsafe {robust})] - pub struct FindAssetsByAccountId { - /// [`AccountId`] under which assets should be found. - pub account: AccountId, - } - - /// [`FindAssetsByAssetDefinitionId`] Iroha Query gets [`AssetDefinitionId`] as input and - /// finds all [`Asset`]s with this [`AssetDefinition`] in Iroha Peer. - #[derive(Display)] - #[display(fmt = "Find assets with `{asset_definition}` asset definition")] - #[repr(transparent)] - // SAFETY: `FindAssetsByAssetDefinitionId` has no trap representation in `EvaluatesTo` - #[ffi_type(unsafe {robust})] - pub struct FindAssetsByAssetDefinitionId { - /// [`AssetDefinitionId`] with type of [`Asset`]s should be found. - pub asset_definition: AssetDefinitionId, - } - - /// [`FindAssetsByDomainId`] Iroha Query gets [`Domain`]s id as input and - /// finds all [`Asset`]s under this [`Domain`] in Iroha [`Peer`]. - #[derive(Display)] - #[display(fmt = "Find assets under the `{domain}` domain")] - #[repr(transparent)] - // SAFETY: `FindAssetsByDomainId` has no trap representation in `EvaluatesTo` - #[ffi_type(unsafe {robust})] - pub struct FindAssetsByDomainId { - /// `Id` of the domain under which assets should be found. - pub domain: DomainId, - } - - /// [`FindAssetsByDomainIdAndAssetDefinitionId`] Iroha Query gets [`DomainId`] and - /// [`AssetDefinitionId`] as inputs and finds [`Asset`]s under the [`Domain`] - /// with this [`AssetDefinition`] in Iroha [`Peer`]. - #[derive(Display)] - #[display(fmt = "Find assets under the `{domain}` domain with `{asset_definition}` asset definition")] - #[ffi_type] - pub struct FindAssetsByDomainIdAndAssetDefinitionId { - /// `Id` of the domain under which assets should be found. - pub domain: DomainId, - /// [`AssetDefinitionId`] assets of which type should be found. - pub asset_definition: AssetDefinitionId, - } - /// [`FindAssetQuantityById`] Iroha Query gets [`AssetId`] as input and finds [`Asset::quantity`] /// value if [`Asset`] is presented in Iroha Peer. #[derive(Display)] @@ -1212,10 +656,8 @@ pub mod asset { /// The prelude re-exports most commonly used traits, structs and macros from this crate. pub mod prelude { pub use super::{ - FindAllAssets, FindAllAssetsDefinitions, FindAssetById, FindAssetDefinitionById, - FindAssetDefinitionKeyValueByIdAndKey, FindAssetKeyValueByIdAndKey, - FindAssetQuantityById, FindAssetsByAccountId, FindAssetsByAssetDefinitionId, - FindAssetsByDomainId, FindAssetsByDomainIdAndAssetDefinitionId, FindAssetsByName, + FindAllAssets, FindAllAssetsDefinitions, FindAssetDefinitionKeyValueByIdAndKey, + FindAssetKeyValueByIdAndKey, FindAssetQuantityById, FindTotalAssetQuantityByAssetDefinitionId, }; } @@ -1230,9 +672,7 @@ pub mod domain { use alloc::{format, string::String, vec::Vec}; use derive_more::Display; - use parity_scale_codec::Encode; - use super::{JsonString, Query}; use crate::prelude::*; queries! { @@ -1242,17 +682,6 @@ pub mod domain { #[ffi_type] pub struct FindAllDomains; - /// [`FindDomainById`] Iroha Query finds a [`Domain`] by it's identification in Iroha [`Peer`]. - #[derive(Display)] - #[display(fmt = "Find `{id}` domain")] - #[repr(transparent)] - // SAFETY: `FindTotalAssetQuantityByAssetDefinitionId` has no trap representation in `EvaluatesTo` - #[ffi_type(unsafe {robust})] - pub struct FindDomainById { - /// `Id` of the domain to find. - pub id: DomainId, - } - /// [`FindDomainKeyValueByIdAndKey`] Iroha Query finds a [`MetadataValue`] of the key-value metadata pair /// in the specified domain. #[derive(Display)] @@ -1268,7 +697,7 @@ pub mod domain { /// The prelude re-exports most commonly used traits, structs and macros from this crate. pub mod prelude { - pub use super::{FindAllDomains, FindDomainById, FindDomainKeyValueByIdAndKey}; + pub use super::{FindAllDomains, FindDomainKeyValueByIdAndKey}; } } @@ -1279,9 +708,6 @@ pub mod peer { use alloc::{format, string::String, vec::Vec}; use derive_more::Display; - use parity_scale_codec::Encode; - - use super::Query; queries! { /// [`FindAllPeers`] Iroha Query finds all trusted [`Peer`]s presented in current Iroha [`Peer`]. @@ -1331,10 +757,8 @@ pub mod trigger { use alloc::{format, string::String, vec::Vec}; use derive_more::Display; - use parity_scale_codec::Encode; - use super::{JsonString, Query}; - use crate::{account::AccountId, domain::prelude::*, trigger::TriggerId, Identifiable, Name}; + use crate::{trigger::TriggerId, Name}; queries! { /// Find all currently active (as in not disabled and/or expired) @@ -1365,36 +789,11 @@ pub mod trigger { /// The key inside the metadata dictionary to be returned. pub key: Name, } - - /// Find all triggers executable on behalf of the given account. - #[derive(Display)] - #[display(fmt = "Find triggers executable on behalf of the `{account}` account")] - #[repr(transparent)] - // SAFETY: `FindTriggersByAuthorityId` has no trap representation in `EvaluatesTo` - #[ffi_type(unsafe {robust})] - pub struct FindTriggersByAuthorityId { - /// [`AccountId`] specifies the authority behind the trigger execution. - pub account: AccountId, - } - - /// Find all triggers whose authority belongs to the given domain. - #[derive(Display)] - #[display(fmt = "Find triggers with authority under `{domain}` domain")] - #[repr(transparent)] - // SAFETY: `FindTriggersByAuthorityDomainId` has no trap representation in `EvaluatesTo` - #[ffi_type(unsafe {robust})] - pub struct FindTriggersByAuthorityDomainId { - /// [`DomainId`] specifies the domain in which to search for triggers. - pub domain: DomainId, - } } pub mod prelude { //! Prelude Re-exports most commonly used traits, structs and macros from this crate. - pub use super::{ - FindAllActiveTriggerIds, FindTriggerById, FindTriggerKeyValueByIdAndKey, - FindTriggersByAuthorityDomainId, FindTriggersByAuthorityId, - }; + pub use super::{FindAllActiveTriggerIds, FindTriggerById, FindTriggerKeyValueByIdAndKey}; } } @@ -1408,9 +807,7 @@ pub mod transaction { use derive_more::Display; use iroha_crypto::HashOf; - use parity_scale_codec::Encode; - use super::Query; use crate::{account::AccountId, transaction::SignedTransaction}; queries! { @@ -1461,9 +858,8 @@ pub mod block { use derive_more::Display; use iroha_crypto::HashOf; - use parity_scale_codec::{Decode, Encode}; - use super::{Query, SignedBlock}; + use super::SignedBlock; queries! { /// [`FindAllBlocks`] Iroha Query lists all blocks sorted by @@ -1498,258 +894,6 @@ pub mod block { } } -#[cfg(feature = "http")] -pub mod http { - //! Structures related to sending queries over HTTP - - use iroha_crypto::SignatureOf; - use iroha_data_model_derive::model; - use iroha_schema::IntoSchema; - use iroha_version::{declare_versioned, version_with_scale}; - use parity_scale_codec::{Decode, Encode}; - use serde::{Deserialize, Serialize}; - - pub use self::model::*; - use super::{ - predicate::PredicateBox, FetchSize, ForwardCursor, Pagination, Query, QueryBox, - QueryRequest, Sorting, - }; - // use super::*; - use crate::account::AccountId; - - declare_versioned!(SignedQuery 1..2, Debug, Clone, iroha_macro::FromVariant, IntoSchema); - - #[model] - mod model { - use core::num::NonZeroU64; - - use super::*; - - /// I/O ready structure to send queries. - #[derive(Debug, Clone)] - #[repr(transparent)] - #[must_use] - pub struct ClientQueryBuilder { - /// Payload - pub(super) payload: ClientQueryPayload, - } - - /// Payload of a query. - #[derive( - Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, - )] - pub(crate) struct ClientQueryPayload { - /// Account id of the user who will sign this query. - pub authority: AccountId, - /// Query definition. - pub query: QueryBox, - /// The filter applied to the result on the server-side. - pub filter: PredicateBox, - /// Sorting applied to the result on the server-side. - pub sorting: Sorting, - /// Selects the page of the result set to return. - pub pagination: Pagination, - /// Specifies the size of a single batch of results. - pub fetch_size: FetchSize, - } - - /// Signature of query - #[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Decode, - Encode, - Deserialize, - Serialize, - IntoSchema, - )] - pub struct QuerySignature(pub SignatureOf); - - /// I/O ready structure to send queries. - #[derive(Debug, Clone, Encode, Serialize, IntoSchema)] - #[version_with_scale(version = 1, versioned_alias = "SignedQuery")] - pub struct SignedQueryV1 { - /// Signature of the client who sends this query. - pub signature: QuerySignature, - /// Payload - pub payload: ClientQueryPayload, - } - - /// End type of a query http clients can send to an endpoint. - #[derive(Debug, Clone, Decode, Encode)] - pub struct ClientQueryRequest(pub QueryRequest); - } - - impl ClientQueryRequest { - /// Construct a new request containing query. - pub fn query(query: SignedQuery) -> Self { - Self(QueryRequest::Query(query)) - } - - /// Construct a new request containing cursor. - pub fn cursor(cursor: ForwardCursor) -> Self { - Self(QueryRequest::Cursor(cursor)) - } - } - - mod candidate { - use parity_scale_codec::Input; - - use super::*; - - #[derive(Decode, Deserialize)] - struct SignedQueryCandidate { - signature: QuerySignature, - payload: ClientQueryPayload, - } - - impl SignedQueryCandidate { - fn validate(self) -> Result { - let QuerySignature(signature) = &self.signature; - - signature - .verify(&self.payload.authority.signatory, &self.payload) - .map_err(|_| "Query signature is not valid")?; - - Ok(SignedQueryV1 { - payload: self.payload, - signature: self.signature, - }) - } - } - - impl Decode for SignedQueryV1 { - fn decode(input: &mut I) -> Result { - SignedQueryCandidate::decode(input)? - .validate() - .map_err(Into::into) - } - } - - impl<'de> Deserialize<'de> for SignedQueryV1 { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - use serde::de::Error as _; - - SignedQueryCandidate::deserialize(deserializer)? - .validate() - .map_err(D::Error::custom) - } - } - } - - #[cfg(feature = "transparent_api")] - impl SignedQuery { - /// Return query signature - pub fn signature(&self) -> &QuerySignature { - let SignedQuery::V1(query) = self; - &query.signature - } - /// Return query payload - pub fn query(&self) -> &QueryBox { - let SignedQuery::V1(query) = self; - &query.payload.query - } - /// Return query authority - pub fn authority(&self) -> &AccountId { - let SignedQuery::V1(query) = self; - &query.payload.authority - } - /// Return query filter - pub fn filter(&self) -> &PredicateBox { - let SignedQuery::V1(query) = self; - &query.payload.filter - } - /// Return query sorting - pub fn sorting(&self) -> &Sorting { - let SignedQuery::V1(query) = self; - &query.payload.sorting - } - /// Return query pagination - pub fn pagination(&self) -> Pagination { - let SignedQuery::V1(query) = self; - query.payload.pagination - } - /// Return query fetch size - pub fn fetch_size(&self) -> FetchSize { - let SignedQuery::V1(query) = self; - query.payload.fetch_size - } - } - - impl ClientQueryBuilder { - /// Construct a new request with the `query`. - pub fn new(query: impl Query, authority: AccountId) -> Self { - Self { - payload: ClientQueryPayload { - query: query.into(), - authority, - filter: PredicateBox::default(), - sorting: Sorting::default(), - pagination: Pagination::default(), - fetch_size: FetchSize::default(), - }, - } - } - - /// Set the filter for the query - #[inline] - pub fn with_filter(mut self, filter: PredicateBox) -> Self { - self.payload.filter = filter; - self - } - - /// Set the sorting for the query - #[inline] - pub fn with_sorting(mut self, sorting: Sorting) -> Self { - self.payload.sorting = sorting; - self - } - - /// Set the pagination for the query - #[inline] - pub fn with_pagination(mut self, pagination: Pagination) -> Self { - self.payload.pagination = pagination; - self - } - - /// Set the fetch size for the query - #[inline] - pub fn with_fetch_size(mut self, fetch_size: FetchSize) -> Self { - self.payload.fetch_size = fetch_size; - self - } - - /// Consumes self and returns a signed [`ClientQueryBuilder`]. - /// - /// # Errors - /// Fails if signature creation fails. - #[inline] - #[must_use] - pub fn sign(self, key_pair: &iroha_crypto::KeyPair) -> SignedQuery { - let signature = SignatureOf::new(key_pair.private_key(), &self.payload); - - SignedQueryV1 { - signature: QuerySignature(signature), - payload: self.payload, - } - .into() - } - } - - pub mod prelude { - //! The prelude re-exports most commonly used traits, structs and macros from this crate. - - pub use super::{ClientQueryBuilder, QuerySignature, SignedQuery, SignedQueryV1}; - } -} - pub mod error { //! Module containing errors that can occur during query execution @@ -1852,12 +996,10 @@ pub mod error { /// The prelude re-exports most commonly used traits, structs and macros from this crate. #[allow(ambiguous_glob_reexports)] pub mod prelude { - #[cfg(feature = "http")] - pub use super::http::*; pub use super::{ account::prelude::*, asset::prelude::*, block::prelude::*, domain::prelude::*, - executor::prelude::*, peer::prelude::*, permission::prelude::*, predicate::PredicateTrait, - role::prelude::*, transaction::prelude::*, trigger::prelude::*, FetchSize, - IterableQueryBox, QueryBox, QueryRequest2, SingularQueryBox, TransactionQueryOutput, + executor::prelude::*, parameters::prelude::*, peer::prelude::*, permission::prelude::*, + predicate::prelude::*, role::prelude::*, transaction::prelude::*, trigger::prelude::*, + IterableQueryBox, QueryBox, QueryRequest, SingularQueryBox, TransactionQueryOutput, }; } diff --git a/data_model/src/query/pagination.rs b/data_model/src/query/pagination.rs deleted file mode 100644 index 61943e7018f..00000000000 --- a/data_model/src/query/pagination.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! Structures and traits related to pagination. - -#[cfg(not(feature = "std"))] -use alloc::{ - borrow::ToOwned as _, - collections::btree_map, - format, - string::{String, ToString as _}, - vec, - vec::Vec, -}; -use core::num::{NonZeroU32, NonZeroU64, NonZeroUsize}; -#[cfg(feature = "std")] -use std::collections::btree_map; - -use derive_more::{Constructor, Display}; -use iroha_data_model_derive::model; -use iroha_schema::IntoSchema; -use parity_scale_codec::{Decode, Encode}; -use serde::{Deserialize, Serialize}; - -/// Structure for pagination requests -#[derive( - Debug, - Display, - Clone, - Copy, - PartialEq, - Eq, - Default, - Decode, - Encode, - Deserialize, - Serialize, - IntoSchema, -)] -#[display( - fmt = "{}--{}", - "start.map(NonZeroU64::get).unwrap_or(0)", - "limit.map_or(\".inf\".to_owned(), |n| n.to_string())" -)] -pub struct Pagination { - /// limit of indexing - pub limit: Option, - /// start of indexing - // TODO: Rename to offset - pub start: Option, -} - -pub mod prelude { - //! Prelude: re-export most commonly used traits, structs and macros from this module. - pub use super::*; -} diff --git a/data_model/src/query/parameters.rs b/data_model/src/query/parameters.rs new file mode 100644 index 00000000000..96c579c66ff --- /dev/null +++ b/data_model/src/query/parameters.rs @@ -0,0 +1,137 @@ +//! Defines parameters that can be sent along with a query. + +#[cfg(not(feature = "std"))] +use alloc::{borrow::ToOwned, format, string::String, string::ToString, vec::Vec}; +use core::num::{NonZeroU32, NonZeroU64}; + +use derive_more::{Constructor, Display}; +use getset::Getters; +use iroha_data_model_derive::model; +use iroha_schema::IntoSchema; +use iroha_version::{Decode, Encode}; +use nonzero_ext::nonzero; +use serde::{Deserialize, Serialize}; + +use crate::name::Name; + +/// Default value for `fetch_size` parameter in queries. +pub const DEFAULT_FETCH_SIZE: NonZeroU32 = nonzero!(10_u32); +/// Max value for `fetch_size` parameter in queries. +pub const MAX_FETCH_SIZE: NonZeroU32 = nonzero!(10_000_u32); + +pub use self::model::*; + +/// Unique id of a query +pub type QueryId = String; + +#[model] +mod model { + + use super::*; + + /// Forward-only (a.k.a non-scrollable) cursor + #[derive( + Debug, Clone, PartialEq, Eq, Getters, Encode, Decode, Serialize, Deserialize, IntoSchema, + )] + #[getset(get = "pub")] + pub struct ForwardCursor { + /// Unique ID of query. When provided in a query the query will look up if there + /// is was already a query with a matching ID and resume returning result batches + pub query: QueryId, + /// Pointer to the next element in the result set + pub cursor: NonZeroU64, + } + + /// Structure for pagination requests + #[derive( + Debug, + Display, + Clone, + Copy, + PartialEq, + Eq, + Default, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + #[display( + fmt = "{}--{}", + "start.map(NonZeroU64::get).unwrap_or(0)", + "limit.map_or(\".inf\".to_owned(), |n| n.to_string())" + )] + pub struct Pagination { + /// limit of indexing + pub limit: Option, + /// start of indexing + // TODO: Rename to offset + pub start: Option, + } + + /// Struct for sorting requests + #[derive( + Debug, Clone, Default, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, + )] + pub struct Sorting { + /// Sort query result using [`Name`] of the key in [`Asset`]'s metadata. + pub sort_by_metadata_key: Option, + } + + /// Structure for query fetch size parameter encoding/decoding + #[derive( + Debug, + Default, + Clone, + Copy, + PartialEq, + Eq, + Constructor, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + pub struct FetchSize { + /// Inner value of a fetch size. + /// + /// If not specified then [`DEFAULT_FETCH_SIZE`] is used. + pub fetch_size: Option, + } + + /// Parameters that can modify iterable query execution. + #[derive( + Debug, + Clone, + PartialEq, + Eq, + Default, + Constructor, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + )] + pub struct IterableQueryParams { + pub pagination: Pagination, + pub sorting: Sorting, + pub fetch_size: FetchSize, + } +} + +impl Sorting { + /// Creates a sorting by [`Name`] of the key. + pub fn by_metadata_key(key: Name) -> Self { + Self { + sort_by_metadata_key: Some(key), + } + } +} + +pub mod prelude { + //! Prelude: re-export most commonly used traits, structs and macros from this module. + pub use super::{FetchSize, Pagination, Sorting}; +} diff --git a/data_model/src/query/predicate/mod.rs b/data_model/src/query/predicate/mod.rs index 7be402d5b0b..c5db045824b 100644 --- a/data_model/src/query/predicate/mod.rs +++ b/data_model/src/query/predicate/mod.rs @@ -1,5 +1,4 @@ -// TODO: remove -#![warn(unused, missing_docs)] +#![warn(missing_docs)] // TODO #![allow(missing_copy_implementations)] //! Predicate-related logic. Should contain predicate-related `impl`s. @@ -10,13 +9,9 @@ pub mod projectors; pub mod prototypes; #[cfg(not(feature = "std"))] -use alloc::{borrow::ToOwned, boxed::Box, vec, vec::Vec}; -use core::{fmt::Display, ops::Not}; - -use iroha_data_model_derive::{PartiallyTaggedDeserialize, PartiallyTaggedSerialize}; +use alloc::{boxed::Box, vec, vec::Vec}; use super::*; -use crate::IdBox; /// Trait for generic predicates. pub trait PredicateTrait { @@ -24,1068 +19,6 @@ pub trait PredicateTrait { fn applies(&self, input: &T) -> bool; } -mod nontrivial { - use super::*; - /// Struct representing a sequence with at least three elements. - #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] - pub struct NonTrivial(Vec); - - impl NonTrivial { - /// Constructor - #[inline] - pub fn new(first: T, second: T) -> Self { - Self(vec![first, second]) - } - - /// Extend the sequence with elements of another non-empty sequence - #[inline] - pub fn extend(&mut self, other: Self) { - self.0.extend(other.0); - } - - /// Append `value` to the end of the sequence - #[inline] - pub fn push(&mut self, value: T) { - self.0.push(value); - } - - /// Apply the provided function to every element of the sequence - #[must_use] - #[inline] - pub fn map(self, f: impl FnMut(T) -> U) -> NonTrivial { - NonTrivial(self.0.into_iter().map(f).collect()) - } - - /// Get reference to first element of the sequence - #[inline] - pub fn head(&self) -> &T { - self.0.first().expect("Shouldn't be empty by construction") - } - - /// Produce an iterator over the sequence - #[inline] - pub fn iter(&self) -> impl Iterator { - self.0.iter() - } - } - - impl<'item, T> IntoIterator for &'item NonTrivial { - type Item = &'item T; - - type IntoIter = <&'item Vec as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } - } -} -pub use nontrivial::NonTrivial; - -macro_rules! nontrivial { - ($first:expr, $second:expr $(, $( $t:expr ),*)? ) => {{ - let res = NonTrivial::new(($first), ($second)); - $({ res.push($t); })* - res - }}; -} - -/// Predicate combinator enum. -#[derive( - Debug, - Clone, - PartialEq, - Eq, - Decode, - Encode, - PartiallyTaggedSerialize, - PartiallyTaggedDeserialize, - IntoSchema, -)] -// Ideally we would enforce `P: PredicateTrait` here, but I -// couldn't find a way to do it without polluting everything -// downstream with explicit lifetimes, since we would need to -// store PhantomData here, and `Input`s are most often -// references (e.g. &QueryOutputBox). -pub enum GenericPredicateBox

{ - /// Logically `&&` the results of applying the predicates. - And(NonTrivial), - /// Logically `||` the results of applying the predicats. - Or(NonTrivial), - /// Negate the result of applying the predicate. - Not(Box), - /// The raw predicate that must be applied. - #[serde_partially_tagged(untagged)] - Raw(P), -} - -impl

Display for GenericPredicateBox

-where - P: Display, -{ - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - GenericPredicateBox::And(predicates) => { - write!(f, "AND(")?; - for predicate in predicates { - predicate.fmt(f)?; - } - write!(f, ")") - } - GenericPredicateBox::Or(predicates) => { - write!(f, "OR(")?; - for predicate in predicates { - predicate.fmt(f)?; - } - write!(f, ")") - } - GenericPredicateBox::Not(predicate) => write!(f, "NOT({predicate})"), - GenericPredicateBox::Raw(predicate) => predicate.fmt(f), - } - } -} - -impl

GenericPredicateBox

{ - /// Construct [`PredicateBox::Raw`] variant. - #[inline] - pub fn new(pred: impl Into

) -> Self - where - P: PredicateTrait, - { - Self::Raw(pred.into()) - } - - /// Construct [`PredicateBox::And`] variant. - #[inline] - pub fn and(left: impl Into, right: impl Into) -> Self { - match (left.into(), right.into()) { - (Self::And(mut left), Self::And(right)) => { - left.extend(right); - Self::And(left) - } - (Self::And(mut and), other) => { - and.push(other); - Self::And(and) - } - (left, right) => Self::And(nontrivial![left, right]), - } - } - - /// Construct [`PredicateBox::Or`] variant. - #[inline] - pub fn or(left: impl Into, right: impl Into) -> Self { - match (left.into(), right.into()) { - (Self::Or(mut left), Self::Or(right)) => { - left.extend(right); - Self::Or(left) - } - (Self::Or(mut and), other) => { - and.push(other); - Self::Or(and) - } - (left, right) => Self::Or(nontrivial![left, right]), - } - } - - /// Convert instance into its negation. - #[must_use] - #[inline] - pub fn negate(self) -> Self { - match self { - Self::And(preds) => Self::Or(preds.map(Self::negate)), - Self::Or(preds) => Self::And(preds.map(Self::negate)), - Self::Not(pred) => *pred, // TODO: should we recursively simplify? - Self::Raw(pred) => Self::Not(Box::new(Self::Raw(pred))), - } - } -} - -impl PredicateTrait for GenericPredicateBox -where - Input: ?Sized, - Pred: PredicateTrait, -{ - #[inline] // This is not a simple function, but it allows you to inline the logic and optimise away the logical operations. - fn applies(&self, input: &Input) -> bool { - match self { - Self::Raw(predicate) => predicate.applies(input), - Self::And(predicates) => { - let operands = predicates.iter().map(|predicate| predicate.applies(input)); - - for operand in operands { - if !operand { - return false; - } - } - return true; - } - Self::Or(predicates) => { - let operands = predicates.iter().map(|predicate| predicate.applies(input)); - for operand in operands { - if operand { - return true; - } - } - return false; - } - Self::Not(predicate) => predicate.applies(input).not(), - } - } -} - -/// Predicate combinator for predicates operating on `QueryOutputBox` -pub type PredicateBox = GenericPredicateBox; - -impl Default for PredicateBox { - fn default() -> Self { - PredicateBox::Raw(value::QueryOutputPredicate::Pass) - } -} - -#[cfg(test)] -pub mod test { - use iroha_primitives::json::JsonString; - - use super::{value, PredicateBox, PredicateTrait as _}; - - #[test] - fn pass() { - let t = PredicateBox::new(value::QueryOutputPredicate::Pass); - let f = t.clone().negate(); - let v_t = JsonString::from(true).into(); - let v_f = JsonString::from(false).into(); - println!("t: {t:?}, f: {f:?}"); - - assert!(t.applies(&v_t)); - assert!(t.applies(&v_f)); - assert!(!f.applies(&v_t)); - assert!(!f.applies(&v_f)); - } - - #[test] - fn truth_table() { - let t = PredicateBox::new(value::QueryOutputPredicate::Pass); - let f = t.clone().negate(); - let v = JsonString::from(true).into(); - - assert!(!PredicateBox::and(t.clone(), f.clone()).applies(&v)); - assert!(PredicateBox::and(t.clone(), t.clone()).applies(&v)); - assert!(!PredicateBox::and(f.clone(), f.clone()).applies(&v)); - assert!(!PredicateBox::and(f.clone(), t.clone()).applies(&v)); - - assert!(PredicateBox::or(t.clone(), t.clone()).applies(&v)); - assert!(PredicateBox::or(t.clone(), f.clone()).applies(&v)); - assert!(PredicateBox::or(f.clone(), t).applies(&v)); - assert!(!PredicateBox::or(f.clone(), f).applies(&v)); - } - - #[test] - fn negation() { - let t = PredicateBox::default(); - - assert!(matches!(t.clone().negate().negate(), PredicateBox::Raw(_))); - // De-morgan identities - assert!(matches!( - PredicateBox::and(t.clone(), t.clone()).negate(), - PredicateBox::Or(_) - )); - assert!(matches!( - PredicateBox::or(t.clone(), t).negate(), - PredicateBox::And(_) - )); - } -} - -pub mod string { - //! String-related predicates and implementations. - use super::*; - - /// Predicate useful for processing [`String`]s and [`Name`]s. - #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] - pub enum StringPredicate { - /// Forward to [`str::contains()`] - Contains(String), - /// Forward to [`str::starts_with()`] - StartsWith(String), - /// Forward to [`str::ends_with()`] - EndsWith(String), - /// Forward to [`String`] equality. - Is(String), - } - - impl StringPredicate { - /// Construct the [`Self::Contains`] variant - #[inline] - pub fn contains(predicate: &str) -> Self { - Self::Contains(predicate.to_owned()) - } - - /// Construct the [`Self::StartsWith`] variant - #[inline] - pub fn starts_with(predicate: &str) -> Self { - Self::StartsWith(predicate.to_owned()) - } - - /// Construct the [`Self::EndsWith`] variant - #[inline] - pub fn ends_with(predicate: &str) -> Self { - Self::EndsWith(predicate.to_owned()) - } - - /// Construct the [`Self::Is`] variant - #[inline] - pub fn is(predicate: &str) -> Self { - Self::Is(predicate.to_owned()) - } - } - - // TODO: Case insensitive variants? - - impl + ?Sized> PredicateTrait for StringPredicate { - #[inline] // Jump table. Needs inline. - fn applies(&self, input: &T) -> bool { - match self { - StringPredicate::Contains(content) => input.as_ref().contains(content), - StringPredicate::StartsWith(content) => input.as_ref().starts_with(content), - StringPredicate::EndsWith(content) => input.as_ref().ends_with(content), - StringPredicate::Is(content) => *(input.as_ref()) == *content, - } - } - } - - impl PredicateTrait for StringPredicate { - #[inline] // Jump table. Needs inline. - fn applies(&self, input: &IdBox) -> bool { - match input { - IdBox::DomainId(id) => self.applies(&id.to_string()), - IdBox::AccountId(id) => self.applies(&id.to_string()), - IdBox::AssetDefinitionId(id) => self.applies(&id.to_string()), - IdBox::AssetId(id) => self.applies(&id.to_string()), - IdBox::PeerId(id) => self.applies(&id.to_string()), - IdBox::TriggerId(id) => self.applies(&id.to_string()), - IdBox::RoleId(id) => self.applies(&id.to_string()), - IdBox::Permission(id) => self.applies(&id.to_string()), - IdBox::CustomParameterId(id) => self.applies(&id.to_string()), - } - } - } - - #[cfg(test)] - mod tests { - use iroha_primitives::addr::socket_addr; - - use super::*; - - mod id_box { - use iroha_crypto::KeyPair; - - use super::*; - use crate::peer::PeerId; - - #[test] - fn simple_name_wrappers() { - let starts_with = StringPredicate::starts_with("Curiouser"); - let contains = StringPredicate::contains("Curiouser"); - let ends_with = StringPredicate::ends_with("Curiouser"); - let pred_is = StringPredicate::is("Curiouser"); - - // What do you think about explicit scoping in tests? - { - // Domain - let curiouser = IdBox::DomainId("curiouser".parse().expect("Valid")); - // Negatives. - assert!(!starts_with.applies(&curiouser)); - assert!(!contains.applies(&curiouser)); - assert!(!ends_with.applies(&curiouser)); - assert!(!pred_is.applies(&curiouser)); - - let cap_curiouser = - IdBox::DomainId("Curiouser_and_Curiouser".parse().expect("Valid")); - // Some positives - assert!(starts_with.applies(&cap_curiouser)); - assert!(contains.applies(&cap_curiouser)); - assert!(ends_with.applies(&cap_curiouser)); - assert!(!pred_is.applies(&cap_curiouser)); - } - - { - // Role - let curiouser = IdBox::RoleId("curiouser".parse().expect("Valid")); - // Negatives. - assert!(!starts_with.applies(&curiouser)); - assert!(!contains.applies(&curiouser)); - assert!(!ends_with.applies(&curiouser)); - assert!(!pred_is.applies(&curiouser)); - - let cap_curiouser = - IdBox::RoleId("Curiouser_and_Curiouser".parse().expect("Valid")); - // Some positives - assert!(starts_with.applies(&cap_curiouser)); - assert!(contains.applies(&cap_curiouser)); - assert!(ends_with.applies(&cap_curiouser)); - assert!(!pred_is.applies(&cap_curiouser)); - } - } - - #[test] - fn trigger() { - let starts_with = StringPredicate::starts_with("Curiouser"); - let contains = StringPredicate::contains("Curiouser"); - let ends_with = StringPredicate::ends_with("Curiouser"); - let pred_is = StringPredicate::is("Curiouser"); - - let curiouser = IdBox::TriggerId("curiouser".parse().expect("Valid")); - // Negatives. - assert!(!starts_with.applies(&curiouser)); - assert!(!contains.applies(&curiouser)); - assert!(!ends_with.applies(&curiouser)); - assert!(!pred_is.applies(&curiouser)); - - let cap_curiouser = - IdBox::TriggerId("Curiouser_and_Curiouser".parse().expect("Valid")); - // Some positives - assert!(starts_with.applies(&cap_curiouser)); - assert!(contains.applies(&cap_curiouser)); - assert!(ends_with.applies(&cap_curiouser)); - assert!(!pred_is.applies(&cap_curiouser)); - - // TODO: Once #2302 and #1889 are merged, add more cases. - } - - #[test] - fn account_id() { - let alice: PublicKey = KeyPair::random().into_parts().0; - let id = IdBox::AccountId(format!("{alice}@wonderland").parse().expect("Valid")); - assert!(StringPredicate::starts_with(&format!("{alice}@")).applies(&id)); - assert!(StringPredicate::ends_with("@wonderland").applies(&id)); - assert!(StringPredicate::is(&format!("{alice}@wonderland")).applies(&id)); - // Should we also include a check into string - // predicates? If the internal predicate starts with - // whitespace, it can't possibly match any Id, but - // there's no way to enforce this at both type level - // and run-time. - assert!(!StringPredicate::starts_with(&format!(" {alice}@")).applies(&id)); - assert!(!StringPredicate::ends_with("@wonderland ").applies(&id)); - assert!(!StringPredicate::is(&format!("{alice}@@wonderland ")).applies(&id)); - assert!(!StringPredicate::contains("#").applies(&id)); - assert!(!StringPredicate::is(&format!("{alice}#wonderland")).applies(&id)); - } - - #[test] - fn asset_id() { - let alice: PublicKey = KeyPair::random().into_parts().0; - let id = - IdBox::AssetId(format!("rose##{alice}@wonderland").parse().expect("Valid")); - assert!(StringPredicate::starts_with("rose##").applies(&id)); - assert!(StringPredicate::ends_with(&format!("#{alice}@wonderland")).applies(&id)); - assert!(StringPredicate::is(&format!("rose##{alice}@wonderland")).applies(&id)); - assert!(StringPredicate::contains(&format!("#{alice}@")).applies(&id)); - } - - #[test] - fn asset_def_id() { - let id = IdBox::AssetDefinitionId("rose#wonderland".parse().expect("Valid")); - assert!(StringPredicate::starts_with("rose#").applies(&id)); - assert!(StringPredicate::ends_with("#wonderland").applies(&id)); - assert!(StringPredicate::is("rose#wonderland").applies(&id)); - // Should we also include a check into string - // predicates? If the internal predicate starts with - // whitespace, it can't possibly match any Id, but - // there's no way to enforce this at both type level - // and run-time. - assert!(!StringPredicate::starts_with(" rose#").applies(&id)); - assert!(!StringPredicate::ends_with("#wonderland ").applies(&id)); - assert!(!StringPredicate::is("alice##wonderland ").applies(&id)); - assert!(!StringPredicate::contains("@").applies(&id)); - assert!(!StringPredicate::is("rose@wonderland").applies(&id)); - } - - #[test] - fn peer_id() { - let (public_key, _) = iroha_crypto::KeyPair::random().into_parts(); - let id = IdBox::PeerId(PeerId::new(socket_addr!(127.0.0.1:123), public_key)); - assert!(StringPredicate::contains("123").applies(&id)); - } - } - - mod string { - use super::*; - - #[test] - fn contains() { - let pred = StringPredicate::Contains("believed as many".to_owned()); - assert!(pred.applies( - "sometimes I've believed as many as six impossible things before breakfast!" - )); - assert!(!pred.applies("hello world")); - assert!(pred.applies("believed as many")); - assert!(pred.applies(" believed as many")); - assert!(pred.applies("believed as many ")); - assert!(!pred.applies("believed")); - } - - #[test] - fn starts_with() { - let pred = StringPredicate::StartsWith("Curiouser".to_owned()); - assert!(pred.applies("Curiouser and Curiouser")); - assert!(!pred.applies(" Curiouser and Curiouser")); - assert!(!pred.applies("curiouser and curiouser")); - assert!(!pred.applies("More Curiouser")); - assert!(!pred.applies("Curiouse")); - } - - #[test] - fn ends_with() { - let pred = StringPredicate::EndsWith("How long is forever?".to_owned()); - assert!(pred.applies("How long is forever?")); - assert!(!pred.applies("how long is forever?")); - assert!(pred.applies(" How long is forever?")); - assert!(pred.applies("I asked: How long is forever?")); - assert!(!pred.applies("How long is forever")); - } - - #[test] - fn is() { - let pred = StringPredicate::Is("writing-desk".to_owned()); - assert!(!pred.applies("Why is a raven like a writing-desk")); - assert!(pred.applies("writing-desk")); - assert!(!pred.applies("Writing-desk")); - assert!(!pred.applies("writing-des")); - } - - #[test] - fn empty_predicate() { - let pred = StringPredicate::contains(""); - assert!(pred.applies("")); - assert!(pred.applies("asd")); // TODO: is this the correct behaviour that we want - } - } - } -} - -pub mod numerical { - //! Numerical predicates. - use core::cmp::{max, min}; - - use iroha_primitives::numeric::Numeric; - - use super::*; - use crate::query::QueryOutputBox; - - /// A lower-inclusive range predicate. - #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] - pub struct SemiInterval { - /// The start of the range (inclusive) - start: T, - /// The end of the range - limit: T, - } - - impl From<(T, T)> for SemiInterval { - #[inline] - fn from((start, limit): (T, T)) -> Self { - Self { - start: min(start, limit), - limit: max(limit, start), - } - } - } - - impl Copy for SemiInterval {} - impl Copy for SemiInterval {} - impl Copy for SemiInterval {} - impl Copy for SemiInterval {} - - /// A both-inclusive range predicate - #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] - pub struct Interval { - /// The start of the range (inclusive) - start: T, - /// The limit of the range (inclusive) - limit: T, - } - - impl From<(T, T)> for Interval { - #[inline] - fn from((start, limit): (T, T)) -> Self { - Self { - start: min(start, limit), - limit: max(limit, start), - } - } - } - - impl From for Interval { - #[inline] - fn from(single_value: T) -> Self { - Self { - start: single_value, - limit: single_value, - } - } - } - - impl Copy for Interval {} - impl Copy for Interval {} - impl Copy for Interval {} - impl Copy for Interval {} - - /// General purpose predicate for working with Iroha numerical type. - /// - /// # Type checking - /// - /// [`Self`] only applies to `Values` that are variants of - /// compatible types. If the [`Range`] variant and the [`Value`] - /// variant don't match defaults to `false`. - #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] - pub enum SemiRange { - /// Numeric - Numeric(SemiInterval), - } - - /// General-purpose predicate for working with Iroha numerical - /// type, both-ends inclusive variant. - /// - /// # Type checking - /// - /// [`Self`] only applies to `Values` that are variants of - /// compatible types. If the [`Range`] variant and the [`Value`] - /// variant don't match defaults to `false`. - #[derive(Debug, Clone, Decode, Encode, Deserialize, Serialize, IntoSchema)] - pub enum Range { - /// Numeric - Numeric(Interval), - } - - /// A trait to mark objects which should be treated as bounded unsigned values. - pub trait UnsignedMarker { - /// The maximum attainable value - const MAX: Self; - /// The additive neutral element, a.k.a zero, nil, null, - /// 'nada, zilch, etc. Be advised that since this trait is - /// used to mark unsigned values, it coincides with what would - /// be `MIN`. However, do not implement it for types that are - /// non-zero (e.g. `NonZeroU64`), because `ZERO` is not `MIN`. - const ZERO: Self; - } - - impl UnsignedMarker for u8 { - const MAX: Self = u8::MAX; - const ZERO: Self = 0_u8; - } - - impl UnsignedMarker for u32 { - const MAX: Self = u32::MAX; - const ZERO: Self = 0_u32; - } - - impl UnsignedMarker for u128 { - const MAX: Self = u128::MAX; - const ZERO: Self = 0_u128; - } - - impl UnsignedMarker for Numeric { - const MAX: Self = Numeric::MAX; - const ZERO: Self = Numeric::ZERO; - } - - impl SemiInterval { - /// Construct a semi-interval starting at `start` and ending - /// at `T::MAX`. - #[inline] - #[must_use] - pub fn starting(start: T) -> Self { - Self { - start, - limit: T::MAX, - } - } - - /// Construct a semi-interval that ends at `end` and starts at - /// `T::ZERO`. - #[inline] - #[must_use] - pub fn ending(end: T) -> Self { - Self { - start: T::ZERO, - limit: end, - } - } - } - - impl Interval { - /// Construct a semi-interval starting at `start` and ending - /// at `T::MAX`. - #[inline] - #[must_use] - pub fn starting(start: T) -> Self { - Self { - start, - limit: T::MAX, - } - } - - /// Construct a semi-interval that ends at `end` and starts at - /// `T::ZERO`. - #[inline] - #[must_use] - pub fn ending(end: T) -> Self { - Self { - start: T::ZERO, - limit: end, - } - } - } - - impl PredicateTrait for SemiInterval { - #[inline] - fn applies(&self, &input: &T) -> bool { - input < self.limit && input >= self.start - } - } - - impl PredicateTrait for Interval { - #[inline] - fn applies(&self, &input: &T) -> bool { - input <= self.limit && input >= self.start - } - } - - impl PredicateTrait for SemiRange { - #[inline] - fn applies(&self, input: &QueryOutputBox) -> bool { - match input { - QueryOutputBox::Numeric(quantity) => match self { - SemiRange::Numeric(predicate) => predicate.applies(quantity), - }, - _ => false, - } - } - } - - impl PredicateTrait for Range { - #[inline] - fn applies(&self, input: &QueryOutputBox) -> bool { - match input { - QueryOutputBox::Numeric(quantity) => match self { - Range::Numeric(predicate) => predicate.applies(quantity), - }, - _ => false, - } - } - } - - #[cfg(test)] - mod tests { - #![allow(clippy::print_stdout, clippy::use_debug)] - - use iroha_primitives::numeric::numeric; - - use super::*; - - #[test] - fn semi_interval_semantics_numeric() { - let pred = SemiRange::Numeric((numeric!(1), numeric!(100)).into()); - println!("semi_interval range predicate: {pred:?}"); - - assert!(pred.applies(&QueryOutputBox::Numeric(numeric!(1)))); - assert!(!pred.applies(&QueryOutputBox::Numeric(numeric!(0)))); - assert!(pred.applies(&QueryOutputBox::Numeric(numeric!(99)))); - assert!(!pred.applies(&QueryOutputBox::Numeric(numeric!(100)))); - assert!(!pred.applies(&QueryOutputBox::Numeric(numeric!(0.99)))); - assert!(pred.applies(&QueryOutputBox::Numeric(numeric!(99.9999)))); - assert!(pred.applies(&QueryOutputBox::Numeric(numeric!(99.9_999_999_999)))); - } - - #[test] - fn interval_semantics_numeric() { - { - let pred = Range::Numeric((numeric!(1), numeric!(100)).into()); - println!("semi_interval range predicate: {pred:?}"); - - assert!(pred.applies(&QueryOutputBox::Numeric(numeric!(1)))); - assert!(!pred.applies(&QueryOutputBox::Numeric(numeric!(0)))); - assert!(pred.applies(&QueryOutputBox::Numeric(numeric!(100)))); - assert!(!pred.applies(&QueryOutputBox::Numeric(numeric!(101)))); - assert!(!pred.applies(&QueryOutputBox::Numeric(numeric!(0.99)))); - assert!(pred.applies(&QueryOutputBox::Numeric(numeric!(99.9999)))); - assert!(!pred.applies(&QueryOutputBox::Numeric(numeric!(100.000000001)))); - } - - { - let pred = Range::Numeric((numeric!(127), numeric!(127)).into()); - assert!(pred.applies(&QueryOutputBox::Numeric(numeric!(127)))); - assert!(!pred.applies(&QueryOutputBox::Numeric(numeric!(126)))); - assert!(!pred.applies(&QueryOutputBox::Numeric(numeric!(128)))); - } - } - - #[test] - fn invalid_types_false() { - { - let pred = SemiRange::Numeric(SemiInterval::ending(numeric!(100))); - assert!(!pred.applies(&QueryOutputBox::Vec(Vec::new()))); - } - { - let pred = Range::Numeric(Interval::ending(numeric!(100))); - assert!(!pred.applies(&QueryOutputBox::Vec(Vec::new()))); - } - } - - #[test] - fn upper_bounds() { - { - let pred = SemiRange::Numeric(SemiInterval::starting(Numeric::ZERO)); - // Technically the maximum itself is never included in the semi range. - assert!(!pred.applies(&Numeric::MAX.into())); - } - { - let pred = SemiRange::Numeric(SemiInterval::ending(numeric!(100))); - assert!(pred.applies(&numeric!(1).into())); - assert!(pred.applies(&numeric!(99).into())); - assert!(!pred.applies(&numeric!(100).into())); - } - - { - let pred = Range::Numeric(Interval::starting(Numeric::ZERO)); - // Technically the maximum itself is included in the range. - assert!(pred.applies(&Numeric::MAX.into())); - } - { - let pred = Range::Numeric(Interval::ending(numeric!(100))); - assert!(pred.applies(&numeric!(1).into())); - assert!(pred.applies(&numeric!(100).into())); - assert!(!pred.applies(&numeric!(101).into())); - } - } - } -} - -pub mod value { - //! raw predicates applied to `QueryOutputBox`. - use super::*; - use crate::query::QueryOutputBox; - - /// A predicate designed for general processing of `QueryOutputBox`. - #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] - pub enum QueryOutputPredicate { - /// Apply predicate to the [`Identifiable::Id`] and/or [`IdBox`]. - Identifiable(string::StringPredicate), - /// Apply predicate to the container. - Container(Container), - /// Apply predicate to the [`::to_string`](ToString::to_string()) representation. - Display(string::StringPredicate), - /// Apply predicate to the numerical value. - Numerical(numerical::SemiRange), - /// Timestamp (currently for [`SignedBlock`] only). - TimeStamp(numerical::SemiInterval), - /// Always return true. - Pass, - } - - impl PredicateTrait for QueryOutputPredicate { - fn applies(&self, input: &QueryOutputBox) -> bool { - // Large jump table. Do not inline. - match self { - QueryOutputPredicate::Identifiable(pred) => match input { - QueryOutputBox::Id(id_box) => pred.applies(id_box), - QueryOutputBox::Identifiable(identifiable_box) => { - pred.applies(&identifiable_box.id_box()) - } - _ => false, - }, - QueryOutputPredicate::Container(Container::Any(pred)) => match input { - QueryOutputBox::Vec(vec) => vec.iter().any(|val| pred.applies(val)), - _ => false, - }, - QueryOutputPredicate::Container(Container::All(pred)) => match input { - QueryOutputBox::Vec(vec) => vec.iter().all(|val| pred.applies(val)), - _ => false, - }, - QueryOutputPredicate::Container(Container::AtIndex(AtIndex { - index: idx, - predicate: pred, - })) => match input { - QueryOutputBox::Vec(vec) => vec - .get(*idx as usize) // Safe, since this is only executed server-side and servers are 100% going to be 64-bit. - .map_or(false, |val| pred.applies(val)), - _ => false, - }, - QueryOutputPredicate::Numerical(pred) => pred.applies(input), - QueryOutputPredicate::Display(pred) => pred.applies(&input.to_string()), - QueryOutputPredicate::TimeStamp(pred) => match input { - QueryOutputBox::Block(block) => { - pred.applies(&block.header().creation_time().as_millis()) - } - _ => false, - }, - QueryOutputPredicate::Pass => true, - } - } - } - - impl QueryOutputPredicate { - /// Construct [`Predicate::Container`] variant. - #[inline] - #[must_use] - pub fn any(pred: impl Into) -> Self { - Self::Container(Container::Any(Box::new(pred.into()))) - } - - /// Construct [`Predicate::Container`] variant. - #[inline] - #[must_use] - pub fn all(pred: impl Into) -> Self { - Self::Container(Container::All(Box::new(pred.into()))) - } - - /// Construct [`Predicate::Container`] variant. - #[inline] - #[must_use] - pub fn at_index(index: u32, pred: impl Into) -> Self { - Self::Container(Container::AtIndex(AtIndex { - index, - predicate: Box::new(pred.into()), - })) - } - } - - /// A predicate that targets the particular `index` of a collection. - #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] - pub struct AtIndex { - index: u32, - predicate: Box, - } - - /// Predicate that targets specific elements or groups; useful for - /// working with containers. Currently only [`Vec`] is supported. - #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] - pub enum Container { - /// Forward to [`Iterator::any`] - Any(Box), - /// Forward to [`Iterator::all`] - All(Box), - /// Apply predicate to the [`Value`] element at the index. - AtIndex(AtIndex), - } - - impl From for PredicateBox { - fn from(value: QueryOutputPredicate) -> Self { - PredicateBox::Raw(value) - } - } - - #[cfg(test)] - mod test { - use iroha_crypto::KeyPair; - use iroha_primitives::{addr::socket_addr, numeric::numeric}; - - use super::*; - use crate::{ - account::{Account, AccountId}, - domain::{Domain, DomainId}, - metadata::Metadata, - peer::{Peer, PeerId}, - }; - - #[test] - fn typing() { - let alice: PublicKey = KeyPair::random().into_parts().0; - let alice_id: AccountId = format!("{alice}@wonderland").parse().expect("Valid"); - let alice_json: JsonString = JsonString::new(alice_id.to_string()); - { - let pred = QueryOutputPredicate::Identifiable(string::StringPredicate::is( - &alice_id.to_string(), - )); - println!("{pred:?}"); - assert!(pred.applies(&QueryOutputBox::Id(IdBox::AccountId(alice_id.clone())))); - assert!( - pred.applies(&QueryOutputBox::Identifiable(IdentifiableBox::NewAccount( - Account::new(alice_id.clone()) - ))) - ); - assert!(!pred.applies(&alice_json.clone().into())); - assert!(!pred.applies(&QueryOutputBox::Vec(Vec::new()))); - } - { - let pred = QueryOutputPredicate::Pass; - println!("{pred:?}"); - assert!(pred.applies(&alice_json.clone().into())); - } - { - let pred = QueryOutputPredicate::TimeStamp(numerical::SemiInterval::starting(0)); - println!("{pred:?}"); - assert!(!pred.applies(&alice_json.clone().into())); - } - { - let pred = QueryOutputPredicate::Display(string::StringPredicate::is( - &alice_id.to_string(), - )); - println!("{pred:?}"); - - assert!( - !pred.applies(&QueryOutputBox::Identifiable(IdentifiableBox::Peer(Peer { - id: PeerId::new( - socket_addr!(127.0.0.1:123), - KeyPair::random().into_parts().0 - ) - }))) - ); - } - let pred = QueryOutputPredicate::Numerical(numerical::SemiRange::Numeric( - (numeric!(0), numeric!(42)).into(), - )); - - assert!(!pred.applies(&alice_json.into())); - assert!(pred.applies(&numeric!(41).into())); - } - - #[test] - fn container_vec() { - let wonderland: DomainId = "wonderland".parse().expect("Valid"); - let alice: PublicKey = KeyPair::random().into_parts().0; - let alice_id = AccountId::new(wonderland.clone(), alice.clone()); - let list = QueryOutputBox::Vec(vec![ - QueryOutputBox::Identifiable(Domain::new(wonderland.clone()).into()), - QueryOutputBox::Id(alice_id.into()), - QueryOutputBox::Id(wonderland.clone().into()), - ]); - - let wonderland_pred = - QueryOutputPredicate::Display(string::StringPredicate::contains("wonderland")); - - { - let pred = QueryOutputPredicate::any(wonderland_pred.clone()); - println!("{pred:?}"); - assert!(pred.applies(&list)); - assert!(!pred.applies(&QueryOutputBox::Vec(Vec::new()))); - } - - { - let pred = QueryOutputPredicate::all(wonderland_pred.clone()); - println!("{pred:?}"); - assert!(pred.applies(&list)); - assert!(pred.applies(&QueryOutputBox::Vec(Vec::new()))); - } - - { - let wonderland_id_pred = QueryOutputPredicate::Identifiable( - string::StringPredicate::contains("wonderland"), - ); - let pred = QueryOutputPredicate::all(wonderland_id_pred); - println!("{pred:?}"); - assert!(pred.applies(&list)); - assert!(pred.applies(&QueryOutputBox::Vec(Vec::new()))); - } - - assert!(QueryOutputPredicate::at_index(0, wonderland_pred.clone()).applies(&list)); - - let idx_pred = QueryOutputPredicate::at_index(3, wonderland_pred); // Should be out of bounds. - println!("{idx_pred:?}"); - assert!(!idx_pred.applies(&list)); - } - } -} - #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] pub enum CompoundPredicate { Atom(Atom), @@ -1146,8 +79,13 @@ pub trait AstPredicate { Proj: Fn(PredType) -> OutputType + Copy; } +pub mod prelude { + //! Re-export important types and traits for glob import `(::*)` + pub use super::{predicate_atoms::prelude::*, CompoundPredicate, PredicateTrait}; +} + #[cfg(test)] -mod test1 { +mod test { use iroha_crypto::PublicKey; use crate::{ @@ -1236,7 +174,7 @@ mod test1 { // .and(account_id.domain_id.eq(alice_domain_id.clone())) }); - // TODO: do we want to allow `CompoundPredicate` to be passed here? It's kinda inefficient... + // TODO: do we want to allow `CompoundPredicate` to be passed here? Converting from the normalized representation it uses is kind of inefficient... account.id.satisfies(account_id_predicate) }); let account_predicate = account_predicate_denorm.normalize(); diff --git a/data_model/src/query/predicate/predicate_atoms/account.rs b/data_model/src/query/predicate/predicate_atoms/account.rs index 3f3cf43792a..759d9034991 100644 --- a/data_model/src/query/predicate/predicate_atoms/account.rs +++ b/data_model/src/query/predicate/predicate_atoms/account.rs @@ -8,7 +8,6 @@ use serde::{Deserialize, Serialize}; use super::impl_predicate_box; use crate::{ account::{Account, AccountId}, - prelude::PredicateTrait, query::{ predicate::{ predicate_ast_extensions::AstPredicateExt as _, @@ -17,6 +16,7 @@ use crate::{ }, predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, projectors::BaseProjector, + PredicateTrait, }, AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, }, @@ -60,3 +60,8 @@ impl PredicateTrait for AccountPredicateBox { } } } + +pub mod prelude { + //! Re-export all predicate boxes for a glob import `(::*)` + pub use super::{AccountIdPredicateBox, AccountPredicateBox}; +} diff --git a/data_model/src/query/predicate/predicate_atoms/asset.rs b/data_model/src/query/predicate/predicate_atoms/asset.rs index 2d49e2346db..6a301327ce8 100644 --- a/data_model/src/query/predicate/predicate_atoms/asset.rs +++ b/data_model/src/query/predicate/predicate_atoms/asset.rs @@ -8,7 +8,6 @@ use serde::{Deserialize, Serialize}; use super::impl_predicate_box; use crate::{ asset::{Asset, AssetDefinition, AssetDefinitionId, AssetId, AssetValue}, - prelude::PredicateTrait, query::{ predicate::{ predicate_ast_extensions::AstPredicateExt as _, @@ -18,6 +17,7 @@ use crate::{ }, predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, projectors::BaseProjector, + PredicateTrait, }, AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, }, @@ -119,3 +119,11 @@ impl PredicateTrait for AssetDefinitionIdPredicateBox { } } } + +pub mod prelude { + //! Re-export all predicate boxes for a glob import `(::*)` + pub use super::{ + AssetDefinitionIdPredicateBox, AssetDefinitionPredicateBox, AssetIdPredicateBox, + AssetPredicateBox, AssetValuePredicateBox, + }; +} diff --git a/data_model/src/query/predicate/predicate_atoms/block.rs b/data_model/src/query/predicate/predicate_atoms/block.rs index 25cc6a207f0..8f34734f68e 100644 --- a/data_model/src/query/predicate/predicate_atoms/block.rs +++ b/data_model/src/query/predicate/predicate_atoms/block.rs @@ -8,13 +8,12 @@ use serde::{Deserialize, Serialize}; use super::impl_predicate_box; use crate::{ block::{BlockHeader, SignedBlock}, - prelude::PredicateTrait, query::{ predicate::{ predicate_ast_extensions::AstPredicateExt as _, predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, projectors::BaseProjector, - AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, + AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, PredicateTrait, }, TransactionQueryOutput, }, @@ -64,3 +63,10 @@ impl PredicateTrait for TransactionQueryOutputPredicateB } } } + +pub mod prelude { + //! Re-export all predicate boxes for a glob import `(::*)` + pub use super::{ + BlockHeaderPredicateBox, SignedBlockPredicateBox, TransactionQueryOutputPredicateBox, + }; +} diff --git a/data_model/src/query/predicate/predicate_atoms/domain.rs b/data_model/src/query/predicate/predicate_atoms/domain.rs index 6e0b4ae0c65..61b78f1b76f 100644 --- a/data_model/src/query/predicate/predicate_atoms/domain.rs +++ b/data_model/src/query/predicate/predicate_atoms/domain.rs @@ -8,13 +8,12 @@ use serde::{Deserialize, Serialize}; use super::{impl_predicate_box, MetadataPredicateBox}; use crate::{ domain::{Domain, DomainId}, - prelude::PredicateTrait, query::predicate::{ predicate_ast_extensions::AstPredicateExt as _, predicate_atoms::StringPredicateBox, predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, projectors::BaseProjector, - AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, + AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, PredicateTrait, }, }; @@ -54,3 +53,8 @@ impl PredicateTrait for DomainIdPredicateBox { } } } + +pub mod prelude { + //! Re-export all predicate boxes for a glob import `(::*)` + pub use super::{DomainIdPredicateBox, DomainPredicateBox}; +} diff --git a/data_model/src/query/predicate/predicate_atoms/mod.rs b/data_model/src/query/predicate/predicate_atoms/mod.rs index 8bbeda77580..818487537e1 100644 --- a/data_model/src/query/predicate/predicate_atoms/mod.rs +++ b/data_model/src/query/predicate/predicate_atoms/mod.rs @@ -20,9 +20,9 @@ use super::{ predicate_ast_extensions::AstPredicateExt as _, predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, projectors::BaseProjector, - AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, + AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, PredicateTrait, }; -use crate::{metadata::Metadata, name::Name, prelude::PredicateTrait}; +use crate::{metadata::Metadata, name::Name}; /// Adds common methods to a predicate box. /// @@ -162,3 +162,12 @@ impl PredicateTrait for PublicKeyPredicateBox { } } } + +pub mod prelude { + //! Re-export all predicate boxes for a glob import `(::*)` + pub use super::{ + account::prelude::*, asset::prelude::*, block::prelude::*, domain::prelude::*, + parameter::prelude::*, peer::prelude::*, permission::prelude::*, role::prelude::*, + trigger::prelude::*, MetadataPredicateBox, PublicKeyPredicateBox, StringPredicateBox, + }; +} diff --git a/data_model/src/query/predicate/predicate_atoms/parameter.rs b/data_model/src/query/predicate/predicate_atoms/parameter.rs index 9058e93c5de..875298ca566 100644 --- a/data_model/src/query/predicate/predicate_atoms/parameter.rs +++ b/data_model/src/query/predicate/predicate_atoms/parameter.rs @@ -8,12 +8,11 @@ use serde::{Deserialize, Serialize}; use super::impl_predicate_box; use crate::{ parameter::Parameter, - prelude::PredicateTrait, query::predicate::{ predicate_ast_extensions::AstPredicateExt as _, predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, projectors::BaseProjector, - AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, + AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, PredicateTrait, }, }; @@ -31,3 +30,8 @@ impl PredicateTrait for ParameterPredicateBox { } } } + +pub mod prelude { + //! Re-export all predicate boxes for a glob import `(::*)` + pub use super::ParameterPredicateBox; +} diff --git a/data_model/src/query/predicate/predicate_atoms/peer.rs b/data_model/src/query/predicate/predicate_atoms/peer.rs index 5df6dd5e19a..92f3a09990b 100644 --- a/data_model/src/query/predicate/predicate_atoms/peer.rs +++ b/data_model/src/query/predicate/predicate_atoms/peer.rs @@ -8,12 +8,11 @@ use serde::{Deserialize, Serialize}; use super::impl_predicate_box; use crate::{ peer::Peer, - prelude::PredicateTrait, query::predicate::{ predicate_ast_extensions::AstPredicateExt as _, predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, projectors::BaseProjector, - AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, + AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, PredicateTrait, }, }; #[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)] @@ -30,3 +29,8 @@ impl PredicateTrait for PeerPredicateBox { } } } + +pub mod prelude { + //! Re-export all predicate boxes for a glob import `(::*)` + pub use super::PeerPredicateBox; +} diff --git a/data_model/src/query/predicate/predicate_atoms/permission.rs b/data_model/src/query/predicate/predicate_atoms/permission.rs index 193dc09415b..0a26a5779b2 100644 --- a/data_model/src/query/predicate/predicate_atoms/permission.rs +++ b/data_model/src/query/predicate/predicate_atoms/permission.rs @@ -8,12 +8,11 @@ use serde::{Deserialize, Serialize}; use super::impl_predicate_box; use crate::{ permission::Permission, - prelude::PredicateTrait, query::predicate::{ predicate_ast_extensions::AstPredicateExt as _, predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, projectors::BaseProjector, - AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, + AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, PredicateTrait, }, }; @@ -31,3 +30,8 @@ impl PredicateTrait for PermissionPredicateBox { } } } + +pub mod prelude { + //! Re-export all predicate boxes for a glob import `(::*)` + pub use super::PermissionPredicateBox; +} diff --git a/data_model/src/query/predicate/predicate_atoms/role.rs b/data_model/src/query/predicate/predicate_atoms/role.rs index b2ee1c6a42a..e513e599a45 100644 --- a/data_model/src/query/predicate/predicate_atoms/role.rs +++ b/data_model/src/query/predicate/predicate_atoms/role.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use super::impl_predicate_box; use crate::{ - prelude::{PredicateTrait, Role, RoleId}, + prelude::{Role, RoleId}, query::{ predicate::{ predicate_ast_extensions::AstPredicateExt as _, @@ -15,7 +15,7 @@ use crate::{ predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, projectors::BaseProjector, }, - AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, + AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, PredicateTrait, }, }; @@ -53,3 +53,8 @@ impl PredicateTrait for RolePredicateBox { } } } + +pub mod prelude { + //! Re-export all predicate boxes for a glob import `(::*)` + pub use super::{RoleIdPredicateBox, RolePredicateBox}; +} diff --git a/data_model/src/query/predicate/predicate_atoms/trigger.rs b/data_model/src/query/predicate/predicate_atoms/trigger.rs index 23d150261fd..7847ce32186 100644 --- a/data_model/src/query/predicate/predicate_atoms/trigger.rs +++ b/data_model/src/query/predicate/predicate_atoms/trigger.rs @@ -7,13 +7,13 @@ use serde::{Deserialize, Serialize}; use super::impl_predicate_box; use crate::{ - prelude::{PredicateTrait, Trigger, TriggerId}, + prelude::{Trigger, TriggerId}, query::predicate::{ predicate_ast_extensions::AstPredicateExt as _, predicate_atoms::StringPredicateBox, predicate_combinators::{AndAstPredicate, NotAstPredicate, OrAstPredicate}, projectors::BaseProjector, - AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, + AstPredicate, CompoundPredicate, HasPredicateBox, HasPrototype, PredicateTrait, }, }; @@ -51,3 +51,8 @@ impl PredicateTrait for TriggerPredicateBox { } } } + +pub mod prelude { + //! Re-export all predicate boxes for a glob import `(::*)` + pub use super::{TriggerIdPredicateBox, TriggerPredicateBox}; +} diff --git a/data_model/src/query/predicate/prototypes/account.rs b/data_model/src/query/predicate/prototypes/account.rs index cc8b271440f..5d120097f6f 100644 --- a/data_model/src/query/predicate/prototypes/account.rs +++ b/data_model/src/query/predicate/prototypes/account.rs @@ -1,9 +1,6 @@ -use core::marker::PhantomData; - use super::impl_prototype; use crate::{ account::AccountId, - asset::AssetDefinitionId, query::{ predicate::{ predicate_atoms::account::{AccountIdPredicateBox, AccountPredicateBox}, diff --git a/data_model/src/query/sorting.rs b/data_model/src/query/sorting.rs deleted file mode 100644 index 82b2aeb595b..00000000000 --- a/data_model/src/query/sorting.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! Structures and traits related to sorting. - -#[cfg(not(feature = "std"))] -use alloc::{ - format, - string::{String, ToString as _}, - vec, - vec::Vec, -}; - -use iroha_data_model_derive::model; -use iroha_schema::IntoSchema; -use parity_scale_codec::{Decode, Encode}; -use serde::{Deserialize, Serialize}; - -pub use self::model::*; -use crate::{name::Name, prelude::*}; - -#[model] -mod model { - use super::*; - - /// Struct for sorting requests - #[derive( - Debug, Clone, Default, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, - )] - pub struct Sorting { - /// Sort query result using [`Name`] of the key in [`Asset`]'s metadata. - pub sort_by_metadata_key: Option, - } -} - -impl Sorting { - /// Creates a sorting by [`Name`] of the key. - pub fn by_metadata_key(key: Name) -> Self { - Self { - sort_by_metadata_key: Some(key), - } - } -} - -pub mod prelude { - //! Prelude: re-export most commonly used traits, structs and macros from this module. - pub use super::*; -} diff --git a/data_model/src/visit.rs b/data_model/src/visit.rs index f315487482e..f83448fe71c 100644 --- a/data_model/src/visit.rs +++ b/data_model/src/visit.rs @@ -6,7 +6,7 @@ use iroha_primitives::numeric::Numeric; use crate::{ isi::Log, prelude::*, - query::{IterableQueryWithFilterFor, IterableQueryWithParams, QueryBox2, SingularQueryBox}, + query::{IterableQueryWithFilterFor, IterableQueryWithParams, QueryBox, SingularQueryBox}, }; macro_rules! delegate { @@ -28,7 +28,7 @@ pub trait Visit { visit_transaction(&SignedTransaction), visit_instruction(&InstructionBox), visit_wasm(&WasmSmartContract), - visit_query(&QueryBox2), + visit_query(&QueryBox), visit_singular_query(&SingularQueryBox), visit_iter_query(&IterableQueryWithParams), @@ -215,10 +215,10 @@ pub fn visit_iter_query( } } -pub fn visit_query(visitor: &mut V, authority: &AccountId, query: &QueryBox2) { +pub fn visit_query(visitor: &mut V, authority: &AccountId, query: &QueryBox) { match query { - QueryBox2::Singular(query) => visitor.visit_singular_query(authority, query), - QueryBox2::Iterable(query) => visitor.visit_iter_query(authority, query), + QueryBox::Singular(query) => visitor.visit_singular_query(authority, query), + QueryBox::Iterable(query) => visitor.visit_iter_query(authority, query), } } diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index 65ed5f1a562..0e0d68de2eb 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -127,6 +127,25 @@ } ] }, + "AccountIdPredicateBox": { + "Enum": [ + { + "tag": "Equals", + "discriminant": 0, + "type": "AccountId" + }, + { + "tag": "DomainId", + "discriminant": 1, + "type": "DomainIdPredicateBox" + }, + { + "tag": "Signatory", + "discriminant": 2, + "type": "PublicKeyPredicateBox" + } + ] + }, "AccountPermissionChanged": { "Struct": [ { @@ -139,6 +158,20 @@ } ] }, + "AccountPredicateBox": { + "Enum": [ + { + "tag": "Id", + "discriminant": 0, + "type": "AccountIdPredicateBox" + }, + { + "tag": "Metadata", + "discriminant": 1, + "type": "MetadataPredicateBox" + } + ] + }, "AccountRoleChanged": { "Struct": [ { @@ -363,6 +396,25 @@ } ] }, + "AssetDefinitionIdPredicateBox": { + "Enum": [ + { + "tag": "Equals", + "discriminant": 0, + "type": "AssetDefinitionId" + }, + { + "tag": "DomainId", + "discriminant": 1, + "type": "DomainIdPredicateBox" + }, + { + "tag": "Name", + "discriminant": 2, + "type": "StringPredicateBox" + } + ] + }, "AssetDefinitionOwnerChanged": { "Struct": [ { @@ -375,6 +427,25 @@ } ] }, + "AssetDefinitionPredicateBox": { + "Enum": [ + { + "tag": "Id", + "discriminant": 0, + "type": "AssetDefinitionIdPredicateBox" + }, + { + "tag": "Metadata", + "discriminant": 1, + "type": "MetadataPredicateBox" + }, + { + "tag": "OwnedBy", + "discriminant": 2, + "type": "AccountIdPredicateBox" + } + ] + }, "AssetDefinitionTotalQuantityChanged": { "Struct": [ { @@ -476,6 +547,39 @@ } ] }, + "AssetIdPredicateBox": { + "Enum": [ + { + "tag": "Equals", + "discriminant": 0, + "type": "AssetId" + }, + { + "tag": "DefinitionId", + "discriminant": 1, + "type": "AssetDefinitionIdPredicateBox" + }, + { + "tag": "AccountId", + "discriminant": 2, + "type": "AccountIdPredicateBox" + } + ] + }, + "AssetPredicateBox": { + "Enum": [ + { + "tag": "Id", + "discriminant": 0, + "type": "AssetIdPredicateBox" + }, + { + "tag": "Value", + "discriminant": 1, + "type": "AssetValuePredicateBox" + } + ] + }, "AssetTransferBox": { "Enum": [ { @@ -517,38 +621,8 @@ } ] }, - "AtIndex": { - "Struct": [ - { - "name": "index", - "type": "u32" - }, - { - "name": "predicate", - "type": "QueryOutputPredicate" - } - ] - }, - "BatchedResponse": { - "Enum": [ - { - "tag": "V1", - "discriminant": 1, - "type": "BatchedResponseV1" - } - ] - }, - "BatchedResponseV1": { - "Struct": [ - { - "name": "batch", - "type": "QueryOutputBox" - }, - { - "name": "cursor", - "type": "ForwardCursor" - } - ] + "AssetValuePredicateBox": { + "Enum": [] }, "BlockEvent": { "Struct": [ @@ -606,6 +680,9 @@ } ] }, + "BlockHeaderPredicateBox": { + "Enum": [] + }, "BlockMessage": "SignedBlock", "BlockParameter": { "Enum": [ @@ -955,34 +1032,6 @@ }, "CanUpgradeExecutor": null, "ChainId": "String", - "ClientQueryPayload": { - "Struct": [ - { - "name": "authority", - "type": "AccountId" - }, - { - "name": "query", - "type": "QueryBox" - }, - { - "name": "filter", - "type": "GenericPredicateBox" - }, - { - "name": "sorting", - "type": "Sorting" - }, - { - "name": "pagination", - "type": "Pagination" - }, - { - "name": "fetch_size", - "type": "FetchSize" - } - ] - }, "CommittedTransaction": { "Struct": [ { @@ -1001,143 +1050,412 @@ "Compact": { "Int": "Compact" }, - "ConfigurationEvent": { + "CompoundPredicate": { "Enum": [ { - "tag": "Changed", + "tag": "Atom", "discriminant": 0, - "type": "ParameterChanged" - } - ] - }, - "ConfigurationEventFilter": { - "Struct": [ + "type": "AccountPredicateBox" + }, { - "name": "event_set", - "type": "ConfigurationEventSet" + "tag": "Not", + "discriminant": 1, + "type": "CompoundPredicate" + }, + { + "tag": "And", + "discriminant": 2, + "type": "Vec>" + }, + { + "tag": "Or", + "discriminant": 3, + "type": "Vec>" } ] }, - "ConfigurationEventSet": { - "Bitmap": { - "repr": "u32", - "masks": [ - { - "name": "Changed", - "mask": 1 - } - ] - } - }, - "Container": { + "CompoundPredicate": { "Enum": [ { - "tag": "Any", + "tag": "Atom", "discriminant": 0, - "type": "QueryOutputPredicate" + "type": "AssetDefinitionPredicateBox" }, { - "tag": "All", + "tag": "Not", "discriminant": 1, - "type": "QueryOutputPredicate" + "type": "CompoundPredicate" }, { - "tag": "AtIndex", + "tag": "And", "discriminant": 2, - "type": "AtIndex" - } - ] - }, - "CustomInstruction": { - "Struct": [ + "type": "Vec>" + }, { - "name": "payload", - "type": "JsonString" + "tag": "Or", + "discriminant": 3, + "type": "Vec>" } ] }, - "CustomParameter": { - "Struct": [ + "CompoundPredicate": { + "Enum": [ { - "name": "id", - "type": "CustomParameterId" + "tag": "Atom", + "discriminant": 0, + "type": "AssetPredicateBox" }, { - "name": "payload", - "type": "JsonString" + "tag": "Not", + "discriminant": 1, + "type": "CompoundPredicate" + }, + { + "tag": "And", + "discriminant": 2, + "type": "Vec>" + }, + { + "tag": "Or", + "discriminant": 3, + "type": "Vec>" } ] }, - "CustomParameterId": "Name", - "DataEvent": { + "CompoundPredicate": { "Enum": [ { - "tag": "Peer", + "tag": "Atom", "discriminant": 0, - "type": "PeerEvent" + "type": "BlockHeaderPredicateBox" }, { - "tag": "Domain", + "tag": "Not", "discriminant": 1, - "type": "DomainEvent" + "type": "CompoundPredicate" }, { - "tag": "Trigger", + "tag": "And", "discriminant": 2, - "type": "TriggerEvent" + "type": "Vec>" }, { - "tag": "Role", + "tag": "Or", "discriminant": 3, - "type": "RoleEvent" + "type": "Vec>" + } + ] + }, + "CompoundPredicate": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "DomainPredicateBox" }, { - "tag": "Configuration", - "discriminant": 4, - "type": "ConfigurationEvent" + "tag": "Not", + "discriminant": 1, + "type": "CompoundPredicate" }, { - "tag": "Executor", - "discriminant": 5, - "type": "ExecutorEvent" + "tag": "And", + "discriminant": 2, + "type": "Vec>" + }, + { + "tag": "Or", + "discriminant": 3, + "type": "Vec>" } ] }, - "DataEventFilter": { + "CompoundPredicate": { "Enum": [ { - "tag": "Any", - "discriminant": 0 + "tag": "Atom", + "discriminant": 0, + "type": "PeerPredicateBox" }, { - "tag": "Peer", + "tag": "Not", "discriminant": 1, - "type": "PeerEventFilter" + "type": "CompoundPredicate" }, { - "tag": "Domain", + "tag": "And", "discriminant": 2, - "type": "DomainEventFilter" + "type": "Vec>" }, { - "tag": "Account", + "tag": "Or", "discriminant": 3, - "type": "AccountEventFilter" - }, + "type": "Vec>" + } + ] + }, + "CompoundPredicate": { + "Enum": [ { - "tag": "Asset", - "discriminant": 4, - "type": "AssetEventFilter" + "tag": "Atom", + "discriminant": 0, + "type": "PermissionPredicateBox" }, { - "tag": "AssetDefinition", - "discriminant": 5, - "type": "AssetDefinitionEventFilter" + "tag": "Not", + "discriminant": 1, + "type": "CompoundPredicate" }, { - "tag": "Trigger", - "discriminant": 6, - "type": "TriggerEventFilter" + "tag": "And", + "discriminant": 2, + "type": "Vec>" + }, + { + "tag": "Or", + "discriminant": 3, + "type": "Vec>" + } + ] + }, + "CompoundPredicate": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "RoleIdPredicateBox" + }, + { + "tag": "Not", + "discriminant": 1, + "type": "CompoundPredicate" + }, + { + "tag": "And", + "discriminant": 2, + "type": "Vec>" + }, + { + "tag": "Or", + "discriminant": 3, + "type": "Vec>" + } + ] + }, + "CompoundPredicate": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "RolePredicateBox" + }, + { + "tag": "Not", + "discriminant": 1, + "type": "CompoundPredicate" + }, + { + "tag": "And", + "discriminant": 2, + "type": "Vec>" + }, + { + "tag": "Or", + "discriminant": 3, + "type": "Vec>" + } + ] + }, + "CompoundPredicate": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "SignedBlockPredicateBox" + }, + { + "tag": "Not", + "discriminant": 1, + "type": "CompoundPredicate" + }, + { + "tag": "And", + "discriminant": 2, + "type": "Vec>" + }, + { + "tag": "Or", + "discriminant": 3, + "type": "Vec>" + } + ] + }, + "CompoundPredicate": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "TransactionQueryOutputPredicateBox" + }, + { + "tag": "Not", + "discriminant": 1, + "type": "CompoundPredicate" + }, + { + "tag": "And", + "discriminant": 2, + "type": "Vec>" + }, + { + "tag": "Or", + "discriminant": 3, + "type": "Vec>" + } + ] + }, + "CompoundPredicate": { + "Enum": [ + { + "tag": "Atom", + "discriminant": 0, + "type": "TriggerIdPredicateBox" + }, + { + "tag": "Not", + "discriminant": 1, + "type": "CompoundPredicate" + }, + { + "tag": "And", + "discriminant": 2, + "type": "Vec>" + }, + { + "tag": "Or", + "discriminant": 3, + "type": "Vec>" + } + ] + }, + "ConfigurationEvent": { + "Enum": [ + { + "tag": "Changed", + "discriminant": 0, + "type": "ParameterChanged" + } + ] + }, + "ConfigurationEventFilter": { + "Struct": [ + { + "name": "event_set", + "type": "ConfigurationEventSet" + } + ] + }, + "ConfigurationEventSet": { + "Bitmap": { + "repr": "u32", + "masks": [ + { + "name": "Changed", + "mask": 1 + } + ] + } + }, + "CustomInstruction": { + "Struct": [ + { + "name": "payload", + "type": "JsonString" + } + ] + }, + "CustomParameter": { + "Struct": [ + { + "name": "id", + "type": "CustomParameterId" + }, + { + "name": "payload", + "type": "JsonString" + } + ] + }, + "CustomParameterId": "Name", + "DataEvent": { + "Enum": [ + { + "tag": "Peer", + "discriminant": 0, + "type": "PeerEvent" + }, + { + "tag": "Domain", + "discriminant": 1, + "type": "DomainEvent" + }, + { + "tag": "Trigger", + "discriminant": 2, + "type": "TriggerEvent" + }, + { + "tag": "Role", + "discriminant": 3, + "type": "RoleEvent" + }, + { + "tag": "Configuration", + "discriminant": 4, + "type": "ConfigurationEvent" + }, + { + "tag": "Executor", + "discriminant": 5, + "type": "ExecutorEvent" + } + ] + }, + "DataEventFilter": { + "Enum": [ + { + "tag": "Any", + "discriminant": 0 + }, + { + "tag": "Peer", + "discriminant": 1, + "type": "PeerEventFilter" + }, + { + "tag": "Domain", + "discriminant": 2, + "type": "DomainEventFilter" + }, + { + "tag": "Account", + "discriminant": 3, + "type": "AccountEventFilter" + }, + { + "tag": "Asset", + "discriminant": 4, + "type": "AssetEventFilter" + }, + { + "tag": "AssetDefinition", + "discriminant": 5, + "type": "AssetDefinitionEventFilter" + }, + { + "tag": "Trigger", + "discriminant": 6, + "type": "TriggerEventFilter" }, { "tag": "Role", @@ -1270,6 +1588,20 @@ } ] }, + "DomainIdPredicateBox": { + "Enum": [ + { + "tag": "Equals", + "discriminant": 0, + "type": "DomainId" + }, + { + "tag": "Name", + "discriminant": 1, + "type": "StringPredicateBox" + } + ] + }, "DomainOwnerChanged": { "Struct": [ { @@ -1282,6 +1614,20 @@ } ] }, + "DomainPredicateBox": { + "Enum": [ + { + "tag": "Id", + "discriminant": 0, + "type": "DomainIdPredicateBox" + }, + { + "tag": "Metadata", + "discriminant": 1, + "type": "MetadataPredicateBox" + } + ] + }, "Duration": { "Tuple": [ "u64", @@ -1487,14 +1833,6 @@ } ] }, - "FindAccountById": { - "Struct": [ - { - "name": "id", - "type": "AccountId" - } - ] - }, "FindAccountKeyValueByIdAndKey": { "Struct": [ { @@ -1507,14 +1845,6 @@ } ] }, - "FindAccountsByDomainId": { - "Struct": [ - { - "name": "domain", - "type": "DomainId" - } - ] - }, "FindAccountsWithAsset": { "Struct": [ { @@ -1535,23 +1865,7 @@ "FindAllRoleIds": null, "FindAllRoles": null, "FindAllTransactions": null, - "FindAssetById": { - "Struct": [ - { - "name": "id", - "type": "AssetId" - } - ] - }, - "FindAssetDefinitionById": { - "Struct": [ - { - "name": "id", - "type": "AssetDefinitionId" - } - ] - }, - "FindAssetDefinitionKeyValueByIdAndKey": { + "FindAssetDefinitionKeyValueByIdAndKey": { "Struct": [ { "name": "id", @@ -1583,50 +1897,6 @@ } ] }, - "FindAssetsByAccountId": { - "Struct": [ - { - "name": "account", - "type": "AccountId" - } - ] - }, - "FindAssetsByAssetDefinitionId": { - "Struct": [ - { - "name": "asset_definition", - "type": "AssetDefinitionId" - } - ] - }, - "FindAssetsByDomainId": { - "Struct": [ - { - "name": "domain", - "type": "DomainId" - } - ] - }, - "FindAssetsByDomainIdAndAssetDefinitionId": { - "Struct": [ - { - "name": "domain", - "type": "DomainId" - }, - { - "name": "asset_definition", - "type": "AssetDefinitionId" - } - ] - }, - "FindAssetsByName": { - "Struct": [ - { - "name": "name", - "type": "Name" - } - ] - }, "FindBlockHeaderByHash": { "Struct": [ { @@ -1635,14 +1905,6 @@ } ] }, - "FindDomainById": { - "Struct": [ - { - "name": "id", - "type": "DomainId" - } - ] - }, "FindDomainKeyValueByIdAndKey": { "Struct": [ { @@ -1728,14 +1990,6 @@ } ] }, - "FindRoleByRoleId": { - "Struct": [ - { - "name": "id", - "type": "RoleId" - } - ] - }, "FindRolesByAccountId": { "Struct": [ { @@ -1788,55 +2042,15 @@ } ] }, - "FindTriggersByAuthorityDomainId": { - "Struct": [ - { - "name": "domain", - "type": "DomainId" - } - ] - }, - "FindTriggersByAuthorityId": { - "Struct": [ - { - "name": "account", - "type": "AccountId" - } - ] - }, "ForwardCursor": { "Struct": [ { "name": "query", - "type": "Option" + "type": "String" }, { "name": "cursor", - "type": "Option>" - } - ] - }, - "GenericPredicateBox": { - "Enum": [ - { - "tag": "And", - "discriminant": 0, - "type": "NonTrivial>" - }, - { - "tag": "Or", - "discriminant": 1, - "type": "NonTrivial>" - }, - { - "tag": "Not", - "discriminant": 2, - "type": "GenericPredicateBox" - }, - { - "tag": "Raw", - "discriminant": 3, - "type": "QueryOutputPredicate" + "type": "NonZero" } ] }, @@ -1948,70 +2162,6 @@ } ] }, - "IdentifiableBox": { - "Enum": [ - { - "tag": "NewDomain", - "discriminant": 0, - "type": "NewDomain" - }, - { - "tag": "NewAccount", - "discriminant": 1, - "type": "NewAccount" - }, - { - "tag": "NewAssetDefinition", - "discriminant": 2, - "type": "NewAssetDefinition" - }, - { - "tag": "NewRole", - "discriminant": 3, - "type": "NewRole" - }, - { - "tag": "Peer", - "discriminant": 4, - "type": "Peer" - }, - { - "tag": "Domain", - "discriminant": 5, - "type": "Domain" - }, - { - "tag": "Account", - "discriminant": 6, - "type": "Account" - }, - { - "tag": "AssetDefinition", - "discriminant": 7, - "type": "AssetDefinition" - }, - { - "tag": "Asset", - "discriminant": 8, - "type": "Asset" - }, - { - "tag": "Trigger", - "discriminant": 9, - "type": "Trigger" - }, - { - "tag": "Role", - "discriminant": 10, - "type": "Role" - }, - { - "tag": "CustomParameter", - "discriminant": 11, - "type": "CustomParameter" - } - ] - }, "InstructionBox": { "Enum": [ { @@ -2154,98 +2304,466 @@ } ] }, - "InstructionExecutionFail": { + "InstructionExecutionFail": { + "Struct": [ + { + "name": "instruction", + "type": "InstructionBox" + }, + { + "name": "reason", + "type": "String" + } + ] + }, + "InstructionType": { + "Enum": [ + { + "tag": "Register", + "discriminant": 0 + }, + { + "tag": "Unregister", + "discriminant": 1 + }, + { + "tag": "Mint", + "discriminant": 2 + }, + { + "tag": "Burn", + "discriminant": 3 + }, + { + "tag": "Transfer", + "discriminant": 4 + }, + { + "tag": "SetKeyValue", + "discriminant": 5 + }, + { + "tag": "RemoveKeyValue", + "discriminant": 6 + }, + { + "tag": "Grant", + "discriminant": 7 + }, + { + "tag": "Revoke", + "discriminant": 8 + }, + { + "tag": "ExecuteTrigger", + "discriminant": 9 + }, + { + "tag": "SetParameter", + "discriminant": 10 + }, + { + "tag": "Upgrade", + "discriminant": 11 + }, + { + "tag": "Log", + "discriminant": 12 + }, + { + "tag": "Custom", + "discriminant": 13 + } + ] + }, + "InvalidParameterError": { + "Enum": [ + { + "tag": "Wasm", + "discriminant": 0, + "type": "String" + }, + { + "tag": "NameLength", + "discriminant": 1 + }, + { + "tag": "TimeTriggerInThePast", + "discriminant": 2 + } + ] + }, + "IpfsPath": "String", + "Ipv4Addr": "Array", + "Ipv6Addr": "Array", + "IterableQueryBox": { + "Enum": [ + { + "tag": "FindAllDomains", + "discriminant": 0, + "type": "IterableQueryWithFilter" + }, + { + "tag": "FindAllAccounts", + "discriminant": 1, + "type": "IterableQueryWithFilter" + }, + { + "tag": "FindAllAssets", + "discriminant": 2, + "type": "IterableQueryWithFilter" + }, + { + "tag": "FindAllAssetsDefinitions", + "discriminant": 3, + "type": "IterableQueryWithFilter" + }, + { + "tag": "FindAllRoles", + "discriminant": 4, + "type": "IterableQueryWithFilter" + }, + { + "tag": "FindAllRoleIds", + "discriminant": 5, + "type": "IterableQueryWithFilter" + }, + { + "tag": "FindPermissionsByAccountId", + "discriminant": 6, + "type": "IterableQueryWithFilter" + }, + { + "tag": "FindRolesByAccountId", + "discriminant": 7, + "type": "IterableQueryWithFilter" + }, + { + "tag": "FindTransactionsByAccountId", + "discriminant": 8, + "type": "IterableQueryWithFilter" + }, + { + "tag": "FindAccountsWithAsset", + "discriminant": 9, + "type": "IterableQueryWithFilter" + }, + { + "tag": "FindAllPeers", + "discriminant": 10, + "type": "IterableQueryWithFilter" + }, + { + "tag": "FindAllActiveTriggerIds", + "discriminant": 11, + "type": "IterableQueryWithFilter" + }, + { + "tag": "FindAllTransactions", + "discriminant": 12, + "type": "IterableQueryWithFilter" + }, + { + "tag": "FindAllBlocks", + "discriminant": 13, + "type": "IterableQueryWithFilter" + }, + { + "tag": "FindAllBlockHeaders", + "discriminant": 14, + "type": "IterableQueryWithFilter" + } + ] + }, + "IterableQueryOutput": { + "Struct": [ + { + "name": "batch", + "type": "IterableQueryOutputBatchBox" + }, + { + "name": "continue_cursor", + "type": "Option" + } + ] + }, + "IterableQueryOutputBatchBox": { + "Enum": [ + { + "tag": "Domain", + "discriminant": 0, + "type": "Vec" + }, + { + "tag": "Account", + "discriminant": 1, + "type": "Vec" + }, + { + "tag": "Asset", + "discriminant": 2, + "type": "Vec" + }, + { + "tag": "AssetDefinition", + "discriminant": 3, + "type": "Vec" + }, + { + "tag": "Role", + "discriminant": 4, + "type": "Vec" + }, + { + "tag": "Parameter", + "discriminant": 5, + "type": "Vec" + }, + { + "tag": "Permission", + "discriminant": 6, + "type": "Vec" + }, + { + "tag": "Transaction", + "discriminant": 7, + "type": "Vec" + }, + { + "tag": "Peer", + "discriminant": 8, + "type": "Vec" + }, + { + "tag": "RoleId", + "discriminant": 9, + "type": "Vec" + }, + { + "tag": "TriggerId", + "discriminant": 10, + "type": "Vec" + }, + { + "tag": "Block", + "discriminant": 11, + "type": "Vec" + }, + { + "tag": "BlockHeader", + "discriminant": 12, + "type": "Vec" + } + ] + }, + "IterableQueryParams": { + "Struct": [ + { + "name": "pagination", + "type": "Pagination" + }, + { + "name": "sorting", + "type": "Sorting" + }, + { + "name": "fetch_size", + "type": "FetchSize" + } + ] + }, + "IterableQueryWithFilter": { + "Struct": [ + { + "name": "query", + "type": "FindAccountsWithAsset" + }, + { + "name": "predicate", + "type": "CompoundPredicate" + } + ] + }, + "IterableQueryWithFilter": { + "Struct": [ + { + "name": "query", + "type": "FindAllAccounts" + }, + { + "name": "predicate", + "type": "CompoundPredicate" + } + ] + }, + "IterableQueryWithFilter": { + "Struct": [ + { + "name": "query", + "type": "FindAllActiveTriggerIds" + }, + { + "name": "predicate", + "type": "CompoundPredicate" + } + ] + }, + "IterableQueryWithFilter": { + "Struct": [ + { + "name": "query", + "type": "FindAllAssets" + }, + { + "name": "predicate", + "type": "CompoundPredicate" + } + ] + }, + "IterableQueryWithFilter": { + "Struct": [ + { + "name": "query", + "type": "FindAllAssetsDefinitions" + }, + { + "name": "predicate", + "type": "CompoundPredicate" + } + ] + }, + "IterableQueryWithFilter": { + "Struct": [ + { + "name": "query", + "type": "FindAllBlockHeaders" + }, + { + "name": "predicate", + "type": "CompoundPredicate" + } + ] + }, + "IterableQueryWithFilter": { + "Struct": [ + { + "name": "query", + "type": "FindAllBlocks" + }, + { + "name": "predicate", + "type": "CompoundPredicate" + } + ] + }, + "IterableQueryWithFilter": { + "Struct": [ + { + "name": "query", + "type": "FindAllDomains" + }, + { + "name": "predicate", + "type": "CompoundPredicate" + } + ] + }, + "IterableQueryWithFilter": { "Struct": [ { - "name": "instruction", - "type": "InstructionBox" + "name": "query", + "type": "FindAllPeers" }, { - "name": "reason", - "type": "String" + "name": "predicate", + "type": "CompoundPredicate" } ] }, - "InstructionType": { - "Enum": [ - { - "tag": "Register", - "discriminant": 0 - }, - { - "tag": "Unregister", - "discriminant": 1 - }, + "IterableQueryWithFilter": { + "Struct": [ { - "tag": "Mint", - "discriminant": 2 + "name": "query", + "type": "FindAllRoleIds" }, { - "tag": "Burn", - "discriminant": 3 - }, + "name": "predicate", + "type": "CompoundPredicate" + } + ] + }, + "IterableQueryWithFilter": { + "Struct": [ { - "tag": "Transfer", - "discriminant": 4 + "name": "query", + "type": "FindAllRoles" }, { - "tag": "SetKeyValue", - "discriminant": 5 - }, + "name": "predicate", + "type": "CompoundPredicate" + } + ] + }, + "IterableQueryWithFilter": { + "Struct": [ { - "tag": "RemoveKeyValue", - "discriminant": 6 + "name": "query", + "type": "FindAllTransactions" }, { - "tag": "Grant", - "discriminant": 7 - }, + "name": "predicate", + "type": "CompoundPredicate" + } + ] + }, + "IterableQueryWithFilter": { + "Struct": [ { - "tag": "Revoke", - "discriminant": 8 + "name": "query", + "type": "FindPermissionsByAccountId" }, { - "tag": "ExecuteTrigger", - "discriminant": 9 - }, + "name": "predicate", + "type": "CompoundPredicate" + } + ] + }, + "IterableQueryWithFilter": { + "Struct": [ { - "tag": "SetParameter", - "discriminant": 10 + "name": "query", + "type": "FindRolesByAccountId" }, { - "tag": "Upgrade", - "discriminant": 11 - }, + "name": "predicate", + "type": "CompoundPredicate" + } + ] + }, + "IterableQueryWithFilter": { + "Struct": [ { - "tag": "Log", - "discriminant": 12 + "name": "query", + "type": "FindTransactionsByAccountId" }, { - "tag": "Custom", - "discriminant": 13 + "name": "predicate", + "type": "CompoundPredicate" } ] }, - "InvalidParameterError": { - "Enum": [ - { - "tag": "Wasm", - "discriminant": 0, - "type": "String" - }, + "IterableQueryWithParams": { + "Struct": [ { - "tag": "NameLength", - "discriminant": 1 + "name": "query", + "type": "IterableQueryBox" }, { - "tag": "TimeTriggerInThePast", - "discriminant": 2 + "name": "params", + "type": "IterableQueryParams" } ] }, - "IpfsPath": "String", - "Ipv4Addr": "Array", - "Ipv6Addr": "Array", "JsonString": "String", "Level": { "Enum": [ @@ -2400,6 +2918,9 @@ } ] }, + "MetadataPredicateBox": { + "Enum": [] + }, "Mint": { "Struct": [ { @@ -2539,7 +3060,6 @@ } ] }, - "NonTrivial>": "Vec>", "NonZero": "u32", "NonZero": "u64", "Numeric": { @@ -2580,6 +3100,9 @@ "Option": { "Option": "Duration" }, + "Option": { + "Option": "ForwardCursor" + }, "Option>": { "Option": "HashOf" }, @@ -2610,9 +3133,6 @@ "Option": { "Option": "RoleId" }, - "Option": { - "Option": "String" - }, "Option": { "Option": "TimeInterval" }, @@ -2778,6 +3298,9 @@ } ] }, + "PeerPredicateBox": { + "Enum": [] + }, "Permission": { "Struct": [ { @@ -2790,6 +3313,9 @@ } ] }, + "PermissionPredicateBox": { + "Enum": [] + }, "PipelineEventBox": { "Enum": [ { @@ -2822,215 +3348,20 @@ "Struct": [ { "name": "algorithm", - "type": "Algorithm" - }, - { - "name": "payload", - "type": "Vec" - } - ] - }, - "QueryBox": { - "Enum": [ - { - "tag": "FindAllAccounts", - "discriminant": 0, - "type": "FindAllAccounts" - }, - { - "tag": "FindAccountById", - "discriminant": 1, - "type": "FindAccountById" - }, - { - "tag": "FindAccountKeyValueByIdAndKey", - "discriminant": 2, - "type": "FindAccountKeyValueByIdAndKey" - }, - { - "tag": "FindAccountsByDomainId", - "discriminant": 3, - "type": "FindAccountsByDomainId" - }, - { - "tag": "FindAccountsWithAsset", - "discriminant": 4, - "type": "FindAccountsWithAsset" - }, - { - "tag": "FindAllAssets", - "discriminant": 5, - "type": "FindAllAssets" - }, - { - "tag": "FindAllAssetsDefinitions", - "discriminant": 6, - "type": "FindAllAssetsDefinitions" - }, - { - "tag": "FindAssetById", - "discriminant": 7, - "type": "FindAssetById" - }, - { - "tag": "FindAssetDefinitionById", - "discriminant": 8, - "type": "FindAssetDefinitionById" - }, - { - "tag": "FindAssetsByName", - "discriminant": 9, - "type": "FindAssetsByName" - }, - { - "tag": "FindAssetsByAccountId", - "discriminant": 10, - "type": "FindAssetsByAccountId" - }, - { - "tag": "FindAssetsByAssetDefinitionId", - "discriminant": 11, - "type": "FindAssetsByAssetDefinitionId" - }, - { - "tag": "FindAssetsByDomainId", - "discriminant": 12, - "type": "FindAssetsByDomainId" - }, - { - "tag": "FindAssetsByDomainIdAndAssetDefinitionId", - "discriminant": 13, - "type": "FindAssetsByDomainIdAndAssetDefinitionId" - }, - { - "tag": "FindAssetQuantityById", - "discriminant": 14, - "type": "FindAssetQuantityById" - }, - { - "tag": "FindTotalAssetQuantityByAssetDefinitionId", - "discriminant": 15, - "type": "FindTotalAssetQuantityByAssetDefinitionId" - }, - { - "tag": "FindAssetKeyValueByIdAndKey", - "discriminant": 16, - "type": "FindAssetKeyValueByIdAndKey" - }, - { - "tag": "FindAssetDefinitionKeyValueByIdAndKey", - "discriminant": 17, - "type": "FindAssetDefinitionKeyValueByIdAndKey" - }, - { - "tag": "FindAllDomains", - "discriminant": 18, - "type": "FindAllDomains" - }, - { - "tag": "FindDomainById", - "discriminant": 19, - "type": "FindDomainById" - }, - { - "tag": "FindDomainKeyValueByIdAndKey", - "discriminant": 20, - "type": "FindDomainKeyValueByIdAndKey" - }, - { - "tag": "FindAllPeers", - "discriminant": 21, - "type": "FindAllPeers" - }, - { - "tag": "FindAllBlocks", - "discriminant": 22, - "type": "FindAllBlocks" - }, - { - "tag": "FindAllBlockHeaders", - "discriminant": 23, - "type": "FindAllBlockHeaders" - }, - { - "tag": "FindBlockHeaderByHash", - "discriminant": 24, - "type": "FindBlockHeaderByHash" - }, - { - "tag": "FindAllTransactions", - "discriminant": 25, - "type": "FindAllTransactions" - }, - { - "tag": "FindTransactionsByAccountId", - "discriminant": 26, - "type": "FindTransactionsByAccountId" - }, - { - "tag": "FindTransactionByHash", - "discriminant": 27, - "type": "FindTransactionByHash" - }, - { - "tag": "FindPermissionsByAccountId", - "discriminant": 28, - "type": "FindPermissionsByAccountId" - }, - { - "tag": "FindExecutorDataModel", - "discriminant": 29, - "type": "FindExecutorDataModel" - }, - { - "tag": "FindAllActiveTriggerIds", - "discriminant": 30, - "type": "FindAllActiveTriggerIds" - }, - { - "tag": "FindTriggerById", - "discriminant": 31, - "type": "FindTriggerById" - }, - { - "tag": "FindTriggerKeyValueByIdAndKey", - "discriminant": 32, - "type": "FindTriggerKeyValueByIdAndKey" - }, - { - "tag": "FindTriggersByAuthorityId", - "discriminant": 33, - "type": "FindTriggersByAuthorityId" - }, - { - "tag": "FindTriggersByAuthorityDomainId", - "discriminant": 34, - "type": "FindTriggersByAuthorityDomainId" - }, - { - "tag": "FindAllRoles", - "discriminant": 35, - "type": "FindAllRoles" - }, - { - "tag": "FindAllRoleIds", - "discriminant": 36, - "type": "FindAllRoleIds" - }, - { - "tag": "FindRoleByRoleId", - "discriminant": 37, - "type": "FindRoleByRoleId" + "type": "Algorithm" }, { - "tag": "FindRolesByAccountId", - "discriminant": 38, - "type": "FindRolesByAccountId" - }, + "name": "payload", + "type": "Vec" + } + ] + }, + "PublicKeyPredicateBox": { + "Enum": [ { - "tag": "FindAllParameters", - "discriminant": 39, - "type": "FindAllParameters" + "tag": "Equals", + "discriminant": 0, + "type": "PublicKey" } ] }, @@ -3060,99 +3391,52 @@ } ] }, - "QueryOutputBox": { + "QueryRequest": { "Enum": [ { - "tag": "Id", + "tag": "Singular", "discriminant": 0, - "type": "IdBox" + "type": "SingularQueryBox" }, { - "tag": "Identifiable", + "tag": "StartIterable", "discriminant": 1, - "type": "IdentifiableBox" + "type": "IterableQueryWithParams" }, { - "tag": "Transaction", + "tag": "ContinueIterable", "discriminant": 2, - "type": "TransactionQueryOutput" - }, - { - "tag": "Permission", - "discriminant": 3, - "type": "Permission" - }, - { - "tag": "Parameters", - "discriminant": 4, - "type": "Parameters" - }, - { - "tag": "Metadata", - "discriminant": 5, - "type": "JsonString" - }, - { - "tag": "Numeric", - "discriminant": 6, - "type": "Numeric" - }, - { - "tag": "BlockHeader", - "discriminant": 7, - "type": "BlockHeader" - }, - { - "tag": "Block", - "discriminant": 8, - "type": "SignedBlock" - }, + "type": "ForwardCursor" + } + ] + }, + "QueryRequestWithAuthority": { + "Struct": [ { - "tag": "ExecutorDataModel", - "discriminant": 9, - "type": "ExecutorDataModel" + "name": "authority", + "type": "AccountId" }, { - "tag": "Vec", - "discriminant": 10, - "type": "Vec" + "name": "request", + "type": "QueryRequest" } ] }, - "QueryOutputPredicate": { + "QueryResponse": { "Enum": [ { - "tag": "Identifiable", + "tag": "Singular", "discriminant": 0, - "type": "StringPredicate" + "type": "SingularQueryOutputBox" }, { - "tag": "Container", + "tag": "Iterable", "discriminant": 1, - "type": "Container" - }, - { - "tag": "Display", - "discriminant": 2, - "type": "StringPredicate" - }, - { - "tag": "Numerical", - "discriminant": 3, - "type": "SemiRange" - }, - { - "tag": "TimeStamp", - "discriminant": 4, - "type": "SemiInterval" - }, - { - "tag": "Pass", - "discriminant": 5 + "type": "IterableQueryOutput" } ] }, - "QuerySignature": "SignatureOf", + "QuerySignature": "SignatureOf", "RawGenesisTransaction": { "Struct": [ { @@ -3520,60 +3804,50 @@ } ] }, - "RolePermissionChanged": { - "Struct": [ + "RoleIdPredicateBox": { + "Enum": [ { - "name": "role", + "tag": "Equals", + "discriminant": 0, "type": "RoleId" }, { - "name": "permission", - "type": "Permission" + "tag": "Name", + "discriminant": 1, + "type": "StringPredicateBox" } ] }, - "Schedule": { + "RolePermissionChanged": { "Struct": [ { - "name": "start", - "type": "Duration" + "name": "role", + "type": "RoleId" }, { - "name": "period", - "type": "Option" + "name": "permission", + "type": "Permission" } ] }, - "SemiInterval": { - "Struct": [ - { - "name": "start", - "type": "Numeric" - }, + "RolePredicateBox": { + "Enum": [ { - "name": "limit", - "type": "Numeric" + "tag": "Id", + "discriminant": 0, + "type": "RoleIdPredicateBox" } ] }, - "SemiInterval": { + "Schedule": { "Struct": [ { "name": "start", - "type": "u128" + "type": "Duration" }, { - "name": "limit", - "type": "u128" - } - ] - }, - "SemiRange": { - "Enum": [ - { - "tag": "Numeric", - "discriminant": 0, - "type": "SemiInterval" + "name": "period", + "type": "Option" } ] }, @@ -3696,7 +3970,7 @@ ] }, "SignatureOf": "Signature", - "SignatureOf": "Signature", + "SignatureOf": "Signature", "SignatureOf": "Signature", "SignedBlock": { "Enum": [ @@ -3707,6 +3981,9 @@ } ] }, + "SignedBlockPredicateBox": { + "Enum": [] + }, "SignedBlockV1": { "Struct": [ { @@ -3736,7 +4013,7 @@ }, { "name": "payload", - "type": "ClientQueryPayload" + "type": "QueryRequestWithAuthority" } ] }, @@ -3761,6 +4038,109 @@ } ] }, + "SingularQueryBox": { + "Enum": [ + { + "tag": "FindAssetQuantityById", + "discriminant": 0, + "type": "FindAssetQuantityById" + }, + { + "tag": "FindExecutorDataModel", + "discriminant": 1, + "type": "FindExecutorDataModel" + }, + { + "tag": "FindAllParameters", + "discriminant": 2, + "type": "FindAllParameters" + }, + { + "tag": "FindTotalAssetQuantityByAssetDefinitionId", + "discriminant": 3, + "type": "FindTotalAssetQuantityByAssetDefinitionId" + }, + { + "tag": "FindTriggerById", + "discriminant": 4, + "type": "FindTriggerById" + }, + { + "tag": "FindDomainKeyValueByIdAndKey", + "discriminant": 5, + "type": "FindDomainKeyValueByIdAndKey" + }, + { + "tag": "FindAccountKeyValueByIdAndKey", + "discriminant": 6, + "type": "FindAccountKeyValueByIdAndKey" + }, + { + "tag": "FindAssetKeyValueByIdAndKey", + "discriminant": 7, + "type": "FindAssetKeyValueByIdAndKey" + }, + { + "tag": "FindAssetDefinitionKeyValueByIdAndKey", + "discriminant": 8, + "type": "FindAssetDefinitionKeyValueByIdAndKey" + }, + { + "tag": "FindTriggerKeyValueByIdAndKey", + "discriminant": 9, + "type": "FindTriggerKeyValueByIdAndKey" + }, + { + "tag": "FindTransactionByHash", + "discriminant": 10, + "type": "FindTransactionByHash" + }, + { + "tag": "FindBlockHeaderByHash", + "discriminant": 11, + "type": "FindBlockHeaderByHash" + } + ] + }, + "SingularQueryOutputBox": { + "Enum": [ + { + "tag": "Numeric", + "discriminant": 0, + "type": "Numeric" + }, + { + "tag": "ExecutorDataModel", + "discriminant": 1, + "type": "ExecutorDataModel" + }, + { + "tag": "JsonString", + "discriminant": 2, + "type": "JsonString" + }, + { + "tag": "Trigger", + "discriminant": 3, + "type": "Trigger" + }, + { + "tag": "Parameters", + "discriminant": 4, + "type": "Parameters" + }, + { + "tag": "Transaction", + "discriminant": 5, + "type": "TransactionQueryOutput" + }, + { + "tag": "BlockHeader", + "discriminant": 6, + "type": "BlockHeader" + } + ] + }, "SmartContractParameter": { "Enum": [ { @@ -3869,25 +4249,25 @@ ] }, "String": "String", - "StringPredicate": { + "StringPredicateBox": { "Enum": [ { - "tag": "Contains", + "tag": "Equals", "discriminant": 0, "type": "String" }, { - "tag": "StartsWith", + "tag": "Contains", "discriminant": 1, "type": "String" }, { - "tag": "EndsWith", + "tag": "StartsWith", "discriminant": 2, "type": "String" }, { - "tag": "Is", + "tag": "EndsWith", "discriminant": 3, "type": "String" } @@ -4054,6 +4434,9 @@ } ] }, + "TransactionQueryOutputPredicateBox": { + "Enum": [] + }, "TransactionRejectionReason": { "Enum": [ { @@ -4334,6 +4717,20 @@ } ] }, + "TriggerIdPredicateBox": { + "Enum": [ + { + "tag": "Equals", + "discriminant": 0, + "type": "TriggerId" + }, + { + "tag": "Name", + "discriminant": 1, + "type": "StringPredicateBox" + } + ] + }, "TriggerNumberOfExecutionsChanged": { "Struct": [ { @@ -4514,32 +4911,98 @@ } ] }, + "Vec": { + "Vec": "Account" + }, + "Vec": { + "Vec": "Asset" + }, + "Vec": { + "Vec": "AssetDefinition" + }, + "Vec": { + "Vec": "BlockHeader" + }, "Vec": { "Vec": "BlockSignature" }, "Vec": { "Vec": "CommittedTransaction" }, + "Vec>": { + "Vec": "CompoundPredicate" + }, + "Vec>": { + "Vec": "CompoundPredicate" + }, + "Vec>": { + "Vec": "CompoundPredicate" + }, + "Vec>": { + "Vec": "CompoundPredicate" + }, + "Vec>": { + "Vec": "CompoundPredicate" + }, + "Vec>": { + "Vec": "CompoundPredicate" + }, + "Vec>": { + "Vec": "CompoundPredicate" + }, + "Vec>": { + "Vec": "CompoundPredicate" + }, + "Vec>": { + "Vec": "CompoundPredicate" + }, + "Vec>": { + "Vec": "CompoundPredicate" + }, + "Vec>": { + "Vec": "CompoundPredicate" + }, + "Vec>": { + "Vec": "CompoundPredicate" + }, + "Vec": { + "Vec": "Domain" + }, "Vec": { "Vec": "EventBox" }, "Vec": { "Vec": "EventFilterBox" }, - "Vec>": { - "Vec": "GenericPredicateBox" - }, "Vec": { "Vec": "InstructionBox" }, "Vec": { "Vec": "Parameter" }, + "Vec": { + "Vec": "Peer" + }, "Vec": { "Vec": "PeerId" }, - "Vec": { - "Vec": "QueryOutputBox" + "Vec": { + "Vec": "Permission" + }, + "Vec": { + "Vec": "Role" + }, + "Vec": { + "Vec": "RoleId" + }, + "Vec": { + "Vec": "SignedBlock" + }, + "Vec": { + "Vec": "TransactionQueryOutput" + }, + "Vec": { + "Vec": "TriggerId" }, "Vec": { "Vec": "u8" @@ -4553,9 +5016,6 @@ ] }, "WasmSmartContract": "Vec", - "u128": { - "Int": "FixedWidth" - }, "u16": { "Int": "FixedWidth" }, diff --git a/schema/gen/src/lib.rs b/schema/gen/src/lib.rs index 0b8fb381f22..4f33d08a8ce 100644 --- a/schema/gen/src/lib.rs +++ b/schema/gen/src/lib.rs @@ -4,8 +4,7 @@ use iroha_crypto::MerkleTree; use iroha_data_model::{ block::stream::{BlockMessage, BlockSubscriptionRequest}, - query::QueryOutputBox, - BatchedResponse, + query::{QueryResponse, SignedQuery}, }; use iroha_schema::prelude::*; @@ -43,7 +42,7 @@ pub fn build_schemas() -> MetaMap { // Query + response SignedQuery, - BatchedResponse, + QueryResponse, // Event stream EventMessage, @@ -103,7 +102,9 @@ types!( AccountEventFilter, AccountEventSet, AccountId, + AccountIdPredicateBox, AccountPermissionChanged, + AccountPredicateBox, AccountRoleChanged, Action, Algorithm, @@ -114,28 +115,24 @@ types!( AssetDefinitionEventFilter, AssetDefinitionEventSet, AssetDefinitionId, + AssetDefinitionIdPredicateBox, AssetDefinitionOwnerChanged, + AssetDefinitionPredicateBox, AssetDefinitionTotalQuantityChanged, AssetEvent, AssetEventFilter, AssetEventSet, AssetId, + AssetIdPredicateBox, + AssetPredicateBox, AssetTransferBox, AssetType, AssetValue, - AtIndex, - BTreeMap, - BTreeMap, - BTreeMap, - BTreeMap, - BTreeMap, - BTreeSet, - BTreeSet, - BatchedResponse, - BatchedResponseV1, + AssetValuePredicateBox, BlockEvent, BlockEventFilter, BlockHeader, + BlockHeaderPredicateBox, BlockMessage, BlockParameter, BlockParameters, @@ -144,21 +141,45 @@ types!( BlockSignature, BlockStatus, BlockSubscriptionRequest, - Box>, - Box, + Box>, + Box>, + Box>, + Box>, + Box>, + Box>, + Box>, + Box>, + Box>, + Box>, + Box>, + Box>, Box, + BTreeMap, + BTreeMap, + BTreeSet, + BTreeSet, + BurnBox, Burn, Burn, - BurnBox, ChainId, - ClientQueryPayload, CommittedTransaction, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, + CompoundPredicate, ConfigurationEvent, ConfigurationEventFilter, ConfigurationEventSet, ConstString, ConstVec, - Container, CustomInstruction, CustomParameter, CustomParameterId, @@ -169,7 +190,9 @@ types!( DomainEventFilter, DomainEventSet, DomainId, + DomainIdPredicateBox, DomainOwnerChanged, + DomainPredicateBox, Duration, EventBox, EventFilterBox, @@ -188,9 +211,7 @@ types!( ExecutorPath, ExecutorUpgrade, FetchSize, - FindAccountById, FindAccountKeyValueByIdAndKey, - FindAccountsByDomainId, FindAccountsWithAsset, FindAllAccounts, FindAllActiveTriggerIds, @@ -204,42 +225,30 @@ types!( FindAllRoleIds, FindAllRoles, FindAllTransactions, - FindAssetById, - FindAssetDefinitionById, FindAssetDefinitionKeyValueByIdAndKey, FindAssetKeyValueByIdAndKey, FindAssetQuantityById, - FindAssetsByAccountId, - FindAssetsByAssetDefinitionId, - FindAssetsByDomainId, - FindAssetsByDomainIdAndAssetDefinitionId, - FindAssetsByName, FindBlockHeaderByHash, - FindDomainById, FindDomainKeyValueByIdAndKey, FindError, FindExecutorDataModel, FindPermissionsByAccountId, - FindRoleByRoleId, FindRolesByAccountId, FindTotalAssetQuantityByAssetDefinitionId, FindTransactionByHash, FindTransactionsByAccountId, FindTriggerById, FindTriggerKeyValueByIdAndKey, - FindTriggersByAuthorityDomainId, - FindTriggersByAuthorityId, ForwardCursor, + GrantBox, Grant, Grant, Grant, - GrantBox, Hash, HashOf>, HashOf, HashOf, IdBox, - IdentifiableBox, InstructionBox, InstructionEvaluationError, InstructionExecutionError, @@ -249,6 +258,26 @@ types!( IpfsPath, Ipv4Addr, Ipv6Addr, + IterableQueryBox, + IterableQueryOutput, + IterableQueryOutputBatchBox, + IterableQueryParams, + IterableQueryWithFilter, + IterableQueryWithFilter, + IterableQueryWithFilter, + IterableQueryWithFilter, + IterableQueryWithFilter, + IterableQueryWithFilter, + IterableQueryWithFilter, + IterableQueryWithFilter, + IterableQueryWithFilter, + IterableQueryWithFilter, + IterableQueryWithFilter, + IterableQueryWithFilter, + IterableQueryWithFilter, + IterableQueryWithFilter, + IterableQueryWithFilter, + IterableQueryWithParams, JsonString, Level, Log, @@ -260,18 +289,18 @@ types!( MetadataChanged, MetadataChanged, MetadataChanged, - Mint, - Mint, - MintBox, + MetadataPredicateBox, MintabilityError, Mintable, + MintBox, + Mint, + Mint, Mismatch, Name, NewAccount, NewAssetDefinition, NewDomain, NewRole, - NonTrivial, NonZeroU32, NonZeroU64, Numeric, @@ -282,6 +311,7 @@ types!( Option, Option, Option, + Option, Option>, Option>, Option, @@ -292,7 +322,6 @@ types!( Option>, Option, Option, - Option, Option, Option, Option, @@ -308,62 +337,66 @@ types!( PeerEventFilter, PeerEventSet, PeerId, + PeerPredicateBox, Permission, + PermissionPredicateBox, PipelineEventBox, PipelineEventFilterBox, - PredicateBox, PublicKey, - QueryBox, + PublicKeyPredicateBox, QueryExecutionFail, - QueryOutputBox, - QueryOutputPredicate, + QueryRequest, + QueryRequestWithAuthority, + QueryResponse, QuerySignature, Register, Register, Register, + RegisterBox, Register, Register, Register, Register, - RegisterBox, RemoveKeyValue, RemoveKeyValue, RemoveKeyValue, + RemoveKeyValueBox, RemoveKeyValue, RemoveKeyValue, - RemoveKeyValueBox, Repeats, RepetitionError, + RevokeBox, Revoke, Revoke, Revoke, - RevokeBox, Role, RoleEvent, RoleEventFilter, RoleEventSet, RoleId, + RoleIdPredicateBox, RolePermissionChanged, - SemiInterval, - SemiInterval, - SemiRange, + RolePredicateBox, SetKeyValue, SetKeyValue, SetKeyValue, + SetKeyValueBox, SetKeyValue, SetKeyValue, - SetKeyValueBox, SetParameter, Signature, SignatureOf, - SignatureOf, + SignatureOf, SignatureOf, SignedBlock, + SignedBlockPredicateBox, SignedBlockV1, SignedQuery, SignedQueryV1, SignedTransaction, SignedTransactionV1, + SingularQueryBox, + SingularQueryOutputBox, SmartContractParameter, SmartContractParameters, SocketAddr, @@ -372,7 +405,7 @@ types!( SocketAddrV6, Sorting, String, - StringPredicate, + StringPredicateBox, SumeragiParameter, SumeragiParameters, TimeEvent, @@ -386,6 +419,7 @@ types!( TransactionParameters, TransactionPayload, TransactionQueryOutput, + TransactionQueryOutputPredicateBox, TransactionRejectionReason, TransactionSignature, TransactionStatus, @@ -403,34 +437,58 @@ types!( TriggerEventFilter, TriggerEventSet, TriggerId, - TriggerNumberOfExecutionsChanged, + TriggerIdPredicateBox, TriggeringEventFilterBox, + TriggerNumberOfExecutionsChanged, TypeError, Unregister, Unregister, Unregister, + UnregisterBox, Unregister, Unregister, Unregister, Unregister, - UnregisterBox, Upgrade, ValidationFail, + Vec, + Vec, + Vec, + Vec, Vec, Vec, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec>, + Vec, Vec, Vec, Vec, + Vec, + Vec, Vec, - Vec, - Vec, + Vec, + Vec, + Vec, + Vec, + Vec, + Vec, Vec, WasmExecutionFail, WasmSmartContract, + [u16; 8], [u8; 32], [u8; 4], - u128, u16, u32, u64, @@ -476,19 +534,17 @@ pub mod complete_data_model { prelude::*, query::{ error::{FindError, QueryExecutionFail}, - predicate::{ - numerical::{SemiInterval, SemiRange}, - string::StringPredicate, - value::{AtIndex, Container, QueryOutputPredicate}, - GenericPredicateBox, NonTrivial, PredicateBox, - }, - ForwardCursor, Pagination, QueryOutputBox, Sorting, + parameters::{ForwardCursor, IterableQueryParams}, + predicate::CompoundPredicate, + IterableQueryOutput, IterableQueryOutputBatchBox, IterableQueryWithFilter, + IterableQueryWithParams, QueryRequestWithAuthority, QueryResponse, QuerySignature, + SignedQuery, SignedQueryV1, SingularQueryOutputBox, }, transaction::{ error::TransactionLimitError, SignedTransactionV1, TransactionPayload, TransactionSignature, }, - BatchedResponse, BatchedResponseV1, Level, + Level, }; pub use iroha_genesis::ExecutorPath; pub use iroha_primitives::{ diff --git a/smart_contract/executor/derive/src/default.rs b/smart_contract/executor/derive/src/default.rs index 5fc5206b6dc..9eef6269f82 100644 --- a/smart_contract/executor/derive/src/default.rs +++ b/smart_contract/executor/derive/src/default.rs @@ -82,7 +82,7 @@ pub fn impl_derive_entrypoints(emitter: &mut Emitter, input: &syn::DeriveInput) #[::iroha_executor::prelude::entrypoint] pub fn validate_query( authority: ::iroha_executor::prelude::AccountId, - query: ::iroha_executor::data_model::query::QueryBox2, + query: ::iroha_executor::data_model::query::QueryBox, block_height: u64, #(#custom_args),* ) -> ::iroha_executor::prelude::Result { diff --git a/smart_contract/executor/src/lib.rs b/smart_contract/executor/src/lib.rs index 3647d269c50..302e1bdf0d1 100644 --- a/smart_contract/executor/src/lib.rs +++ b/smart_contract/executor/src/lib.rs @@ -8,7 +8,7 @@ use alloc::collections::BTreeSet; use data_model::{executor::Result, ValidationFail}; #[cfg(not(test))] -use data_model::{prelude::*, query::QueryBox2, smart_contract::payloads}; +use data_model::{prelude::*, query::QueryBox, smart_contract::payloads}; use iroha_executor_data_model::{parameter::Parameter, permission::Permission}; use iroha_schema::{Ident, MetaMap}; pub use iroha_smart_contract as smart_contract; @@ -61,7 +61,7 @@ pub fn get_validate_instruction_payload() -> payloads::Validate /// Host side will generate a trap if this function was called not from a /// executor `validate_query()` entrypoint. #[cfg(not(test))] -pub fn get_validate_query_payload() -> payloads::Validate { +pub fn get_validate_query_payload() -> payloads::Validate { // Safety: ownership of the returned result is transferred into `_decode_from_raw` unsafe { decode_with_length_prefix_from_raw(host::get_validate_query_payload()) } } diff --git a/smart_contract/src/lib.rs b/smart_contract/src/lib.rs index 9b7c23e1afe..fa2d5773256 100644 --- a/smart_contract/src/lib.rs +++ b/smart_contract/src/lib.rs @@ -1,11 +1,11 @@ //! API which simplifies writing of smartcontracts #![no_std] #![allow(unsafe_code)] -#![warn(unused, missing_docs)] // TODO +#![warn(missing_docs)] // TODO extern crate alloc; -use alloc::{boxed::Box, vec::Vec}; +use alloc::boxed::Box; use core::fmt::Debug; #[cfg(not(test))] @@ -13,17 +13,13 @@ use data_model::smart_contract::payloads; use data_model::{ isi::BuiltInInstruction, prelude::*, - query::{ - cursor::ForwardCursor, predicate::PredicateBox, sorting::Sorting, IterableQuery, - Pagination, Query, QueryOutputBox, - }, - BatchedResponse, + query::{parameters::ForwardCursor, IterableQuery}, }; pub use iroha_data_model as data_model; use iroha_data_model::query::{ builder::{IterableQueryBuilder, QueryExecutor, SingleQueryError}, predicate::HasPredicateBox, - IterableQueryOutputBatchBox, IterableQueryWithParams, QueryRequest2, QueryResponse2, + IterableQueryOutputBatchBox, IterableQueryWithParams, QueryRequest, QueryResponse, SingularQuery, SingularQueryBox, SingularQueryOutputBox, }; use iroha_macro::FromVariant; @@ -33,7 +29,7 @@ use iroha_smart_contract_utils::{ debug::{dbg_panic, DebugExpectExt as _}, decode_with_length_prefix_from_raw, encode_and_execute, }; -use parity_scale_codec::{Decode, DecodeAll, Encode}; +use parity_scale_codec::{Decode, Encode}; #[no_mangle] extern "C" fn _iroha_smart_contract_alloc(len: usize) -> *const u8 { @@ -121,7 +117,7 @@ pub struct SmartContractQueryCursor { cursor: ForwardCursor, } -fn execute_query(query: QueryRequest2) -> Result { +fn execute_query(query: QueryRequest) -> Result { #[cfg(not(test))] use host::execute_query as host_execute_query; #[cfg(test)] @@ -152,8 +148,7 @@ impl QueryExecutor for SmartContractQueryExecutor { &self, query: SingularQueryBox, ) -> Result { - let QueryResponse2::Singular(output) = execute_query(QueryRequest2::Singular(query))? - else { + let QueryResponse::Singular(output) = execute_query(QueryRequest::Singular(query))? else { dbg_panic("BUG: iroha returned unexpected type in singular query"); }; @@ -164,7 +159,7 @@ impl QueryExecutor for SmartContractQueryExecutor { &self, query: IterableQueryWithParams, ) -> Result<(IterableQueryOutputBatchBox, Option), Self::Error> { - let QueryResponse2::Iterable(output) = execute_query(QueryRequest2::StartIterable(query))? + let QueryResponse::Iterable(output) = execute_query(QueryRequest::StartIterable(query))? else { dbg_panic("BUG: iroha returned unexpected type in iterable query"); }; @@ -180,8 +175,8 @@ impl QueryExecutor for SmartContractQueryExecutor { fn continue_iterable_query( cursor: Self::Cursor, ) -> Result<(IterableQueryOutputBatchBox, Option), Self::Error> { - let QueryResponse2::Iterable(output) = - execute_query(QueryRequest2::ContinueIterable(cursor.cursor))? + let QueryResponse::Iterable(output) = + execute_query(QueryRequest::ContinueIterable(cursor.cursor))? else { dbg_panic("BUG: iroha returned unexpected type in iterable query"); }; @@ -275,15 +270,16 @@ pub mod prelude { mod tests { use core::{mem::ManuallyDrop, slice}; - use data_model::{prelude::numeric, query::asset::FindAssetQuantityById, BatchedResponseV1}; + // use data_model::{prelude::numeric, query::asset::FindAssetQuantityById}; use iroha_smart_contract_utils::encode_with_length_prefix; - use parity_scale_codec::Decode; + use parity_scale_codec::DecodeAll; use webassembly_test::webassembly_test; use super::*; getrandom::register_custom_getrandom!(super::stub_getrandom); + const QUERY_RESULT: Result = Ok(numeric!(1234)); const ISI_RESULT: Result<(), ValidationFail> = Ok(()); fn get_test_instruction() -> InstructionBox { @@ -291,11 +287,10 @@ mod tests { Register::asset(Asset::new(new_asset_id, 1_u32)).into() } - // TODO - // fn get_test_query() -> QueryBox { - // let asset_id: AssetId = "rose##ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland".parse().unwrap(); - // FindAssetQuantityById::new(asset_id).into() - // } + fn get_test_query() -> FindAssetQuantityById { + let asset_id: AssetId = "rose##ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland".parse().unwrap(); + FindAssetQuantityById::new(asset_id) + } #[no_mangle] pub unsafe extern "C" fn _iroha_smart_contract_execute_instruction_mock( @@ -309,25 +304,22 @@ mod tests { ManuallyDrop::new(encode_with_length_prefix(&ISI_RESULT)).as_ptr() } - #[warn(unused)] // TODO #[no_mangle] pub unsafe extern "C" fn _iroha_smart_contract_execute_query_mock( ptr: *const u8, len: usize, ) -> *const u8 { - todo!() - // let bytes = slice::from_raw_parts(ptr, len); - // let query_request = SmartContractQueryRequest::decode_all(&mut &*bytes).unwrap(); - // let query = query_request.unwrap_query().0; - // assert_eq!(query, get_test_query()); - // - // let response: Result, ValidationFail> = - // Ok(BatchedResponseV1::new( - // QUERY_RESULT.unwrap().collect().unwrap(), - // todo!(), // ForwardCursor::new(None, None), - // ) - // .into()); - // ManuallyDrop::new(encode_with_length_prefix(&response)).as_ptr() + let bytes = slice::from_raw_parts(ptr, len); + let query_request = QueryRequest::decode_all(&mut &*bytes).unwrap(); + let QueryRequest::Singular(query) = query_request else { + panic!("Expected a singular query") + }; + let query: FindAssetQuantityById = query.try_into().expect("Unexpected query type"); + assert_eq!(query, get_test_query()); + + let response: Result = + Ok(QueryResponse::Singular(QUERY_RESULT.unwrap().into())); + ManuallyDrop::new(encode_with_length_prefix(&response)).as_ptr() } #[webassembly_test] @@ -335,8 +327,8 @@ mod tests { get_test_instruction().execute().unwrap(); } - // #[webassembly_test] - // fn execute_query() { - // assert_eq!(get_test_query().execute(), QUERY_RESULT); - // } + #[webassembly_test] + fn execute_query() { + assert_eq!(query(get_test_query()), QUERY_RESULT); + } } diff --git a/torii/src/routing.rs b/torii/src/routing.rs index 71023466bfa..229a22cf2d7 100644 --- a/torii/src/routing.rs +++ b/torii/src/routing.rs @@ -14,7 +14,7 @@ use iroha_data_model::{ SignedBlock, }, prelude::*, - query::{QueryRequestWithAuthority, QueryResponse2, SignedQuery2}, + query::{QueryRequestWithAuthority, QueryResponse, SignedQuery}, }; #[cfg(feature = "telemetry")] use iroha_telemetry::metrics::Status; @@ -52,12 +52,12 @@ pub async fn handle_transaction( pub async fn handle_queries( live_query_store: LiveQueryStoreHandle, state: Arc, - query: SignedQuery2, -) -> Result> { + query: SignedQuery, +) -> Result> { let handle = task::spawn_blocking(move || { let state_view = state.view(); - let SignedQuery2::V1(query) = query; + let SignedQuery::V1(query) = query; let query: QueryRequestWithAuthority = query.payload; let valid_query = ValidQueryRequest::validate_for_client(query, &state_view)?;