diff --git a/Cargo.lock b/Cargo.lock index c380ea9c76b..e5929280f42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2781,6 +2781,7 @@ dependencies = [ "displaydoc", "eyre", "futures-util", + "getset", "hex", "http", "iroha", diff --git a/client/Cargo.toml b/client/Cargo.toml index 2b108118dcd..3a5f8ad0f8f 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -46,6 +46,7 @@ parity-scale-codec = { workspace = true, default-features = false, features = [" tokio = { workspace = true, features = ["rt"] } tokio-tungstenite = { workspace = true, features = ["native-tls"] } futures-util = "0.3.28" +getset = { workspace = true } [dev-dependencies] iroha_wasm_builder = { workspace = true } diff --git a/client/src/client.rs b/client/src/client.rs index d81c39aff56..11e96c56752 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -20,7 +20,7 @@ use iroha_data_model::{ isi::Instruction, predicate::PredicateBox, prelude::*, - query::{Pagination, Query, Sorting}, + query::{cursor::ForwardCursor, Pagination, Query, Sorting}, transaction::TransactionPayload, BatchedResponse, ValidationFail, }; @@ -338,12 +338,13 @@ impl_query_output! { } /// Iroha client -#[derive(Clone, DebugCustom, Display)] +#[derive(Clone, DebugCustom, Display, getset::Getters)] #[debug( fmt = "Client {{ torii: {torii_url}, public_key: {} }}", "key_pair.public_key()" )] #[display(fmt = "{}@{torii_url}", "key_pair.public_key()")] +#[getset(get = "pub")] pub struct Client { /// Url for accessing iroha node torii_url: Url, @@ -388,6 +389,7 @@ impl QueryRequest { ), } } + fn assemble(self) -> DefaultRequestBuilder { let builder = DefaultRequestBuilder::new( HttpMethod::POST, @@ -837,7 +839,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 +875,29 @@ impl Client { self.build_query(request).execute() } + /// Query API entry point using cursor. + /// + /// # Errors + /// Fails if sending request fails + pub fn request_with_cursor(&self, 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..3ffbf1358c1 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 0ef1007e61c..bc3c581289e 100644 Binary files a/configs/peer/executor.wasm and b/configs/peer/executor.wasm differ diff --git a/core/src/query/store.rs b/core/src/query/store.rs index 8e0122b5093..95fc5aefed1 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, } @@ -103,7 +103,8 @@ impl LiveQueryStore { "All handler to LiveQueryStore are dropped. Shutting down..."; let (insert_sender, mut insert_receiver) = mpsc::channel(1); - let (remove_sender, mut remove_receiver) = mpsc::channel::<(String, oneshot::Sender<_>)>(1); + let (remove_sender, mut remove_receiver) = + mpsc::channel::<(QueryId, oneshot::Sender<_>)>(1); let mut idle_interval = tokio::time::interval(self.query_idle_time); @@ -141,7 +142,7 @@ impl LiveQueryStore { } } - 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())); } @@ -154,10 +155,10 @@ impl LiveQueryStore { #[derive(Clone)] pub struct LiveQueryStoreHandle { /// Sender to insert a new query with specified id. - insert_sender: mpsc::Sender<(String, LiveQuery)>, + insert_sender: mpsc::Sender<(QueryId, LiveQuery)>, /// Sender to send a tuple of query id and another sender, which will be /// used by [`LiveQueryStore`] to write a response with optional live query. - remove_sender: mpsc::Sender<(String, oneshot::Sender>)>, + remove_sender: mpsc::Sender<(QueryId, oneshot::Sender>)>, } impl LiveQueryStoreHandle { @@ -209,13 +210,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.insert_sender .blocking_send((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.remove_sender @@ -227,7 +240,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);