From 914c44095ef546f70021436f2aaa2a1f2dfba7ff Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Fri, 27 Oct 2023 15:31:36 +0300 Subject: [PATCH] [refactor] #3982: Clear live queries after smart contract end Signed-off-by: Daniil Polyakov --- client/src/client.rs | 46 +- client/tests/integration/queries/mod.rs | 49 +- .../integration/smartcontracts/Cargo.toml | 2 + .../executor_with_custom_token/src/lib.rs | 1 + .../query_assets_and_save_cursor/Cargo.toml | 18 + .../query_assets_and_save_cursor/src/lib.rs | 38 + .../integration/triggers/by_call_trigger.rs | 1 + .../integration/triggers/time_trigger.rs | 1 + client/tests/integration/upgrade.rs | 1 + configs/peer/executor.wasm | Bin 498053 -> 498073 bytes core/src/query/store.rs | 28 +- core/src/smartcontracts/wasm.rs | 707 +++++++++--------- data_model/src/query/cursor.rs | 9 +- data_model/src/query/mod.rs | 5 +- ffi/derive/src/convert.rs | 8 +- smart_contract/executor/src/default.rs | 2 +- smart_contract/executor/src/lib.rs | 27 +- smart_contract/executor/src/permission.rs | 9 +- smart_contract/src/lib.rs | 38 +- wasm_codec/derive/src/lib.rs | 4 +- 20 files changed, 575 insertions(+), 419 deletions(-) create mode 100644 client/tests/integration/smartcontracts/query_assets_and_save_cursor/Cargo.toml create mode 100644 client/tests/integration/smartcontracts/query_assets_and_save_cursor/src/lib.rs diff --git a/client/src/client.rs b/client/src/client.rs index dd907973c2c..0978ff49d58 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -346,20 +346,20 @@ impl_query_output! { #[display(fmt = "{}@{torii_url}", "key_pair.public_key()")] pub struct Client { /// Url for accessing iroha node - torii_url: Url, + pub torii_url: Url, /// Accounts keypair - key_pair: KeyPair, + pub key_pair: KeyPair, /// Transaction time to live in milliseconds - transaction_ttl: Option, + pub transaction_ttl: Option, /// Transaction status timeout - transaction_status_timeout: Duration, + pub transaction_status_timeout: Duration, /// Current account - account_id: AccountId, + pub account_id: AccountId, /// Http headers which will be appended to each request - headers: HashMap, + pub headers: HashMap, /// If `true` add nonce, which makes different hashes for /// transactions which occur repeatedly and/or simultaneously - add_transaction_nonce: bool, + pub add_transaction_nonce: bool, } /// Query request @@ -388,6 +388,7 @@ impl QueryRequest { ), } } + fn assemble(self) -> DefaultRequestBuilder { let builder = DefaultRequestBuilder::new( HttpMethod::POST, @@ -837,7 +838,7 @@ impl Client { /// /// # Errors /// Fails if sending request fails - pub fn request_with_filter_and_pagination_and_sorting( + pub(crate) fn request_with_filter_and_pagination_and_sorting( &self, request: R, pagination: Pagination, @@ -873,6 +874,35 @@ impl Client { self.build_query(request).execute() } + /// Query API entry point using cursor. + /// + /// You should probably not use this function directly. + /// + /// # Errors + /// Fails if sending request fails + #[cfg(debug_assertions)] + pub fn request_with_cursor( + &self, + cursor: iroha_data_model::query::cursor::ForwardCursor, + ) -> QueryResult + where + O: QueryOutput, + >::Error: Into, + { + let request = QueryRequest { + torii_url: self.torii_url.clone(), + headers: self.headers.clone(), + request: iroha_data_model::query::QueryRequest::Cursor(cursor), + }; + let response = request.clone().assemble().build()?.send()?; + + let mut resp_handler = QueryResponseHandler::::new(request); + let value = resp_handler.handle(&response)?; + let output = O::new(value, resp_handler); + + Ok(output) + } + /// Query API entry point. /// Creates a [`QueryRequestBuilder`] which can be used to configure requests queries from `Iroha` peers. /// diff --git a/client/tests/integration/queries/mod.rs b/client/tests/integration/queries/mod.rs index 19306ab3f31..01c266191c7 100644 --- a/client/tests/integration/queries/mod.rs +++ b/client/tests/integration/queries/mod.rs @@ -1,7 +1,10 @@ +use std::str::FromStr as _; + +use eyre::{bail, Result}; use iroha_client::client::{self, ClientQueryError}; use iroha_data_model::{ - query::{error::QueryExecutionFail, FetchSize, MAX_FETCH_SIZE}, - ValidationFail, + prelude::*, + query::{cursor::ForwardCursor, error::QueryExecutionFail, MAX_FETCH_SIZE}, }; use test_network::*; @@ -27,3 +30,45 @@ fn too_big_fetch_size_is_not_allowed() { )) )); } + +#[test] +fn live_query_is_dropped_after_smart_contract_end() -> Result<()> { + let (_rt, _peer, client) = ::new().with_port(11_140).start_with_runtime(); + wait_for_genesis_committed(&[client.clone()], 0); + + let wasm = iroha_wasm_builder::Builder::new( + "tests/integration/smartcontracts/query_assets_and_save_cursor", + ) + .show_output() + .build()? + .optimize()? + .into_bytes()?; + + let transaction = client.build_transaction( + WasmSmartContract::from_compiled(wasm), + UnlimitedMetadata::default(), + )?; + client.submit_transaction_blocking(&transaction)?; + + let metadata_value = client.request(FindAccountKeyValueByIdAndKey::new( + client.account_id.clone(), + Name::from_str("cursor").unwrap(), + ))?; + let Value::String(cursor) = metadata_value.0 else { + bail!("Expected `Value::String`, got {:?}", metadata_value.0); + }; + let asset_cursor = serde_json::from_str::(&cursor)?; + + let err = client + .request_with_cursor::>(asset_cursor) + .expect_err("Request with cursor from smart contract should fail"); + + assert!(matches!( + err, + ClientQueryError::Validation(ValidationFail::QueryFailed( + QueryExecutionFail::UnknownCursor + )) + )); + + Ok(()) +} diff --git a/client/tests/integration/smartcontracts/Cargo.toml b/client/tests/integration/smartcontracts/Cargo.toml index e81dee9b259..1ab1801377d 100644 --- a/client/tests/integration/smartcontracts/Cargo.toml +++ b/client/tests/integration/smartcontracts/Cargo.toml @@ -14,6 +14,7 @@ members = [ "executor_with_admin", "executor_with_custom_token", "executor_with_migration_fail", + "query_assets_and_save_cursor", ] [profile.dev] @@ -27,6 +28,7 @@ opt-level = "z" # Optimize for size vs speed with "s"/"z"(removes vectorizat codegen-units = 1 # Further reduces binary size but increases compilation time [workspace.dependencies] +iroha_smart_contract = { version = "=2.0.0-pre-rc.20", path = "../../../../smart_contract", features = ["debug"]} iroha_trigger = { version = "=2.0.0-pre-rc.20", path = "../../../../smart_contract/trigger", features = ["debug"]} iroha_executor = { version = "=2.0.0-pre-rc.20", path = "../../../../smart_contract/executor" } iroha_schema = { version = "=2.0.0-pre-rc.20", path = "../../../../schema" } diff --git a/client/tests/integration/smartcontracts/executor_with_custom_token/src/lib.rs b/client/tests/integration/smartcontracts/executor_with_custom_token/src/lib.rs index d757913c836..f75d0e43fed 100644 --- a/client/tests/integration/smartcontracts/executor_with_custom_token/src/lib.rs +++ b/client/tests/integration/smartcontracts/executor_with_custom_token/src/lib.rs @@ -170,6 +170,7 @@ impl Executor { } } +// TODO (#4049): Fix unused `visit_register_domain()` fn visit_register_domain(executor: &mut Executor, authority: &AccountId, _isi: Register) { if executor.block_height() == 0 { pass!(executor) diff --git a/client/tests/integration/smartcontracts/query_assets_and_save_cursor/Cargo.toml b/client/tests/integration/smartcontracts/query_assets_and_save_cursor/Cargo.toml new file mode 100644 index 00000000000..bc012a36958 --- /dev/null +++ b/client/tests/integration/smartcontracts/query_assets_and_save_cursor/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "query_assets_and_save_cursor" + +edition.workspace = true +version.workspace = true +authors.workspace = true + +license.workspace = true + +[lib] +crate-type = ['cdylib'] + +[dependencies] +iroha_smart_contract.workspace = true + +panic-halt.workspace = true +lol_alloc.workspace = true +serde_json = { version = "1.0.108", default-features = false } 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 new file mode 100644 index 00000000000..c86e452e693 --- /dev/null +++ b/client/tests/integration/smartcontracts/query_assets_and_save_cursor/src/lib.rs @@ -0,0 +1,38 @@ +//! Smart contract which executes [`FindAllAssets`] and saves cursor to the owner's metadata. + +#![no_std] + +#[cfg(not(test))] +extern crate panic_halt; + +extern crate alloc; + +use alloc::string::ToString as _; +use core::num::NonZeroU32; + +use iroha_smart_contract::{parse, prelude::*}; +use lol_alloc::{FreeListAllocator, LockedAllocator}; + +#[global_allocator] +static ALLOC: LockedAllocator = LockedAllocator::new(FreeListAllocator::new()); + +/// Execute [`FindAllAssets`] and save cursor to the owner's metadata. +#[iroha_smart_contract::main] +fn main(owner: AccountId) { + let asset_cursor = FindAllAssets + .fetch_size(FetchSize::new(Some(NonZeroU32::try_from(1).dbg_unwrap()))) + .execute() + .dbg_unwrap(); + + let (_batch, cursor) = asset_cursor.into_raw_parts(); + + SetKeyValueExpr::new( + owner, + parse!("cursor" as Name), + serde_json::to_value(cursor) + .dbg_expect("Failed to convert cursor to JSON") + .to_string(), + ) + .execute() + .dbg_expect("Failed to save cursor to the owner's metadata"); +} diff --git a/client/tests/integration/triggers/by_call_trigger.rs b/client/tests/integration/triggers/by_call_trigger.rs index 4770de13945..d7602a54cdb 100644 --- a/client/tests/integration/triggers/by_call_trigger.rs +++ b/client/tests/integration/triggers/by_call_trigger.rs @@ -329,6 +329,7 @@ fn trigger_in_genesis_using_base64() -> Result<()> { info!("Building trigger"); let wasm = iroha_wasm_builder::Builder::new("tests/integration/smartcontracts/mint_rose_trigger") + .show_output() .build()? .optimize()? .into_bytes()?; diff --git a/client/tests/integration/triggers/time_trigger.rs b/client/tests/integration/triggers/time_trigger.rs index 020c95eca26..959256b7f47 100644 --- a/client/tests/integration/triggers/time_trigger.rs +++ b/client/tests/integration/triggers/time_trigger.rs @@ -204,6 +204,7 @@ fn mint_nft_for_every_user_every_1_sec() -> Result<()> { let wasm = iroha_wasm_builder::Builder::new( "tests/integration/smartcontracts/create_nft_for_every_user_trigger", ) + .show_output() .build()? .optimize()? .into_bytes()?; diff --git a/client/tests/integration/upgrade.rs b/client/tests/integration/upgrade.rs index 05c82710561..63490b138e9 100644 --- a/client/tests/integration/upgrade.rs +++ b/client/tests/integration/upgrade.rs @@ -143,6 +143,7 @@ fn upgrade_executor(client: &Client, executor: impl AsRef) -> Result<()> { info!("Building executor"); let wasm = iroha_wasm_builder::Builder::new(executor.as_ref()) + .show_output() .build()? .optimize()? .into_bytes()?; diff --git a/configs/peer/executor.wasm b/configs/peer/executor.wasm index d124730bbd19009b34ea0464fe06ec53031ac541..bc3c581289e5f2b17ee4f8ebdb51094f532157b0 100644 GIT binary patch delta 60752 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 diff --git a/core/src/query/store.rs b/core/src/query/store.rs index 17b537d909e..432f3ab142c 100644 --- a/core/src/query/store.rs +++ b/core/src/query/store.rs @@ -12,7 +12,7 @@ use iroha_data_model::{ asset::AssetValue, query::{ cursor::ForwardCursor, error::QueryExecutionFail, pagination::Pagination, sorting::Sorting, - FetchSize, DEFAULT_FETCH_SIZE, MAX_FETCH_SIZE, + FetchSize, QueryId, DEFAULT_FETCH_SIZE, MAX_FETCH_SIZE, }, BatchedResponse, BatchedResponseV1, HasMetadata, IdentifiableBox, ValidationFail, Value, }; @@ -67,7 +67,7 @@ type LiveQuery = Batched>; /// Clients can handle their queries using [`LiveQueryStoreHandle`] #[derive(Debug)] pub struct LiveQueryStore { - queries: HashMap, + queries: HashMap, query_idle_time: Duration, } @@ -138,7 +138,7 @@ impl LiveQueryStore { LiveQueryStoreHandle { message_sender } } - fn insert(&mut self, query_id: String, live_query: LiveQuery) { + fn insert(&mut self, query_id: QueryId, live_query: LiveQuery) { self.queries.insert(query_id, (live_query, Instant::now())); } @@ -148,8 +148,8 @@ impl LiveQueryStore { } enum Message { - Insert(String, Batched>), - Remove(String, oneshot::Sender>>>), + Insert(QueryId, Batched>), + Remove(QueryId, oneshot::Sender>>>), } /// Handle to interact with [`LiveQueryStore`]. @@ -207,13 +207,25 @@ impl LiveQueryStoreHandle { self.construct_query_response(query_id, cursor.cursor.map(NonZeroU64::get), live_query) } - fn insert(&self, query_id: String, live_query: LiveQuery) -> Result<()> { + /// Remove query from the storage if there is any. + /// + /// Returns `true` if query was removed, `false` otherwise. + /// + /// # Errors + /// + /// - Returns [`Error::ConnectionClosed`] if [`QueryService`] is dropped, + /// - Otherwise throws up query output handling errors. + pub fn drop_query(&self, query_id: QueryId) -> Result { + self.remove(query_id).map(|query_opt| query_opt.is_some()) + } + + fn insert(&self, query_id: QueryId, live_query: LiveQuery) -> Result<()> { self.message_sender .blocking_send(Message::Insert(query_id, live_query)) .map_err(|_| Error::ConnectionClosed) } - fn remove(&self, query_id: String) -> Result> { + fn remove(&self, query_id: QueryId) -> Result> { let (sender, receiver) = oneshot::channel(); self.message_sender @@ -225,7 +237,7 @@ impl LiveQueryStoreHandle { fn construct_query_response( &self, - query_id: String, + query_id: QueryId, curr_cursor: Option, mut live_query: Batched>, ) -> Result> { diff --git a/core/src/smartcontracts/wasm.rs b/core/src/smartcontracts/wasm.rs index 3b5cd8d48f1..4b901950423 100644 --- a/core/src/smartcontracts/wasm.rs +++ b/core/src/smartcontracts/wasm.rs @@ -3,7 +3,7 @@ //! to wasm format and submitted in a transaction use error::*; -use import_traits::{ +use import::traits::{ ExecuteOperations as _, GetExecutorPayloads as _, SetPermissionTokenSchema as _, }; use iroha_config::{ @@ -16,7 +16,7 @@ use iroha_data_model::{ isi::InstructionExpr, permission::PermissionTokenSchema, prelude::*, - query::{QueryBox, QueryRequest, QueryWithParameters}, + query::{QueryBox, QueryId, QueryRequest, QueryWithParameters}, smart_contract::{ payloads::{self, Validate}, SmartContractQueryRequest, @@ -27,13 +27,16 @@ 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 state::{Wsv as _, WsvMut as _}; use wasmtime::{ Caller, Config, Engine, Linker, Module, Store, StoreLimits, StoreLimitsBuilder, TypedFunc, }; -use self::state::Authority; -use crate::{smartcontracts::Execute, wsv::WorldStateView, ValidQuery as _}; +use crate::{ + query::store::LiveQueryStoreHandle, + smartcontracts::{wasm::state::ValidateQueryOperation, Execute}, + wsv::WorldStateView, + ValidQuery as _, +}; /// Name of the exported memory const WASM_MEMORY: &str = "memory"; @@ -65,48 +68,48 @@ mod import { pub const EXECUTOR_VALIDATE_INSTRUCTION: &str = "_iroha_executor_validate_instruction"; pub const EXECUTOR_VALIDATE_QUERY: &str = "_iroha_executor_validate_query"; pub const EXECUTOR_MIGRATE: &str = "_iroha_executor_migrate"; -} -mod import_traits { - //! Traits which some [Runtime]s should implement to import functions from Iroha to WASM + pub mod traits { + //! Traits which some [Runtime]s should implement to import functions from Iroha to WASM - use iroha_data_model::{query::QueryBox, smart_contract::payloads::Validate}; + use iroha_data_model::{query::QueryBox, smart_contract::payloads::Validate}; - use super::*; + use super::super::*; - pub trait ExecuteOperations { - /// Execute `query` on host - #[codec::wrap_trait_fn] - fn execute_query( - query_request: SmartContractQueryRequest, - state: &S, - ) -> Result, ValidationFail>; - - /// Execute `instruction` on host - #[codec::wrap_trait_fn] - fn execute_instruction( - instruction: InstructionExpr, - state: &mut S, - ) -> Result<(), ValidationFail>; - } + pub trait ExecuteOperations { + /// Execute `query` on host + #[codec::wrap_trait_fn] + fn execute_query( + query_request: SmartContractQueryRequest, + state: &mut S, + ) -> Result, ValidationFail>; - pub trait GetExecutorPayloads { - #[codec::wrap_trait_fn] - fn get_migrate_payload(state: &S) -> payloads::Migrate; + /// Execute `instruction` on host + #[codec::wrap_trait_fn] + fn execute_instruction( + instruction: InstructionExpr, + state: &mut S, + ) -> Result<(), ValidationFail>; + } - #[codec::wrap_trait_fn] - fn get_validate_transaction_payload(state: &S) -> Validate; + pub trait GetExecutorPayloads { + #[codec::wrap_trait_fn] + fn get_migrate_payload(state: &S) -> payloads::Migrate; - #[codec::wrap_trait_fn] - fn get_validate_instruction_payload(state: &S) -> Validate; + #[codec::wrap_trait_fn] + fn get_validate_transaction_payload(state: &S) -> Validate; - #[codec::wrap_trait_fn] - fn get_validate_query_payload(state: &S) -> Validate; - } + #[codec::wrap_trait_fn] + fn get_validate_instruction_payload(state: &S) -> Validate; - pub trait SetPermissionTokenSchema { - #[codec::wrap_trait_fn] - fn set_permission_token_schema(schema: PermissionTokenSchema, state: &mut S); + #[codec::wrap_trait_fn] + fn get_validate_query_payload(state: &S) -> Validate; + } + + pub trait SetPermissionTokenSchema { + #[codec::wrap_trait_fn] + fn set_permission_token_schema(schema: PermissionTokenSchema, state: &mut S); + } } } @@ -117,9 +120,15 @@ pub mod error { /// `WebAssembly` execution error type #[derive(Debug, thiserror::Error, displaydoc::Display)] + #[ignore_extra_doc_attributes] pub enum Error { /// Runtime initialization failure Initialization(#[source] WasmtimeError), + /// Runtime finalization failure. + /// + /// Currently only [`crate::query::store::Error`] might fail in this case. + /// [`From`] is not implemented to force users to explicitly wrap this error. + Finalization(#[source] crate::query::store::Error), /// Failed to load module ModuleLoading(#[source] WasmtimeError), /// Module could not be instantiated @@ -268,6 +277,19 @@ fn create_config() -> Result { Ok(config) } +/// Remove all executed queries from the query storage. +fn forget_all_executed_queries( + query_handle: &LiveQueryStoreHandle, + executed_queries: impl IntoIterator, +) -> Result<()> { + for query_id in executed_queries { + let _ = query_handle + .drop_query(query_id) + .map_err(Error::Finalization)?; + } + Ok(()) +} + /// Limits checker for smartcontracts. #[derive(Copy, Clone)] struct LimitsExecutor { @@ -306,6 +328,10 @@ impl LimitsExecutor { pub mod state { //! All supported states for [`Runtime`](super::Runtime) + use std::collections::HashSet; + + use derive_more::Constructor; + use super::*; /// Construct [`StoreLimits`] from [`Configuration`] @@ -327,258 +353,210 @@ pub mod state { .build() } - /// Common data for states - pub struct Common<'wrld> { + /// State for most common operations. + /// Generic over borrowed [`WorldStateView`] type and specific executable state. + pub struct CommonState { pub(super) authority: AccountId, pub(super) store_limits: StoreLimits, - pub(super) wsv: &'wrld mut WorldStateView, /// Span inside of which all logs are recorded for this smart contract pub(super) log_span: Span, + pub(super) executed_queries: HashSet, + /// Borrowed [`WorldStateView`] kind + pub(super) wsv: W, + /// Concrete state for specific executable + pub(super) specific_state: S, } - impl<'wrld> Common<'wrld> { - /// Create new [`Common`] + impl CommonState { + /// Create new [`OrdinaryState`] pub fn new( - wsv: &'wrld mut WorldStateView, authority: AccountId, config: Configuration, log_span: Span, + wsv: W, + specific_state: S, ) -> Self { Self { - wsv, authority, store_limits: store_limits_from_config(&config), log_span, + executed_queries: HashSet::new(), + wsv, + specific_state, } } - } - - /// Trait to get span for logs. - /// - /// Used to implement [`log()`](Runtime::log) export. - pub trait LogSpan { - /// Get log span - fn log_span(&self) -> &Span; - } - - /// Trait to get mutable reference to limits - /// - /// Used to implement [`Runtime::create_store()`]. - pub trait LimitsMut { - /// Get mutable reference to store limits - fn limits_mut(&mut self) -> &mut StoreLimits; - } - - /// Trait to get authority account id - pub trait Authority { - /// Get authority account id - fn authority(&self) -> &AccountId; - } - - /// Trait to get an immutable reference to [`WorldStateView`] - pub trait Wsv { - /// Get immutable [`WorldStateView`] - fn wsv(&self) -> &WorldStateView; - } - - /// Trait to get mutable reference to [`WorldStateView`] - pub trait WsvMut { - /// Get mutable [`WorldStateView`] - fn wsv_mut(&mut self) -> &mut WorldStateView; - } - - /// Smart Contract execution state - pub struct SmartContract<'wrld> { - pub(super) common: Common<'wrld>, - /// Should be set for smart contract validation only. - pub(super) limits_executor: Option, - } - - impl LogSpan for SmartContract<'_> { - fn log_span(&self) -> &Span { - &self.common.log_span - } - } - - impl LimitsMut for SmartContract<'_> { - fn limits_mut(&mut self) -> &mut StoreLimits { - &mut self.common.store_limits - } - } - impl Authority for SmartContract<'_> { - fn authority(&self) -> &AccountId { - &self.common.authority + /// Take executed queries leaving an empty set + pub fn take_executed_queries(&mut self) -> HashSet { + std::mem::take(&mut self.executed_queries) } } - impl Wsv for SmartContract<'_> { - fn wsv(&self) -> &WorldStateView { - self.common.wsv - } + /// Trait to validate queries and instructions before execution. + pub trait ValidateQueryOperation { + /// Validate `query`. + /// + /// # Errors + /// + /// Returns error if query validation failed. + fn validate_query( + &self, + authority: &AccountId, + query: QueryBox, + ) -> Result<(), ValidationFail>; } - impl WsvMut for SmartContract<'_> { - fn wsv_mut(&mut self) -> &mut WorldStateView { - self.common.wsv - } - } + pub mod wsv { + //! Strongly typed kinds of borrowed [`WorldStateView`] - /// Trigger execution state - pub struct Trigger<'wrld> { - pub(super) common: Common<'wrld>, - /// Event which activated this trigger - pub(super) triggering_event: Event, - } + use super::*; - impl LogSpan for Trigger<'_> { - fn log_span(&self) -> &Span { - &self.common.log_span - } - } + /// Const reference to [`WorldStateView`]. + pub struct WithConst<'wrld>(pub(in super::super) &'wrld WorldStateView); - impl LimitsMut for Trigger<'_> { - fn limits_mut(&mut self) -> &mut StoreLimits { - &mut self.common.store_limits - } - } + /// Mutable reference to [`WorldStateView`]. + pub struct WithMut<'wrld>(pub(in super::super) &'wrld mut WorldStateView); - impl Authority for Trigger<'_> { - fn authority(&self) -> &AccountId { - &self.common.authority + /// Trait to get immutable [`WorldStateView`] + /// + /// Exists to write generic code for [`WithWsv`] and [`WithMutWsv`. + pub trait Wsv { + /// Get immutable [`WorldStateView`] + fn wsv(&self) -> &WorldStateView; } - } - impl Wsv for Trigger<'_> { - fn wsv(&self) -> &WorldStateView { - self.common.wsv + impl Wsv for WithConst<'_> { + fn wsv(&self) -> &WorldStateView { + self.0 + } } - } - impl WsvMut for Trigger<'_> { - fn wsv_mut(&mut self) -> &mut WorldStateView { - self.common.wsv + impl Wsv for WithMut<'_> { + fn wsv(&self) -> &WorldStateView { + self.0 + } } } - pub mod executor { - //! States related to *Executor* execution. + pub mod specific { + //! States for concrete executable entrypoints. use super::*; - /// Struct to encapsulate common state for `validate_transaction()` and - /// `validate_instruction()` entrypoints. - /// - /// *Mut* means that [`WorldStateView`] will be mutated. - pub struct ValidateMut<'wrld, T> { - pub(in super::super) common: Common<'wrld>, - pub(in super::super) to_validate: T, + /// Smart Contract execution state + #[derive(Copy, Clone)] + pub struct SmartContract { + pub(in super::super) limits_executor: Option, } - impl LogSpan for ValidateMut<'_, T> { - fn log_span(&self) -> &Span { - &self.common.log_span + impl SmartContract { + /// Create new [`SmartContract`] + pub(in super::super) fn new(limits_executor: Option) -> Self { + Self { limits_executor } } } - impl LimitsMut for ValidateMut<'_, T> { - fn limits_mut(&mut self) -> &mut StoreLimits { - &mut self.common.store_limits - } + /// Trigger execution state + #[derive(Constructor)] + pub struct Trigger { + /// Event which activated this trigger + pub(in super::super) triggering_event: Event, } - impl Authority for ValidateMut<'_, T> { - fn authority(&self) -> &AccountId { - &self.common.authority - } - } + pub mod executor { + //! States related to *Executor* execution. - impl Wsv for ValidateMut<'_, T> { - fn wsv(&self) -> &WorldStateView { - self.common.wsv - } - } + use super::*; - impl WsvMut for ValidateMut<'_, T> { - fn wsv_mut(&mut self) -> &mut WorldStateView { - self.common.wsv + /// Struct to encapsulate common state kinds for `validate_*` entrypoints + #[derive(Constructor)] + pub struct Validate { + pub(in super::super::super::super) to_validate: T, } - } - /// State for executing `validate_transaction()` entrypoint of executor - pub type ValidateTransaction<'wrld> = ValidateMut<'wrld, SignedTransaction>; + /// State kind for executing `validate_transaction()` entrypoint of executor + pub type ValidateTransaction = Validate; - /// State for executing `validate_instruction()` entrypoint of executor - pub type ValidateInstruction<'wrld> = ValidateMut<'wrld, InstructionExpr>; + /// State kind for executing `validate_query()` entrypoint of executor + pub type ValidateQuery = Validate; - /// State for executing `validate_query()` entrypoint of executor - /// - /// Does not implement [`WsvMut`] because it contains immutable reference to - /// [`WorldStateView`] since it shouldn't be changed during *query* validation. - pub struct ValidateQuery<'wrld> { - pub(in super::super) authority: AccountId, - pub(in super::super) store_limits: StoreLimits, - pub(in super::super) wsv: &'wrld WorldStateView, - pub(in super::super) log_span: Span, - pub(in super::super) query: QueryBox, - } + /// State kind for executing `validate_instruction()` entrypoint of executor + pub type ValidateInstruction = Validate; - impl LogSpan for ValidateQuery<'_> { - fn log_span(&self) -> &Span { - &self.log_span - } + /// State kind for executing `migrate()` entrypoint of executor + #[derive(Copy, Clone)] + pub struct Migrate; } + } - impl LimitsMut for ValidateQuery<'_> { - fn limits_mut(&mut self) -> &mut StoreLimits { - &mut self.store_limits - } - } + /// State for smart contract execution + pub type SmartContract<'wrld> = CommonState, specific::SmartContract>; - impl Authority for ValidateQuery<'_> { - fn authority(&self) -> &AccountId { - &self.authority - } - } + /// State for trigger execution + pub type Trigger<'wrld> = CommonState, specific::Trigger>; - impl Wsv for ValidateQuery<'_> { - fn wsv(&self) -> &WorldStateView { - self.wsv - } + impl ValidateQueryOperation for SmartContract<'_> { + fn validate_query( + &self, + authority: &AccountId, + query: QueryBox, + ) -> Result<(), ValidationFail> { + let wsv: &WorldStateView = self.wsv.0; + wsv.executor().validate_query(wsv, authority, query) } + } - /// State for executing `migrate()` entrypoint of executor - pub struct Migrate<'wrld>(pub(in super::super) Common<'wrld>); - - impl LimitsMut for Migrate<'_> { - fn limits_mut(&mut self) -> &mut StoreLimits { - &mut self.0.store_limits - } + impl ValidateQueryOperation for Trigger<'_> { + fn validate_query( + &self, + authority: &AccountId, + query: QueryBox, + ) -> Result<(), ValidationFail> { + let wsv: &WorldStateView = self.wsv.0; + wsv.executor().validate_query(wsv, authority, query) } + } - impl LogSpan for Migrate<'_> { - fn log_span(&self) -> &Span { - &self.0.log_span - } - } + pub mod executor { + //! States for different executor entrypoints - impl Authority for Migrate<'_> { - fn authority(&self) -> &AccountId { - &self.0.authority - } - } + use super::*; - impl Wsv for Migrate<'_> { - fn wsv(&self) -> &WorldStateView { - self.0.wsv - } + /// State for executing `validate_transaction()` entrypoint + pub type ValidateTransaction<'wrld> = + CommonState, specific::executor::ValidateTransaction>; + + /// State for executing `validate_query()` entrypoint + pub type ValidateQuery<'wrld> = + CommonState, specific::executor::ValidateQuery>; + + /// State for executing `validate_instruction()` entrypoint + pub type ValidateInstruction<'wrld> = + CommonState, specific::executor::ValidateInstruction>; + + /// State for executing `migrate()` entrypoint + pub type Migrate<'wrld> = CommonState, specific::executor::Migrate>; + + macro_rules! impl_blank_validate_operations { + ($($t:ident),+ $(,)?) => { $( + impl ValidateQueryOperation for $t <'_> { + fn validate_query( + &self, + _authority: &AccountId, + _query: QueryBox, + ) -> Result<(), ValidationFail> { + Ok(()) + } + } + )+ }; } - impl WsvMut for Migrate<'_> { - fn wsv_mut(&mut self) -> &mut WorldStateView { - self.0.wsv - } - } + impl_blank_validate_operations!( + ValidateTransaction, + ValidateInstruction, + ValidateQuery, + Migrate, + ); } } @@ -693,17 +671,20 @@ struct LogError(u8); /// It's required by `#[codec::wrap]` to parse well type WasmtimeError = wasmtime::Error; -impl Runtime { +impl Runtime> { /// Log the given string at the given log level /// /// # Errors /// /// If log level or string decoding fails #[codec::wrap] - pub fn log((log_level, msg): (u8, String), state: &S) -> Result<(), WasmtimeError> { + pub fn log( + (log_level, msg): (u8, String), + state: &state::CommonState, + ) -> Result<(), WasmtimeError> { const TARGET: &str = "WASM"; - let _span = state.log_span().enter(); + let _span = state.log_span.enter(); match LogLevel::from_repr(log_level) .ok_or(LogError(log_level)) .map_err(wasmtime::Error::from)? @@ -726,24 +707,24 @@ impl Runtime { } Ok(()) } -} -impl Runtime { - fn create_store(&self, state: S) -> Store { + fn create_store(&self, state: state::CommonState) -> Store> { let mut store = Store::new(&self.engine, state); - store.limiter(|s| s.limits_mut()); + store.limiter(|s| &mut s.store_limits); store .add_fuel(self.config.fuel_limit) .expect("Wasm Runtime config is malformed, this is a bug"); store } +} +impl Runtime> { fn execute_executor_validate_internal( &self, module: &wasmtime::Module, - state: S, + state: state::CommonState, validate_fn_name: &'static str, ) -> Result { let mut store = self.create_store(state); @@ -761,21 +742,28 @@ impl Runtime { let dealloc_fn = Self::get_typed_func(&instance, &mut store, import::SMART_CONTRACT_DEALLOC) .expect("Checked at instantiation step"); - codec::decode_with_length_prefix_from_memory(&memory, &dealloc_fn, &mut store, offset) - .map_err(Error::Decode) + let validation_res = + codec::decode_with_length_prefix_from_memory(&memory, &dealloc_fn, &mut store, offset) + .map_err(Error::Decode)?; + + let mut state = store.into_data(); + let executed_queries = state.take_executed_queries(); + forget_all_executed_queries(state.wsv.wsv().query_handle(), executed_queries)?; + Ok(validation_res) } } -#[allow(clippy::needless_pass_by_value)] -impl Runtime { +impl Runtime> +where + W: state::wsv::Wsv, + state::CommonState: state::ValidateQueryOperation, +{ fn default_execute_query( query_request: SmartContractQueryRequest, - state: &S, + state: &mut state::CommonState, ) -> Result, ValidationFail> { iroha_logger::debug!(%query_request, "Executing"); - let wsv = state.wsv(); - match query_request.0 { QueryRequest::Query(QueryWithParameters { query, @@ -783,21 +771,40 @@ impl Runtime { pagination, fetch_size, }) => { - wsv.executor() - .validate_query(wsv, state.authority(), query.clone())?; - let output = query.execute(wsv)?; - - wsv.query_handle() - .handle_query_output(output, &sorting, pagination, fetch_size) + let batched = { + let wsv = &state.wsv.wsv(); + state.validate_query(&state.authority, query.clone())?; + let output = query.execute(wsv)?; + + wsv.query_handle() + .handle_query_output(output, &sorting, pagination, fetch_size) + }?; + match &batched { + BatchedResponse::V1(batched) => { + if let Some(query_id) = &batched.cursor.query_id { + state.executed_queries.insert(query_id.clone()); + } + } + } + Ok(batched) + } + QueryRequest::Cursor(cursor) => { + // In a normal situation we already have this `query_id` stored, + // so that's a protection from malicious smart contract + if let Some(query_id) = &cursor.query_id { + state.executed_queries.insert(query_id.clone()); + } + state.wsv.wsv().query_handle().handle_query_cursor(cursor) } - QueryRequest::Cursor(cursor) => wsv.query_handle().handle_query_cursor(cursor), } .map_err(Into::into) } +} +impl<'wrld, S> Runtime, S>> { fn default_execute_instruction( instruction: InstructionExpr, - state: &mut S, + state: &mut state::CommonState, S>, ) -> Result<(), ValidationFail> { debug!(%instruction, "Executing"); @@ -805,8 +812,8 @@ impl Runtime { // There should be two steps validation and execution. First smart contract // is validated and then it's executed. Here it's validating in both steps. // Add a flag indicating whether smart contract is being validated or executed - let authority = state.authority().clone(); - let wsv = state.wsv_mut(); + let authority = state.authority.clone(); + let wsv: &mut WorldStateView = state.wsv.0; wsv.executor() .clone() // Cloning executor is a cheap operation .validate_instruction(wsv, &authority, instruction) @@ -828,10 +835,13 @@ impl<'wrld> Runtime> { bytes: impl AsRef<[u8]>, ) -> Result<()> { let span = wasm_log_span!("Smart contract execution", %authority); - let state = state::SmartContract { - common: state::Common::new(wsv, authority, self.config, span), - limits_executor: None, - }; + let state = state::SmartContract::new( + authority, + self.config, + span, + state::wsv::WithMut(wsv), + state::specific::SmartContract::new(None), + ); self.execute_smart_contract_with_state(bytes, state) } @@ -851,10 +861,13 @@ impl<'wrld> Runtime> { max_instruction_count: u64, ) -> Result<()> { let span = wasm_log_span!("Smart contract validation", %authority); - let state = state::SmartContract { - common: state::Common::new(wsv, authority, self.config, span), - limits_executor: Some(LimitsExecutor::new(max_instruction_count)), - }; + let state = state::SmartContract::new( + authority, + self.config, + span, + state::wsv::WithMut(wsv), + state::specific::SmartContract::new(Some(LimitsExecutor::new(max_instruction_count))), + ); self.execute_smart_contract_with_state(bytes, state) } @@ -872,26 +885,28 @@ impl<'wrld> Runtime> { // NOTE: This function takes ownership of the pointer main_fn - .call(store, ()) - .map_err(ExportFnCallError::from) - .map_err(Into::into) + .call(&mut store, ()) + .map_err(ExportFnCallError::from)?; + let mut state = store.into_data(); + let executed_queries = state.take_executed_queries(); + forget_all_executed_queries(state.wsv.0.query_handle(), executed_queries) } #[codec::wrap] fn get_smart_contract_payload(state: &state::SmartContract) -> payloads::SmartContract { payloads::SmartContract { - owner: state.authority().clone(), + owner: state.authority.clone(), } } } -impl<'wrld> import_traits::ExecuteOperations> +impl<'wrld> import::traits::ExecuteOperations> for Runtime> { #[codec::wrap] fn execute_query( query_request: SmartContractQueryRequest, - state: &state::SmartContract<'wrld>, + state: &mut state::SmartContract<'wrld>, ) -> Result, ValidationFail> { Self::default_execute_query(query_request, state) } @@ -901,7 +916,7 @@ impl<'wrld> import_traits::ExecuteOperations> instruction: InstructionExpr, state: &mut state::SmartContract<'wrld>, ) -> Result<(), ValidationFail> { - if let Some(limits_executor) = state.limits_executor.as_mut() { + if let Some(limits_executor) = state.specific_state.limits_executor.as_mut() { limits_executor.check_instruction_limits()?; } @@ -925,10 +940,13 @@ impl<'wrld> Runtime> { event: Event, ) -> Result<()> { let span = wasm_log_span!("Trigger execution", %id, %authority); - let state = state::Trigger { - common: state::Common::new(wsv, authority, self.config, span), - triggering_event: event, - }; + let state = state::Trigger::new( + authority, + self.config, + span, + state::wsv::WithMut(wsv), + state::specific::Trigger::new(event), + ); let mut store = self.create_store(state); let instance = self.instantiate_module(module, &mut store)?; @@ -937,27 +955,30 @@ impl<'wrld> Runtime> { // NOTE: This function takes ownership of the pointer main_fn - .call(store, ()) - .map_err(ExportFnCallError::from) - .map_err(Into::into) + .call(&mut store, ()) + .map_err(ExportFnCallError::from)?; + + let mut state = store.into_data(); + let executed_queries = state.take_executed_queries(); + forget_all_executed_queries(state.wsv.0.query_handle(), executed_queries) } #[codec::wrap] fn get_trigger_payload(state: &state::Trigger) -> payloads::Trigger { payloads::Trigger { - owner: state.authority().clone(), - event: state.triggering_event.clone(), + owner: state.authority.clone(), + event: state.specific_state.triggering_event.clone(), } } } -impl<'wrld> import_traits::ExecuteOperations> +impl<'wrld> import::traits::ExecuteOperations> for Runtime> { #[codec::wrap] fn execute_query( query_request: SmartContractQueryRequest, - state: &state::Trigger<'wrld>, + state: &mut state::Trigger<'wrld>, ) -> Result, ValidationFail> { Self::default_execute_query(query_request, state) } @@ -977,46 +998,31 @@ impl<'wrld> import_traits::ExecuteOperations> /// *Mut* means that [`WorldStateView`] will be mutated. trait ExecuteOperationsAsExecutorMut {} -impl import_traits::ExecuteOperations for R +impl<'wrld, R, S> + import::traits::ExecuteOperations, S>> for R where - R: ExecuteOperationsAsExecutorMut, - S: state::Wsv + state::WsvMut + state::Authority, + R: ExecuteOperationsAsExecutorMut, S>>, + state::CommonState, S>: state::ValidateQueryOperation, { #[codec::wrap] fn execute_query( query_request: SmartContractQueryRequest, - state: &S, + state: &mut state::CommonState, S>, ) -> Result, ValidationFail> { debug!(%query_request, "Executing as executor"); - let wsv = state.wsv(); - - match query_request.0 { - QueryRequest::Query(QueryWithParameters { - query, - sorting, - pagination, - fetch_size, - }) => { - let output = query.execute(wsv)?; - - wsv.query_handle() - .handle_query_output(output, &sorting, pagination, fetch_size) - } - QueryRequest::Cursor(cursor) => wsv.query_handle().handle_query_cursor(cursor), - } - .map_err(Into::into) + Runtime::default_execute_query(query_request, state) } #[codec::wrap] fn execute_instruction( instruction: InstructionExpr, - state: &mut S, + state: &mut state::CommonState, S>, ) -> Result<(), ValidationFail> { debug!(%instruction, "Executing as executor"); instruction - .execute(&state.authority().clone(), state.wsv_mut()) + .execute(&state.authority.clone(), state.wsv.0) .map_err(Into::into) } } @@ -1030,7 +1036,7 @@ trait FakeSetPermissionTokenSchema { const ENTRYPOINT_FN_NAME: &'static str; } -impl import_traits::SetPermissionTokenSchema for R +impl import::traits::SetPermissionTokenSchema for R where R: FakeSetPermissionTokenSchema, { @@ -1063,10 +1069,13 @@ impl<'wrld> Runtime> { self.execute_executor_validate_internal( module, - state::executor::ValidateTransaction { - common: state::Common::new(wsv, authority.clone(), self.config, span), - to_validate: transaction, - }, + state::executor::ValidateTransaction::new( + authority.clone(), + self.config, + span, + state::wsv::WithMut(wsv), + state::specific::executor::ValidateTransaction::new(transaction), + ), import::EXECUTOR_VALIDATE_TRANSACTION, ) } @@ -1077,7 +1086,7 @@ impl<'wrld> ExecuteOperationsAsExecutorMut import_traits::GetExecutorPayloads> +impl<'wrld> import::traits::GetExecutorPayloads> for Runtime> { #[codec::wrap] @@ -1092,9 +1101,9 @@ impl<'wrld> import_traits::GetExecutorPayloads, ) -> Validate { Validate { - authority: state.authority().clone(), - block_height: state.wsv().height(), - to_validate: state.to_validate.clone(), + authority: state.authority.clone(), + block_height: state.wsv.0.height(), + to_validate: state.specific_state.to_validate.clone(), } } @@ -1139,10 +1148,13 @@ impl<'wrld> Runtime> { self.execute_executor_validate_internal( module, - state::executor::ValidateInstruction { - common: state::Common::new(wsv, authority.clone(), self.config, span), - to_validate: instruction, - }, + state::executor::ValidateInstruction::new( + authority.clone(), + self.config, + span, + state::wsv::WithMut(wsv), + state::specific::executor::ValidateInstruction::new(instruction), + ), import::EXECUTOR_VALIDATE_INSTRUCTION, ) } @@ -1153,7 +1165,7 @@ impl<'wrld> ExecuteOperationsAsExecutorMut import_traits::GetExecutorPayloads> +impl<'wrld> import::traits::GetExecutorPayloads> for Runtime> { #[codec::wrap] @@ -1175,9 +1187,9 @@ impl<'wrld> import_traits::GetExecutorPayloads, ) -> Validate { Validate { - authority: state.authority().clone(), - block_height: state.wsv().height(), - to_validate: state.to_validate.clone(), + authority: state.authority.clone(), + block_height: state.wsv.0.height(), + to_validate: state.specific_state.to_validate.clone(), } } @@ -1215,45 +1227,29 @@ impl<'wrld> Runtime> { self.execute_executor_validate_internal( module, - state::executor::ValidateQuery { - wsv, - authority: authority.clone(), - store_limits: state::store_limits_from_config(&self.config), - log_span: span, - query, - }, + state::executor::ValidateQuery::new( + authority.clone(), + self.config, + span, + state::wsv::WithConst(wsv), + state::specific::executor::ValidateQuery::new(query), + ), import::EXECUTOR_VALIDATE_QUERY, ) } } -impl<'wrld> import_traits::ExecuteOperations> +impl<'wrld> import::traits::ExecuteOperations> for Runtime> { #[codec::wrap] fn execute_query( query_request: SmartContractQueryRequest, - state: &state::executor::ValidateQuery<'wrld>, + state: &mut state::executor::ValidateQuery<'wrld>, ) -> Result, ValidationFail> { debug!(%query_request, "Executing as executor"); - let wsv = state.wsv(); - - match query_request.0 { - QueryRequest::Query(QueryWithParameters { - query, - sorting, - pagination, - fetch_size, - }) => { - let output = query.execute(wsv)?; - - wsv.query_handle() - .handle_query_output(output, &sorting, pagination, fetch_size) - } - QueryRequest::Cursor(cursor) => wsv.query_handle().handle_query_cursor(cursor), - } - .map_err(Into::into) + Runtime::default_execute_query(query_request, state) } #[codec::wrap] @@ -1265,7 +1261,7 @@ impl<'wrld> import_traits::ExecuteOperations import_traits::GetExecutorPayloads> +impl<'wrld> import::traits::GetExecutorPayloads> for Runtime> { #[codec::wrap] @@ -1292,9 +1288,9 @@ impl<'wrld> import_traits::GetExecutorPayloads, ) -> Validate { Validate { - authority: state.authority().clone(), - block_height: state.wsv().height(), - to_validate: state.query.clone(), + authority: state.authority.clone(), + block_height: state.wsv.0.height(), + to_validate: state.specific_state.to_validate.clone(), } } } @@ -1321,12 +1317,13 @@ impl<'wrld> Runtime> { module: &wasmtime::Module, ) -> Result { let span = wasm_log_span!("Running migration"); - let state = state::executor::Migrate(state::Common::new( - wsv, + let state = state::executor::Migrate::new( authority.clone(), self.config, span, - )); + state::wsv::WithMut(wsv), + state::specific::executor::Migrate, + ); let mut store = self.create_store(state); let instance = self.instantiate_module(module, &mut store)?; @@ -1361,13 +1358,13 @@ impl<'wrld> ExecuteOperationsAsExecutorMut> /// /// Panics with error message if called, because it should never be called from /// `migrate()` entrypoint. -impl<'wrld> import_traits::GetExecutorPayloads> +impl<'wrld> import::traits::GetExecutorPayloads> for Runtime> { #[codec::wrap] fn get_migrate_payload(state: &state::executor::Migrate<'wrld>) -> payloads::Migrate { payloads::Migrate { - block_height: state.wsv().height(), + block_height: state.wsv.0.height(), } } @@ -1391,7 +1388,7 @@ impl<'wrld> import_traits::GetExecutorPayloads> } } -impl<'wrld> import_traits::SetPermissionTokenSchema> +impl<'wrld> import::traits::SetPermissionTokenSchema> for Runtime> { #[codec::wrap] @@ -1401,7 +1398,7 @@ impl<'wrld> import_traits::SetPermissionTokenSchema, + pub query_id: Option, /// Pointer to the next element in the result set pub cursor: Option, } impl ForwardCursor { /// Create a new cursor. - pub const fn new(query_id: Option, cursor: Option) -> Self { + pub const fn new(query_id: Option, cursor: Option) -> Self { Self { query_id, cursor } } } @@ -50,7 +51,7 @@ mod candidate { #[derive(Decode, Deserialize)] struct ForwardCursorCandidate { - query_id: Option, + query_id: Option, cursor: Option, } @@ -92,7 +93,7 @@ mod candidate { } } -impl From for Vec<(&'static str, String)> { +impl From for Vec<(&'static str, QueryId)> { fn from(cursor: ForwardCursor) -> Self { match (cursor.query_id, cursor.cursor) { (Some(query_id), Some(cursor)) => { diff --git a/data_model/src/query/mod.rs b/data_model/src/query/mod.rs index 6d826de4956..d5df0b85016 100644 --- a/data_model/src/query/mod.rs +++ b/data_model/src/query/mod.rs @@ -95,6 +95,9 @@ macro_rules! queries { }; } +/// Unique id of a query. +pub type QueryId = String; + /// Trait for typesafe query output pub trait Query: Into + seal::Sealed { /// Output type of query @@ -1613,6 +1616,6 @@ pub mod prelude { pub use super::{ account::prelude::*, asset::prelude::*, block::prelude::*, domain::prelude::*, peer::prelude::*, permission::prelude::*, role::prelude::*, transaction::*, - trigger::prelude::*, FetchSize, QueryBox, TransactionQueryOutput, + trigger::prelude::*, FetchSize, QueryBox, QueryId, TransactionQueryOutput, }; } diff --git a/ffi/derive/src/convert.rs b/ffi/derive/src/convert.rs index 4b2b0450e25..046c6cfd2a2 100644 --- a/ffi/derive/src/convert.rs +++ b/ffi/derive/src/convert.rs @@ -77,13 +77,13 @@ impl syn2::parse::Parse for SpannedFfiTypeToken { "non_owning" => Ok(((span, FfiTypeToken::UnsafeNonOwning), after_group)), other => Err(syn2::Error::new( token.span(), - format!("unknown unsafe ffi type kind: {}", other), + format!("unknown unsafe ffi type kind: {other}"), )), } } other => Err(syn2::Error::new( span, - format!("unknown unsafe ffi type kind: {}", other), + format!("unknown unsafe ffi type kind: {other}"), )), } })?; @@ -110,7 +110,7 @@ impl syn2::parse::Parse for FfiTypeKindAttribute { other => { return Err(syn2::Error::new( token.span, - format!("`{}` cannot be used on a type", other), + format!("`{other}` cannot be used on a type"), )) } }) @@ -132,7 +132,7 @@ impl syn2::parse::Parse for FfiTypeKindFieldAttribute { other => { return Err(syn2::Error::new( token.span, - format!("`{}` cannot be used on a field", other), + format!("`{other}` cannot be used on a field"), )) } }) diff --git a/smart_contract/executor/src/default.rs b/smart_contract/executor/src/default.rs index 38327a7bb11..78e46240d7f 100644 --- a/smart_contract/executor/src/default.rs +++ b/smart_contract/executor/src/default.rs @@ -955,7 +955,7 @@ pub mod role { let role_id = $isi.object; let find_role_query_res = match FindRoleByRoleId::new(role_id).execute() { - Ok(res) => res.into_inner(), + Ok(res) => res.into_raw_parts().0, Err(error) => { deny!($executor, error); } diff --git a/smart_contract/executor/src/lib.rs b/smart_contract/executor/src/lib.rs index a6644def1fd..a8d9dea11e0 100644 --- a/smart_contract/executor/src/lib.rs +++ b/smart_contract/executor/src/lib.rs @@ -18,6 +18,7 @@ pub use iroha_smart_contract as smart_contract; pub use iroha_smart_contract_utils::{debug, encode_with_length_prefix}; #[cfg(not(test))] use iroha_smart_contract_utils::{decode_with_length_prefix_from_raw, encode_and_execute}; +pub use smart_contract::parse; pub mod default; pub mod permission; @@ -171,32 +172,6 @@ macro_rules! deny { }}; } -/// Macro to parse literal as a type. Panics if failed. -/// -/// # Example -/// -/// ```no_run -/// use iroha_executor::parse; -/// use iroha_data_model::prelude::*; -/// -/// let account_id = parse!("alice@wonderland" as AccountId); -/// ``` -#[macro_export] -macro_rules! parse { - ($l:literal as _) => { - compile_error!( - "Don't use `_` as a type in this macro, \ - otherwise panic message would be less informative" - ) - }; - ($l:literal as $t:ty) => { - $crate::debug::DebugExpectExt::dbg_expect( - $l.parse::<$t>(), - concat!("Failed to parse `", $l, "` as `", stringify!($t), "`"), - ) - }; -} - /// Collection of all permission tokens defined by the executor #[derive(Debug, Clone, Default)] pub struct PermissionTokenSchema(Vec, MetaMap); diff --git a/smart_contract/executor/src/permission.rs b/smart_contract/executor/src/permission.rs index 5a4b0f45ebf..e08040fe76a 100644 --- a/smart_contract/executor/src/permission.rs +++ b/smart_contract/executor/src/permission.rs @@ -142,7 +142,8 @@ pub mod asset_definition { ) -> Result { let asset_definition = FindAssetDefinitionById::new(asset_definition_id.clone()) .execute() - .map(QueryOutputCursor::into_inner)?; + .map(QueryOutputCursor::into_raw_parts) + .map(|(batch, _cursor)| batch)?; if asset_definition.owned_by() == authority { Ok(true) } else { @@ -228,7 +229,8 @@ pub mod trigger { pub fn is_trigger_owner(trigger_id: &TriggerId, authority: &AccountId) -> Result { let trigger = FindTriggerById::new(trigger_id.clone()) .execute() - .map(QueryOutputCursor::into_inner)?; + .map(QueryOutputCursor::into_raw_parts) + .map(|(batch, _cursor)| batch)?; if trigger.action().authority() == authority { Ok(true) } else { @@ -272,7 +274,8 @@ pub mod domain { pub fn is_domain_owner(domain_id: &DomainId, authority: &AccountId) -> Result { FindDomainById::new(domain_id.clone()) .execute() - .map(QueryOutputCursor::into_inner) + .map(QueryOutputCursor::into_raw_parts) + .map(|(batch, _cursor)| batch) .map(|domain| domain.owned_by() == authority) } diff --git a/smart_contract/src/lib.rs b/smart_contract/src/lib.rs index 03d84f2ef33..5ec0acd2083 100644 --- a/smart_contract/src/lib.rs +++ b/smart_contract/src/lib.rs @@ -43,6 +43,31 @@ unsafe extern "C" fn _iroha_smart_contract_dealloc(offset: *mut u8, len: usize) let _box = Box::from_raw(core::slice::from_raw_parts_mut(offset, len)); } +/// Macro to parse literal as a type. Panics if failed. +/// +/// # Example +/// +/// ``` +/// use iroha_smart_contract::{prelude::*, parse}; +/// +/// let account_id = parse!("alice@wonderland" as AccountId); +/// ``` +#[macro_export] +macro_rules! parse { + ($l:literal as _) => { + compile_error!( + "Don't use `_` as a type in this macro, \ + otherwise panic message would be less informative" + ) + }; + ($l:literal as $t:ty) => { + $crate::debug::DebugExpectExt::dbg_expect( + $l.parse::<$t>(), + concat!("Failed to parse `", $l, "` as `", stringify!($t), "`"), + ) + }; +} + /// Implementing instructions can be executed on the host pub trait ExecuteOnHost: Instruction { /// Execute instruction on the host @@ -229,9 +254,9 @@ pub struct QueryOutputCursor { } impl QueryOutputCursor { - /// Get inner value consuming [`Self`]. - pub fn into_inner(self) -> T { - self.batch + /// Get inner values of batch and cursor, consuming [`Self`]. + pub fn into_raw_parts(self) -> (T, ForwardCursor) { + (self.batch, self.cursor) } } @@ -435,7 +460,10 @@ mod host { /// Most used items pub mod prelude { - pub use crate::{ExecuteOnHost, ExecuteQueryOnHost}; + pub use iroha_smart_contract_derive::main; + pub use iroha_smart_contract_utils::debug::DebugUnwrapExt; + + pub use crate::{data_model::prelude::*, ExecuteOnHost, ExecuteQueryOnHost}; } #[cfg(test)] @@ -494,7 +522,7 @@ mod tests { assert_eq!(query, get_test_query()); let response: Result, ValidationFail> = Ok(BatchedResponseV1::new( - QUERY_RESULT.unwrap().into_inner(), + QUERY_RESULT.unwrap().into_raw_parts().0, ForwardCursor::new(None, None), ) .into()); diff --git a/wasm_codec/derive/src/lib.rs b/wasm_codec/derive/src/lib.rs index 2562c243ea4..2053fd0d8e3 100644 --- a/wasm_codec/derive/src/lib.rs +++ b/wasm_codec/derive/src/lib.rs @@ -68,7 +68,7 @@ pub fn wrap(attr: TokenStream, item: TokenStream) -> TokenStream { let ident = &fn_item.sig.ident; let mut inner_fn_item = fn_item.clone(); - let inner_fn_ident = syn::Ident::new(&format!("__{}_inner", ident), ident.span()); + let inner_fn_ident = syn::Ident::new(&format!("__{ident}_inner"), ident.span()); inner_fn_item.sig.ident = inner_fn_ident.clone(); let fn_class = classify_fn(&fn_item.sig); @@ -113,7 +113,7 @@ pub fn wrap_trait_fn(attr: TokenStream, item: TokenStream) -> TokenStream { let ident = &fn_item.sig.ident; let mut inner_fn_item = fn_item.clone(); - let inner_fn_ident = syn::Ident::new(&format!("__{}_inner", ident), ident.span()); + let inner_fn_ident = syn::Ident::new(&format!("__{ident}_inner"), ident.span()); inner_fn_item.sig.ident = inner_fn_ident; let fn_class = classify_fn(&fn_item.sig);