diff --git a/cli/src/torii/routing.rs b/cli/src/torii/routing.rs index dfc6f504355..bed382b7bc7 100644 --- a/cli/src/torii/routing.rs +++ b/cli/src/torii/routing.rs @@ -5,7 +5,7 @@ // FIXME: This can't be fixed, because one trait in `warp` is private. #![allow(opaque_hidden_inferred_bound)] -use std::{cmp::Ordering, num::TryFromIntError}; +use std::cmp::Ordering; use eyre::WrapErr; use futures::TryStreamExt; @@ -15,7 +15,10 @@ use iroha_config::{ torii::uri, GetConfiguration, PostConfiguration, }; -use iroha_core::{smartcontracts::isi::query::ValidQueryRequest, sumeragi::SumeragiHandle}; +use iroha_core::{ + smartcontracts::{isi::query::ValidQueryRequest, query::LazyValue}, + sumeragi::SumeragiHandle, +}; use iroha_data_model::{ block::{ stream::{ @@ -25,7 +28,6 @@ use iroha_data_model::{ VersionedCommittedBlock, }, prelude::*, - query::error::QueryExecutionFail, }; use iroha_logger::prelude::*; #[cfg(feature = "telemetry")] @@ -71,43 +73,34 @@ pub(crate) async fn handle_queries( pagination: Pagination, sorting: Sorting, request: VersionedSignedQuery, -) -> Result> { - let result = { - let mut wsv = sumeragi.wsv_clone(); - let valid_request = ValidQueryRequest::validate(request, &mut wsv)?; - valid_request.execute(&wsv).map_err(ValidationFail::from)? - }; +) -> Result> { + let mut wsv = sumeragi.wsv_clone(); - let (total, result) = if let Value::Vec(vec_of_val) = result { - let len = vec_of_val.len(); - let vec_of_val = apply_sorting_and_pagination(vec_of_val.into_iter(), &sorting, pagination); + let valid_request = ValidQueryRequest::validate(request, &mut wsv)?; + let result = valid_request.execute(&wsv).map_err(ValidationFail::from)?; - (len, Value::Vec(vec_of_val)) - } else { - (1, result) + let result = match result { + LazyValue::Value(value) => value, + LazyValue::Iter(iter) => { + Value::Vec(apply_sorting_and_pagination(iter, &sorting, pagination)) + } }; - let total = total - .try_into() - .map_err(|e: TryFromIntError| QueryExecutionFail::Conversion(e.to_string())) - .map_err(ValidationFail::from)?; - let result = QueryResult(result); - let paginated_result = PaginatedQueryResult { + let paginated_result = QueryResult { result, pagination, sorting, - total, }; Ok(Scale(paginated_result.into())) } fn apply_sorting_and_pagination( - vec_of_val: impl Iterator, + iter: impl Iterator, sorting: &Sorting, pagination: Pagination, ) -> Vec { if let Some(key) = &sorting.sort_by_metadata_key { - let mut pairs: Vec<(Option, Value)> = vec_of_val + let mut pairs: Vec<(Option, Value)> = iter .map(|value| { let key = match &value { Value::Identifiable(IdentifiableBox::Asset(asset)) => match asset.value() { @@ -137,7 +130,7 @@ fn apply_sorting_and_pagination( .paginate(pagination) .collect() } else { - vec_of_val.paginate(pagination).collect() + iter.paginate(pagination).collect() } } @@ -167,7 +160,6 @@ async fn handle_pending_transactions( Ok(Scale( queue .all_transactions(&wsv) - .into_iter() .map(Into::into) .paginate(pagination) .collect::>(), @@ -348,7 +340,6 @@ mod subscription { async fn handle_version(sumeragi: SumeragiHandle) -> Json { use iroha_version::Version; - #[allow(clippy::expect_used)] let string = sumeragi .apply_wsv(WorldStateView::latest_block_ref) .expect("Genesis not applied. Nothing we can do. Solve the issue and rerun.") @@ -422,7 +413,6 @@ impl Torii { } } - #[allow(opaque_hidden_inferred_bound)] #[cfg(feature = "telemetry")] /// Helper function to create router. This router can tested without starting up an HTTP server fn create_telemetry_router( @@ -458,7 +448,6 @@ impl Torii { } /// Helper function to create router. This router can tested without starting up an HTTP server - #[allow(opaque_hidden_inferred_bound)] pub(crate) fn create_api_router( &self, ) -> impl warp::Filter + Clone + Send { diff --git a/client/src/client.rs b/client/src/client.rs index 08c4c60b525..687f74b8aca 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -105,10 +105,10 @@ where // Separate-compilation friendly response handling fn _handle_query_response_base( resp: &Response>, - ) -> QueryHandlerResult { + ) -> QueryHandlerResult { match resp.status() { StatusCode::OK => { - let res = VersionedPaginatedQueryResult::decode_all_versioned(resp.body()); + let res = VersionedQueryResult::decode_all_versioned(resp.body()); res.wrap_err( "Failed to decode response from Iroha. \ You are likely using a version of the client library \ @@ -143,7 +143,7 @@ where } } - _handle_query_response_base(&resp).and_then(|VersionedPaginatedQueryResult::V1(result)| { + _handle_query_response_base(&resp).and_then(|VersionedQueryResult::V1(result)| { ClientQueryRequest::try_from(result).map_err(Into::into) }) } @@ -238,7 +238,7 @@ impl From for eyre::Report { } } -/// More convenient version of [`iroha_data_model::prelude::PaginatedQueryResult`]. +/// More convenient version of [`iroha_data_model::prelude::QueryResult`]. /// The only difference is that this struct has `output` field extracted from the result /// accordingly to the source query. #[derive(Clone, Debug)] @@ -249,12 +249,10 @@ where { /// Query output pub output: R::Output, - /// See [`iroha_data_model::prelude::PaginatedQueryResult`] + /// See [`iroha_data_model::prelude::QueryResult`] pub pagination: Pagination, - /// See [`iroha_data_model::prelude::PaginatedQueryResult`] + /// See [`iroha_data_model::prelude::QueryResult`] pub sorting: Sorting, - /// See [`iroha_data_model::prelude::PaginatedQueryResult`] - pub total: u64, } impl ClientQueryRequest @@ -268,7 +266,7 @@ where } } -impl TryFrom for ClientQueryRequest +impl TryFrom for ClientQueryRequest where R: Query + Debug, >::Error: Into, @@ -276,14 +274,13 @@ where type Error = eyre::Report; fn try_from( - PaginatedQueryResult { + QueryResult { result, pagination, sorting, - total, - }: PaginatedQueryResult, + }: QueryResult, ) -> Result { - let output = R::Output::try_from(result.into()) + let output = R::Output::try_from(result) .map_err(Into::into) .wrap_err("Unexpected type")?; @@ -291,7 +288,6 @@ where output, pagination, sorting, - total, }) } } @@ -729,9 +725,9 @@ impl Client { pub fn prepare_query_request( &self, request: R, + filter: PredicateBox, pagination: Pagination, sorting: Sorting, - filter: PredicateBox, ) -> Result<(B, QueryResponseHandler)> where R: Query + Debug, @@ -760,7 +756,7 @@ impl Client { /// /// # Errors /// Fails if sending request fails - pub fn request_with_pagination_and_filter_and_sorting( + pub fn request_with_filter_and_pagination_and_sorting( &self, request: R, pagination: Pagination, @@ -773,7 +769,7 @@ impl Client { { iroha_logger::trace!(?request, %pagination, ?sorting, ?filter); let (req, resp_handler) = self.prepare_query_request::( - request, pagination, sorting, filter, + request, filter, pagination, sorting, )?; let response = req.build()?.send()?; resp_handler.handle(response) @@ -793,7 +789,7 @@ impl Client { R: Query + Debug, >::Error: Into, { - self.request_with_pagination_and_filter_and_sorting( + self.request_with_filter_and_pagination_and_sorting( request, pagination, sorting, @@ -805,7 +801,7 @@ impl Client { /// /// # Errors /// Fails if sending request fails - pub fn request_with_pagination_and_filter( + pub fn request_with_filter_and_pagination( &self, request: R, pagination: Pagination, @@ -815,7 +811,7 @@ impl Client { R: Query + Debug, >::Error: Into, // Seems redundant { - self.request_with_pagination_and_filter_and_sorting( + self.request_with_filter_and_pagination_and_sorting( request, pagination, Sorting::default(), @@ -827,7 +823,7 @@ impl Client { /// /// # Errors /// Fails if sending request fails - pub fn request_with_sorting_and_filter( + pub fn request_with_filter_and_sorting( &self, request: R, sorting: Sorting, @@ -837,7 +833,7 @@ impl Client { R: Query + Debug, >::Error: Into, // Seems redundant { - self.request_with_pagination_and_filter_and_sorting( + self.request_with_filter_and_pagination_and_sorting( request, Pagination::default(), sorting, @@ -861,7 +857,7 @@ impl Client { R: Query + Debug, >::Error: Into, { - self.request_with_pagination_and_filter(request, Pagination::default(), filter) + self.request_with_filter_and_pagination(request, Pagination::default(), filter) } /// Query API entry point. Requests queries from `Iroha` peers with pagination. @@ -880,7 +876,7 @@ impl Client { R: Query + Debug, >::Error: Into, { - self.request_with_pagination_and_filter(request, pagination, PredicateBox::default()) + self.request_with_filter_and_pagination(request, pagination, PredicateBox::default()) } /// Query API entry point. Requests queries from `Iroha` peers with sorting. diff --git a/client/tests/integration/sorting.rs b/client/tests/integration/sorting.rs index 99e5f6aa085..ae59bb2e643 100644 --- a/client/tests/integration/sorting.rs +++ b/client/tests/integration/sorting.rs @@ -157,7 +157,7 @@ fn correct_sorting_of_entities() { .expect("Valid"); let res = test_client - .request_with_sorting_and_filter( + .request_with_filter_and_sorting( client::asset::all_definitions(), Sorting::by_metadata_key(sort_by_metadata_key.clone()), PredicateBox::new(value::ValuePredicate::Identifiable( @@ -208,7 +208,7 @@ fn correct_sorting_of_entities() { .expect("Valid"); let res = test_client - .request_with_sorting_and_filter( + .request_with_filter_and_sorting( client::account::all(), Sorting::by_metadata_key(sort_by_metadata_key.clone()), PredicateBox::new(value::ValuePredicate::Identifiable( @@ -258,7 +258,7 @@ fn correct_sorting_of_entities() { .expect("Valid"); let res = test_client - .request_with_pagination_and_filter_and_sorting( + .request_with_filter_and_pagination_and_sorting( client::domain::all(), Pagination::default(), Sorting::by_metadata_key(sort_by_metadata_key.clone()), @@ -310,7 +310,7 @@ fn correct_sorting_of_entities() { string::StringPredicate::starts_with("neverland_"), )); let res = test_client - .request_with_pagination_and_filter_and_sorting( + .request_with_filter_and_pagination_and_sorting( client::domain::all(), Pagination::default(), Sorting::by_metadata_key(sort_by_metadata_key), @@ -370,7 +370,7 @@ fn sort_only_elements_which_have_sorting_key() -> Result<()> { .wrap_err("Failed to register accounts")?; let res = test_client - .request_with_sorting_and_filter( + .request_with_filter_and_sorting( client::account::all(), Sorting::by_metadata_key(sort_by_metadata_key), PredicateBox::new(value::ValuePredicate::Identifiable( diff --git a/client/tests/integration/triggers/time_trigger.rs b/client/tests/integration/triggers/time_trigger.rs index cb00e6c33ae..0543f4a7361 100644 --- a/client/tests/integration/triggers/time_trigger.rs +++ b/client/tests/integration/triggers/time_trigger.rs @@ -118,10 +118,12 @@ fn change_asset_metadata_after_1_sec() -> Result<()> { usize::try_from(PERIOD_MS / DEFAULT_CONSENSUS_ESTIMATION_MS + 1)?, )?; - let value = test_client.request(FindAssetDefinitionKeyValueByIdAndKey { - id: asset_definition_id.into(), - key: key.into(), - })?; + let value = test_client + .request(FindAssetDefinitionKeyValueByIdAndKey { + id: asset_definition_id.into(), + key: key.into(), + })? + .into(); assert!(matches!(value, Value::Numeric(NumericValue::U32(3_u32)))); Ok(()) diff --git a/core/src/block.rs b/core/src/block.rs index 2bbb8b2f0d7..71b3cbe8077 100644 --- a/core/src/block.rs +++ b/core/src/block.rs @@ -173,7 +173,7 @@ impl BlockBuilder<'_> { for tx in self.transactions { match transaction_validator.validate(tx, height == 1, self.wsv) { Ok(transaction) => txs.push(TransactionValue { - tx: transaction, + value: transaction, error: None, }), Err((transaction, error)) => { @@ -183,7 +183,7 @@ impl BlockBuilder<'_> { "Transaction validation failed", ); txs.push(TransactionValue { - tx: transaction, + value: transaction, error: Some(error), }); } @@ -192,13 +192,13 @@ impl BlockBuilder<'_> { header.transactions_hash = txs .iter() .filter(|tx| tx.error.is_none()) - .map(|tx| tx.tx.hash()) + .map(|tx| tx.value.hash()) .collect::>() .hash(); header.rejected_transactions_hash = txs .iter() .filter(|tx| tx.error.is_some()) - .map(|tx| tx.tx.hash()) + .map(|tx| tx.value.hash()) .collect::>() .hash(); // TODO: Validate Event recommendations somehow? @@ -407,7 +407,7 @@ impl Revalidate for PendingBlock { fn has_committed_transactions(&self, wsv: &WorldStateView) -> bool { self.transactions .iter() - .any(|tx| wsv.has_transaction(tx.tx.hash())) + .any(|tx| wsv.has_transaction(tx.value.hash())) } } @@ -510,7 +510,7 @@ impl Revalidate for VersionedCommittedBlock { VersionedCommittedBlock::V1(block) => block .transactions .iter() - .any(|tx| wsv.has_transaction(tx.tx.hash())), + .any(|tx| wsv.has_transaction(tx.value.hash())), } } } @@ -525,7 +525,7 @@ fn revalidate_hashes( transactions .iter() .filter(|tx| tx.error.is_none()) - .map(|tx| tx.tx.hash()) + .map(|tx| tx.value.hash()) .collect::>() .hash() .eq(&transactions_hash) @@ -535,7 +535,7 @@ fn revalidate_hashes( transactions .iter() .filter(|tx| tx.error.is_some()) - .map(|tx| tx.tx.hash()) + .map(|tx| tx.value.hash()) .collect::>() .hash() .eq(&rejected_transactions_hash) @@ -556,10 +556,10 @@ fn revalidate_transactions( if tx.error.is_some() { let _rejected_tx = if is_genesis { Ok(AcceptedTransaction::accept_genesis(GenesisTransaction( - tx.tx, + tx.value, ))) } else { - AcceptedTransaction::accept(tx.tx, &transaction_validator.transaction_limits) + AcceptedTransaction::accept(tx.value, &transaction_validator.transaction_limits) } .map_err(TransactionRevalidationError::Accept) .and_then(|tx| { @@ -571,10 +571,10 @@ fn revalidate_transactions( } else { let tx = if is_genesis { Ok(AcceptedTransaction::accept_genesis(GenesisTransaction( - tx.tx, + tx.value, ))) } else { - AcceptedTransaction::accept(tx.tx, &transaction_validator.transaction_limits) + AcceptedTransaction::accept(tx.value, &transaction_validator.transaction_limits) } .map_err(TransactionRevalidationError::Accept)?; diff --git a/core/src/kura.rs b/core/src/kura.rs index c9cff4937fc..35a8fc9babf 100644 --- a/core/src/kura.rs +++ b/core/src/kura.rs @@ -316,17 +316,17 @@ impl Kura { } /// Put a block in kura's in memory block store. - pub fn store_block(&self, block: impl Into>) { - let block = block.into(); - self.block_data.lock().push((block.hash(), Some(block))); + pub fn store_block(&self, block: VersionedCommittedBlock) { + self.block_data + .lock() + .push((block.hash(), Some(Arc::new(block)))); } /// Replace the block in `Kura`'s in memory block store. - pub fn replace_top_block(&self, block: impl Into>) { - let block = block.into(); + pub fn replace_top_block(&self, block: VersionedCommittedBlock) { let mut data = self.block_data.lock(); data.pop(); - data.push((block.hash(), Some(block))); + data.push((block.hash(), Some(Arc::new(block)))); } } diff --git a/core/src/queue.rs b/core/src/queue.rs index 61fbb133d18..2fb760a1b93 100644 --- a/core/src/queue.rs +++ b/core/src/queue.rs @@ -175,12 +175,17 @@ impl Queue { } /// Returns all pending transactions. - pub fn all_transactions(&self, wsv: &WorldStateView) -> Vec { - self.txs - .iter() - .filter(|e| self.is_pending(e.value(), wsv)) - .map(|e| e.value().clone()) - .collect() + pub fn all_transactions<'q: 'wsv, 'wsv>( + &'q self, + wsv: &'wsv WorldStateView, + ) -> impl Iterator + 'wsv { + self.txs.iter().filter_map(|tx| { + if self.is_pending(tx.value(), wsv) { + return Some(tx.value().clone()); + } + + None + }) } /// Returns `n` randomly selected transaction from the queue. diff --git a/core/src/smartcontracts/isi/account.rs b/core/src/smartcontracts/isi/account.rs index 0f5033eb5b3..3cda1ee2bcf 100644 --- a/core/src/smartcontracts/isi/account.rs +++ b/core/src/smartcontracts/isi/account.rs @@ -479,53 +479,65 @@ pub mod query { }; use super::*; + use crate::smartcontracts::query::Lazy; impl ValidQuery for FindRolesByAccountId { #[metrics(+"find_roles_by_account_id")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let account_id = wsv .evaluate(&self.id) .wrap_err("Failed to evaluate account id") .map_err(|e| Error::Evaluate(e.to_string()))?; iroha_logger::trace!(%account_id, roles=?wsv.world.roles); - let roles = wsv.map_account(&account_id, |account| { - account.roles.iter().cloned().collect::>() - })?; - Ok(roles) + Ok(Box::new( + wsv.map_account(&account_id, |account| &account.roles)? + .iter() + .cloned(), + )) } } impl ValidQuery for FindPermissionTokensByAccountId { #[metrics(+"find_permission_tokens_by_account_id")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let account_id = wsv .evaluate(&self.id) .wrap_err("Failed to evaluate account id") .map_err(|e| Error::Evaluate(e.to_string()))?; iroha_logger::trace!(%account_id, accounts=?wsv.world.domains); - let tokens = wsv.map_account(&account_id, |account| { - wsv.account_permission_tokens(account) - })?; - Ok(tokens.into_iter().collect()) + Ok(Box::new( + wsv.account_permission_tokens(&account_id)?.cloned(), + )) } } impl ValidQuery for FindAllAccounts { #[metrics(+"find_all_accounts")] - fn execute(&self, wsv: &WorldStateView) -> Result { - let mut vec = Vec::new(); - for domain in wsv.domains().values() { - for account in domain.accounts.values() { - vec.push(account.clone()) - } - } - Ok(vec) + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { + Ok(Box::new( + wsv.domains() + .values() + .flat_map(|domain| domain.accounts.values()) + .cloned(), + )) } } impl ValidQuery for FindAccountById { #[metrics(+"find_account_by_id")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get id") @@ -537,39 +549,53 @@ pub mod query { impl ValidQuery for FindAccountsByName { #[metrics(+"find_account_by_name")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let name = wsv .evaluate(&self.name) .wrap_err("Failed to get account name") .map_err(|e| Error::Evaluate(e.to_string()))?; iroha_logger::trace!(%name); - let mut vec = Vec::new(); - for domain in wsv.domains().values() { - for account in domain.accounts.values() { - if account.id().name == name { - vec.push(account.clone()) - } - } - } - Ok(vec) + Ok(Box::new( + wsv.domains() + .values() + .flat_map(move |domain| { + let name = name.clone(); + + domain + .accounts + .values() + .filter(move |account| account.id().name == name) + }) + .cloned(), + )) } } impl ValidQuery for FindAccountsByDomainId { #[metrics(+"find_accounts_by_domain_id")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let id = wsv .evaluate(&self.domain_id) .wrap_err("Failed to get domain id") .map_err(|e| Error::Evaluate(e.to_string()))?; + iroha_logger::trace!(%id); - Ok(wsv.domain(&id)?.accounts.values().cloned().collect()) + Ok(Box::new(wsv.domain(&id)?.accounts.values().cloned())) } } impl ValidQuery for FindAccountKeyValueByIdAndKey { #[metrics(+"find_account_key_value_by_id_and_key")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get account id") @@ -581,34 +607,32 @@ pub mod query { iroha_logger::trace!(%id, %key); wsv.map_account(&id, |account| account.metadata.get(&key).map(Clone::clone))? .ok_or_else(|| FindError::MetadataKey(key).into()) + .map(Into::into) } } impl ValidQuery for FindAccountsWithAsset { #[metrics(+"find_accounts_with_asset")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let asset_definition_id = wsv .evaluate(&self.asset_definition_id) .wrap_err("Failed to get asset id") .map_err(|e| Error::Evaluate(e.to_string()))?; iroha_logger::trace!(%asset_definition_id); - let domain_id = &asset_definition_id.domain_id; - - wsv.map_domain(domain_id, |domain| { - let found = domain - .accounts - .values() - .filter(|account| { + Ok(Box::new( + wsv.map_domain(&asset_definition_id.domain_id.clone(), move |domain| { + domain.accounts.values().filter(move |account| { let asset_id = AssetId::new(asset_definition_id.clone(), account.id().clone()); account.assets.get(&asset_id).is_some() }) - .cloned() - .collect(); - Ok(found) - }) - .map_err(Into::into) + })? + .cloned(), + )) } } } diff --git a/core/src/smartcontracts/isi/asset.rs b/core/src/smartcontracts/isi/asset.rs index 0028324debd..32544fc073d 100644 --- a/core/src/smartcontracts/isi/asset.rs +++ b/core/src/smartcontracts/isi/asset.rs @@ -441,42 +441,49 @@ pub mod query { }; use super::*; + use crate::smartcontracts::query::Lazy; impl ValidQuery for FindAllAssets { #[metrics(+"find_all_assets")] - fn execute(&self, wsv: &WorldStateView) -> Result { - Ok(wsv - .domains() - .values() - .map(|domain| { - domain - .accounts - .values() - .map(|account| account.assets.values()) - .flatten() - }) - .flatten() - .cloned() - .collect()) + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { + Ok(Box::new( + wsv.domains() + .values() + .flat_map(|domain| { + domain + .accounts + .values() + .flat_map(|account| account.assets.values()) + }) + .cloned(), + )) } } impl ValidQuery for FindAllAssetsDefinitions { #[metrics(+"find_all_asset_definitions")] - fn execute(&self, wsv: &WorldStateView) -> Result { - let mut vec = Vec::new(); - for domain in wsv.domains().values() { - for asset_definition in domain.asset_definitions.values() { - vec.push(asset_definition.clone()) - } - } - Ok(vec) + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { + Ok(Box::new( + wsv.domains() + .values() + .flat_map(|domain| domain.asset_definitions.values()) + .cloned(), + )) } } impl ValidQuery for FindAssetById { #[metrics(+"find_asset_by_id")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get asset id") @@ -494,7 +501,10 @@ pub mod query { impl ValidQuery for FindAssetDefinitionById { #[metrics(+"find_asset_defintion_by_id")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get asset definition id") @@ -508,81 +518,108 @@ pub mod query { impl ValidQuery for FindAssetsByName { #[metrics(+"find_assets_by_name")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let name = wsv .evaluate(&self.name) .wrap_err("Failed to get asset name") .map_err(|e| Error::Evaluate(e.to_string()))?; iroha_logger::trace!(%name); - let mut vec = Vec::new(); - for domain in wsv.domains().values() { - for account in domain.accounts.values() { - for asset in account.assets.values() { - if asset.id().definition_id.name == name { - vec.push(asset.clone()) - } - } - } - } - Ok(vec) + Ok(Box::new( + wsv.domains() + .values() + .flat_map(move |domain| { + let name = name.clone(); + + domain.accounts.values().flat_map(move |account| { + let name = name.clone(); + + account + .assets + .values() + .filter(move |asset| asset.id().definition_id.name == name) + }) + }) + .cloned(), + )) } } impl ValidQuery for FindAssetsByAccountId { #[metrics(+"find_assets_by_account_id")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let id = wsv .evaluate(&self.account_id) .wrap_err("Failed to get account id") .map_err(|e| Error::Evaluate(e.to_string()))?; iroha_logger::trace!(%id); - wsv.account_assets(&id).map_err(Into::into) + Ok(Box::new(wsv.account_assets(&id)?)) } } impl ValidQuery for FindAssetsByAssetDefinitionId { #[metrics(+"find_assets_by_asset_definition_id")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let id = wsv .evaluate(&self.asset_definition_id) .wrap_err("Failed to get asset definition id") .map_err(|e| Error::Evaluate(e.to_string()))?; iroha_logger::trace!(%id); - let mut vec = Vec::new(); - for domain in wsv.domains().values() { - for account in domain.accounts.values() { - for asset in account.assets.values() { - if asset.id().definition_id == id { - vec.push(asset.clone()) - } - } - } - } - Ok(vec) + Ok(Box::new( + wsv.domains() + .values() + .flat_map(move |domain| { + let id = id.clone(); + + domain.accounts.values().flat_map(move |account| { + let id = id.clone(); + + account + .assets + .values() + .filter(move |asset| asset.id().definition_id == id) + }) + }) + .cloned(), + )) } } impl ValidQuery for FindAssetsByDomainId { #[metrics(+"find_assets_by_domain_id")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let id = wsv .evaluate(&self.domain_id) .wrap_err("Failed to get domain id") .map_err(|e| Error::Evaluate(e.to_string()))?; iroha_logger::trace!(%id); - let mut vec = Vec::new(); - for account in wsv.domain(&id)?.accounts.values() { - for asset in account.assets.values() { - vec.push(asset.clone()) - } - } - Ok(vec) + Ok(Box::new( + wsv.domain(&id)? + .accounts + .values() + .flat_map(|account| account.assets.values()) + .cloned(), + )) } } impl ValidQuery for FindAssetsByDomainIdAndAssetDefinitionId { #[metrics(+"find_assets_by_domain_id_and_asset_definition_id")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let domain_id = wsv .evaluate(&self.domain_id) .wrap_err("Failed to get domain id") @@ -597,23 +634,30 @@ pub mod query { .get(&asset_definition_id) .ok_or_else(|| FindError::AssetDefinition(asset_definition_id.clone()))?; iroha_logger::trace!(%domain_id, %asset_definition_id); - let mut assets = Vec::new(); - for account in domain.accounts.values() { - for asset in account.assets.values() { - if asset.id().account_id.domain_id == domain_id - && asset.id().definition_id == asset_definition_id - { - assets.push(asset.clone()) - } - } - } - Ok(assets) + Ok(Box::new( + domain + .accounts + .values() + .flat_map(move |account| { + let domain_id = domain_id.clone(); + let asset_definition_id = asset_definition_id.clone(); + + account.assets.values().filter(move |asset| { + asset.id().account_id.domain_id == domain_id + && asset.id().definition_id == asset_definition_id + }) + }) + .cloned(), + )) } } impl ValidQuery for FindAssetQuantityById { #[metrics(+"find_asset_quantity_by_id")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get asset id") @@ -637,7 +681,10 @@ pub mod query { impl ValidQuery for FindTotalAssetQuantityByAssetDefinitionId { #[metrics(+"find_total_asset_quantity_by_asset_definition_id")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get asset definition id") @@ -650,7 +697,10 @@ pub mod query { impl ValidQuery for FindAssetKeyValueByIdAndKey { #[metrics(+"find_asset_key_value_by_id_and_key")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get asset id") @@ -672,16 +722,20 @@ pub mod query { .try_as_ref() .map_err(eyre::Error::from) .map_err(|e| Error::Conversion(e.to_string()))?; - Ok(store + store .get(&key) - .ok_or_else(|| Error::Find(Box::new(FindError::MetadataKey(key))))? - .clone()) + .ok_or_else(|| Error::Find(Box::new(FindError::MetadataKey(key)))) + .cloned() + .map(Into::into) } } impl ValidQuery for IsAssetDefinitionOwner { #[metrics("is_asset_definition_owner")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let asset_definition_id = wsv .evaluate(&self.asset_definition_id) .wrap_err("Failed to get asset definition id") diff --git a/core/src/smartcontracts/isi/block.rs b/core/src/smartcontracts/isi/block.rs index 4cfd413c6bb..d70c95ad998 100644 --- a/core/src/smartcontracts/isi/block.rs +++ b/core/src/smartcontracts/isi/block.rs @@ -1,7 +1,6 @@ //! This module contains trait implementations related to block queries use eyre::{Result, WrapErr}; use iroha_data_model::{ - block::VersionedCommittedBlock, evaluate::ExpressionEvaluator, query::{ block::FindBlockHeaderByHash, @@ -11,34 +10,40 @@ use iroha_data_model::{ use iroha_telemetry::metrics; use super::*; +use crate::smartcontracts::query::Lazy; impl ValidQuery for FindAllBlocks { #[metrics(+"find_all_blocks")] - fn execute(&self, wsv: &WorldStateView) -> Result { - let blocks = wsv - .all_blocks() - .map(|block| VersionedCommittedBlock::clone(&block)) - .rev() - .collect(); - Ok(blocks) + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, QueryExecutionFail> { + Ok(Box::new( + wsv.all_blocks().rev().map(|block| Clone::clone(&*block)), + )) } } impl ValidQuery for FindAllBlockHeaders { #[metrics(+"find_all_block_headers")] - fn execute(&self, wsv: &WorldStateView) -> Result { - let block_headers = wsv - .all_blocks() - .rev() - .map(|block| block.as_v1().header.clone()) - .collect(); - Ok(block_headers) + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, QueryExecutionFail> { + Ok(Box::new( + wsv.all_blocks() + .rev() + .map(|block| block.as_v1().header.clone()), + )) } } impl ValidQuery for FindBlockHeaderByHash { #[metrics(+"find_block_header")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, QueryExecutionFail> { let hash = wsv .evaluate(&self.hash) .wrap_err("Failed to evaluate hash") diff --git a/core/src/smartcontracts/isi/domain.rs b/core/src/smartcontracts/isi/domain.rs index 177bca4419c..e208457a9dc 100644 --- a/core/src/smartcontracts/isi/domain.rs +++ b/core/src/smartcontracts/isi/domain.rs @@ -291,17 +291,24 @@ pub mod query { use iroha_data_model::query::error::QueryExecutionFail as Error; use super::*; + use crate::smartcontracts::query::Lazy; impl ValidQuery for FindAllDomains { #[metrics(+"find_all_domains")] - fn execute(&self, wsv: &WorldStateView) -> Result { - Ok(wsv.domains().values().cloned().collect()) + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { + Ok(Box::new(wsv.domains().values().cloned())) } } impl ValidQuery for FindDomainById { #[metrics(+"find_domain_by_id")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get domain id") @@ -313,7 +320,10 @@ pub mod query { impl ValidQuery for FindDomainKeyValueByIdAndKey { #[metrics(+"find_domain_key_value_by_id_and_key")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get domain id") @@ -323,16 +333,18 @@ pub mod query { .wrap_err("Failed to get key") .map_err(|e| Error::Evaluate(e.to_string()))?; iroha_logger::trace!(%id, %key); - wsv.map_domain(&id, |domain| { - Ok(domain.metadata.get(&key).map(Clone::clone)) - })? - .ok_or_else(|| FindError::MetadataKey(key).into()) + wsv.map_domain(&id, |domain| domain.metadata.get(&key).map(Clone::clone))? + .ok_or_else(|| FindError::MetadataKey(key).into()) + .map(Into::into) } } impl ValidQuery for FindAssetDefinitionKeyValueByIdAndKey { #[metrics(+"find_asset_definition_key_value_by_id_and_key")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let id = wsv .evaluate(&self.id) .wrap_err("Failed to get asset definition id") @@ -346,8 +358,9 @@ pub mod query { .asset_definition(&id)? .metadata .get(&key) - .ok_or(FindError::MetadataKey(key))? - .clone()) + .ok_or(FindError::MetadataKey(key)) + .cloned() + .map(Into::into)?) } } } diff --git a/core/src/smartcontracts/isi/query.rs b/core/src/smartcontracts/isi/query.rs index ddec4897d35..b363485bb0e 100644 --- a/core/src/smartcontracts/isi/query.rs +++ b/core/src/smartcontracts/isi/query.rs @@ -11,6 +11,49 @@ use parity_scale_codec::{Decode, Encode}; use crate::{prelude::ValidQuery, WorldStateView}; +/// Represents lazy evaluated query output +pub trait Lazy { + /// Type of the lazy evaluated query output + type Lazy<'a>; +} + +/// Lazily evaluated equivalent of [`Value`] +pub enum LazyValue<'a> { + /// Concrete computed [`Value`] + Value(Value), + /// Iterator over a set of [`Value`]s + Iter(Box + 'a>), +} + +impl Lazy for Value { + type Lazy<'a> = LazyValue<'a>; +} + +impl Lazy for Vec { + type Lazy<'a> = Box + 'a>; +} + +macro_rules! impl_lazy { + ( $($ident:ty),+ $(,)? ) => { $( + impl Lazy for $ident { + type Lazy<'a> = Self; + } )+ + }; +} +impl_lazy! { + bool, + NumericValue, + iroha_data_model::role::Role, + iroha_data_model::asset::Asset, + iroha_data_model::asset::AssetDefinition, + iroha_data_model::account::Account, + iroha_data_model::domain::Domain, + iroha_data_model::block::BlockHeader, + iroha_data_model::query::MetadataValue, + iroha_data_model::query::TransactionQueryResult, + iroha_data_model::trigger::Trigger, +} + /// Query Request statefully validated on the Iroha node side. #[derive(Debug, Decode, Encode)] pub struct ValidQueryRequest(VersionedSignedQuery); @@ -40,73 +83,93 @@ impl ValidQueryRequest { wsv.validator_view() .clone() .validate(wsv, query.authority(), query.query().clone())?; - Ok(ValidQueryRequest(query)) + Ok(Self(query)) } /// Execute contained query on the [`WorldStateView`]. /// /// # Errors /// Forwards `self.query.execute` error. - #[inline] - pub fn execute(&self, wsv: &WorldStateView) -> Result { - Ok(self.0.filter().filter(self.0.query().execute(wsv)?)) + pub fn execute<'wsv>(&'wsv self, wsv: &'wsv WorldStateView) -> Result, Error> { + let value = self.0.query().execute(wsv)?; + + Ok(if let LazyValue::Iter(iter) = value { + LazyValue::Iter(Box::new(iter.filter(|val| self.0.filter().applies(val)))) + } else { + value + }) + + // We're not handling the LimitedMetadata case, because + // the predicate when applied to it is ambiguous. We could + // pattern match on that case, but we should assume that + // metadata (since it's limited) isn't going to be too + // difficult to filter client-side. I actually think that + // Metadata should be restricted in what types it can + // contain. } } impl ValidQuery for QueryBox { - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { iroha_logger::debug!(query=%self, "Executing"); macro_rules! match_all { - ( $( $query:ident ),+ $(,)? ) => { + ( non_iter: {$( $non_iter_query:ident ),+ $(,)?} $( $query:ident, )+ ) => { match self { $( - QueryBox::$query(query) => query.execute(wsv).map(Into::into), )+ + QueryBox::$non_iter_query(query) => query.execute(wsv).map(Value::from).map(LazyValue::Value), )+ $( + QueryBox::$query(query) => query.execute(wsv).map(|i| i.map(Value::from)).map(|iter| LazyValue::Iter(Box::new(iter))), )+ } }; } match_all! { + non_iter: { + FindAccountById, + FindAssetById, + FindAssetDefinitionById, + FindAssetQuantityById, + FindTotalAssetQuantityByAssetDefinitionId, + IsAssetDefinitionOwner, + FindDomainById, + FindBlockHeaderByHash, + FindTransactionByHash, + DoesAccountHavePermissionToken, + FindTriggerById, + FindRoleByRoleId, + FindDomainKeyValueByIdAndKey, + FindAssetKeyValueByIdAndKey, + FindAccountKeyValueByIdAndKey, + FindAssetDefinitionKeyValueByIdAndKey, + FindTriggerKeyValueByIdAndKey, + } + FindAllAccounts, - FindAccountById, FindAccountsByName, FindAccountsByDomainId, FindAccountsWithAsset, FindAllAssets, FindAllAssetsDefinitions, - FindAssetById, - FindAssetDefinitionById, FindAssetsByName, FindAssetsByAccountId, FindAssetsByAssetDefinitionId, FindAssetsByDomainId, FindAssetsByDomainIdAndAssetDefinitionId, - FindAssetQuantityById, - FindTotalAssetQuantityByAssetDefinitionId, - IsAssetDefinitionOwner, FindAllDomains, - FindDomainById, - FindDomainKeyValueByIdAndKey, FindAllPeers, - FindAssetKeyValueByIdAndKey, - FindAccountKeyValueByIdAndKey, FindAllBlocks, FindAllBlockHeaders, - FindBlockHeaderByHash, FindAllTransactions, FindTransactionsByAccountId, - FindTransactionByHash, FindPermissionTokensByAccountId, FindAllPermissionTokenDefinitions, - DoesAccountHavePermissionToken, - FindAssetDefinitionKeyValueByIdAndKey, FindAllActiveTriggerIds, - FindTriggerById, - FindTriggerKeyValueByIdAndKey, FindTriggersByDomainId, FindAllRoles, FindAllRoleIds, FindRolesByAccountId, - FindRoleByRoleId, FindAllParameters, } } @@ -275,8 +338,8 @@ mod tests { let bytes = FindAssetKeyValueByIdAndKey::new(asset_id, Name::from_str("Bytes")?).execute(&wsv)?; assert_eq!( - bytes, - Value::Vec(vec![1_u32.to_value(), 2_u32.to_value(), 3_u32.to_value()]) + Value::Vec(vec![1_u32.to_value(), 2_u32.to_value(), 3_u32.to_value()]), + bytes.into(), ); Ok(()) } @@ -289,8 +352,8 @@ mod tests { let bytes = FindAccountKeyValueByIdAndKey::new(ALICE_ID.clone(), Name::from_str("Bytes")?) .execute(&wsv)?; assert_eq!( - bytes, - Value::Vec(vec![1_u32.to_value(), 2_u32.to_value(), 3_u32.to_value()]) + Value::Vec(vec![1_u32.to_value(), 2_u32.to_value(), 3_u32.to_value()]), + bytes.into(), ); Ok(()) } @@ -300,8 +363,7 @@ mod tests { let num_blocks = 100; let wsv = wsv_with_test_blocks_and_transactions(num_blocks, 1, 1)?; - - let blocks = FindAllBlocks.execute(&wsv)?; + let blocks = FindAllBlocks.execute(&wsv)?.collect::>(); assert_eq!(blocks.len() as u64, num_blocks); assert!(blocks.windows(2).all(|wnd| wnd[0] >= wnd[1])); @@ -314,8 +376,7 @@ mod tests { let num_blocks = 100; let wsv = wsv_with_test_blocks_and_transactions(num_blocks, 1, 1)?; - - let block_headers = FindAllBlockHeaders.execute(&wsv)?; + let block_headers = FindAllBlockHeaders.execute(&wsv)?.collect::>(); assert_eq!(block_headers.len() as u64, num_blocks); assert!(block_headers.windows(2).all(|wnd| wnd[0] >= wnd[1])); @@ -347,8 +408,7 @@ mod tests { let num_blocks = 100; let wsv = wsv_with_test_blocks_and_transactions(num_blocks, 1, 1)?; - - let txs = FindAllTransactions.execute(&wsv)?; + let txs = FindAllTransactions.execute(&wsv)?.collect::>(); assert_eq!(txs.len() as u64, num_blocks * 2); assert_eq!( @@ -444,8 +504,8 @@ mod tests { let key = Name::from_str("Bytes")?; let bytes = FindDomainKeyValueByIdAndKey::new(domain_id, key).execute(&wsv)?; assert_eq!( - bytes, - Value::Vec(vec![1_u32.to_value(), 2_u32.to_value(), 3_u32.to_value()]) + Value::Vec(vec![1_u32.to_value(), 2_u32.to_value(), 3_u32.to_value()]), + bytes.into(), ); Ok(()) } diff --git a/core/src/smartcontracts/isi/triggers/mod.rs b/core/src/smartcontracts/isi/triggers/mod.rs index 384a4ed076f..6e2e9613ca9 100644 --- a/core/src/smartcontracts/isi/triggers/mod.rs +++ b/core/src/smartcontracts/isi/triggers/mod.rs @@ -198,18 +198,24 @@ pub mod query { use iroha_data_model::query::error::QueryExecutionFail as Error; use super::*; - use crate::prelude::*; + use crate::{prelude::*, smartcontracts::query::Lazy}; impl ValidQuery for FindAllActiveTriggerIds { #[metrics(+"find_all_active_triggers")] - fn execute(&self, wsv: &WorldStateView) -> Result { - Ok(wsv.triggers().ids()) + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { + Ok(Box::new(wsv.triggers().ids().cloned())) } } impl ValidQuery for FindTriggerById { #[metrics(+"find_trigger_by_id")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let id = wsv .evaluate(&self.id) .map_err(|e| Error::Evaluate(format!("Failed to evaluate trigger id. {e}")))?; @@ -237,7 +243,10 @@ pub mod query { impl ValidQuery for FindTriggerKeyValueByIdAndKey { #[metrics(+"find_trigger_key_value_by_id_and_key")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let id = wsv .evaluate(&self.id) .map_err(|e| Error::Evaluate(format!("Failed to evaluate trigger id. {e}")))?; @@ -250,23 +259,27 @@ pub mod query { action .metadata() .get(&key) - .map(Clone::clone) + .cloned() .ok_or_else(|| FindError::MetadataKey(key.clone()).into()) }) .ok_or_else(|| Error::Find(Box::new(FindError::Trigger(id))))? + .map(Into::into) } } impl ValidQuery for FindTriggersByDomainId { #[metrics(+"find_triggers_by_domain_id")] - fn execute(&self, wsv: &WorldStateView) -> eyre::Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> eyre::Result<::Lazy<'wsv>, Error> { let domain_id = wsv .evaluate(&self.domain_id) .map_err(|e| Error::Evaluate(format!("Failed to evaluate domain id. {e}")))?; - let triggers = wsv - .triggers() - .inspect_by_domain_id(&domain_id, |trigger_id, action| { + Ok(Box::new(wsv.triggers().inspect_by_domain_id( + &domain_id, + |trigger_id, action| { let Action { executable: loaded_executable, repeats, @@ -274,14 +287,14 @@ pub mod query { filter, metadata, } = action.clone_and_box(); + Trigger::new( trigger_id.clone(), Action::new(loaded_executable, repeats, authority, filter) .with_metadata(metadata), ) - }); - - Ok(triggers) + }, + ))) } } } diff --git a/core/src/smartcontracts/isi/triggers/set.rs b/core/src/smartcontracts/isi/triggers/set.rs index f30115715a1..ba0a26c50ea 100644 --- a/core/src/smartcontracts/isi/triggers/set.rs +++ b/core/src/smartcontracts/isi/triggers/set.rs @@ -202,14 +202,18 @@ impl Set { /// Get all contained trigger ids without a particular order #[inline] - pub fn ids(&self) -> Vec { - self.ids.keys().cloned().collect() + pub fn ids(&self) -> impl ExactSizeIterator { + self.ids.keys() } /// Apply `f` to triggers that belong to the given [`DomainId`] /// /// Return an empty list if [`Set`] doesn't contain any triggers belonging to [`DomainId`]. - pub fn inspect_by_domain_id(&self, domain_id: &DomainId, f: F) -> Vec + pub fn inspect_by_domain_id( + &self, + domain_id: &DomainId, + f: F, + ) -> impl ExactSizeIterator where F: Fn(&TriggerId, &dyn ActionTrait) -> R, { @@ -251,7 +255,8 @@ impl Set { Some(result) }) - .collect() + .collect::>() + .into_iter() } /// Apply `f` to the trigger identified by `id`. diff --git a/core/src/smartcontracts/isi/tx.rs b/core/src/smartcontracts/isi/tx.rs index d1531220d8d..2c87fa781cb 100644 --- a/core/src/smartcontracts/isi/tx.rs +++ b/core/src/smartcontracts/isi/tx.rs @@ -1,48 +1,131 @@ //! Query module provides [`Query`] Transaction related implementations. +use std::sync::Arc; + use eyre::{Result, WrapErr}; +use iroha_crypto::HashOf; use iroha_data_model::{ + block::VersionedCommittedBlock, evaluate::ExpressionEvaluator, prelude::*, - query::error::{FindError, QueryExecutionFail}, + query::{ + error::{FindError, QueryExecutionFail}, + TransactionQueryResult, + }, + transaction::TransactionValue, }; use iroha_telemetry::metrics; -use super::*; +use super::{query::Lazy, *}; + +pub(crate) struct BlockTransactionIter(Arc, usize); +pub(crate) struct BlockTransactionRef(Arc, usize); + +impl BlockTransactionIter { + fn new(block: Arc) -> Self { + Self(block, 0) + } +} + +impl Iterator for BlockTransactionIter { + type Item = BlockTransactionRef; + + fn next(&mut self) -> Option { + let block = self.0.as_v1(); + + if self.1 < block.transactions.len() { + return Some(BlockTransactionRef(Arc::clone(&self.0), self.1)); + } + + None + } +} + +impl BlockTransactionRef { + fn block_hash(&self) -> HashOf { + self.0.hash() + } + + fn authority(&self) -> &AccountId { + let block = self.0.as_v1(); + + &block.transactions[self.1].payload().authority + } + fn value(&self) -> TransactionValue { + self.0.as_v1().transactions[self.1].clone() + } +} impl ValidQuery for FindAllTransactions { #[metrics(+"find_all_transactions")] - fn execute(&self, wsv: &WorldStateView) -> Result { - let mut txs = wsv.transaction_values(); - txs.reverse(); - Ok(txs) + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, QueryExecutionFail> { + Ok(Box::new( + wsv.all_blocks() + .flat_map(BlockTransactionIter::new) + .map(|tx| TransactionQueryResult { + block_hash: tx.block_hash(), + transaction: tx.value(), + }), + )) } } impl ValidQuery for FindTransactionsByAccountId { #[metrics(+"find_transactions_by_account_id")] - fn execute(&self, wsv: &WorldStateView) -> Result { - let id = wsv + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, QueryExecutionFail> { + let account_id = wsv .evaluate(&self.account_id) .wrap_err("Failed to get account id") .map_err(|e| QueryExecutionFail::Evaluate(e.to_string()))?; - iroha_logger::trace!(%id); - Ok(wsv.transactions_values_by_account_id(&id)) + + Ok(Box::new( + wsv.all_blocks() + .flat_map(BlockTransactionIter::new) + .filter(move |tx| *tx.authority() == account_id) + .map(|tx| TransactionQueryResult { + block_hash: tx.block_hash(), + transaction: tx.value(), + }), + )) } } impl ValidQuery for FindTransactionByHash { #[metrics(+"find_transaction_by_hash")] - fn execute(&self, wsv: &WorldStateView) -> Result { - let hash = wsv + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, QueryExecutionFail> { + let tx_hash = wsv .evaluate(&self.hash) .wrap_err("Failed to get hash") .map_err(|e| QueryExecutionFail::Evaluate(e.to_string()))?; - iroha_logger::trace!(%hash); - if !wsv.has_transaction(hash) { - return Err(FindError::Transaction(hash).into()); + iroha_logger::trace!(%tx_hash); + if !wsv.has_transaction(tx_hash) { + return Err(FindError::Transaction(tx_hash).into()); }; - wsv.transaction_value_by_hash(&hash) - .ok_or_else(|| FindError::Transaction(hash).into()) + let block = wsv + .block_with_tx(&tx_hash) + .ok_or_else(|| FindError::Transaction(tx_hash))?; + + let block_hash = block.hash(); + let block = block.as_v1(); + + block + .transactions + .iter() + .find(|transaction| transaction.value.hash() == tx_hash) + .cloned() + .map(|transaction| TransactionQueryResult { + block_hash, + transaction, + }) + .ok_or_else(|| FindError::Transaction(tx_hash).into()) } } diff --git a/core/src/smartcontracts/isi/world.rs b/core/src/smartcontracts/isi/world.rs index a4909bd0e48..f81fe0f44f1 100644 --- a/core/src/smartcontracts/isi/world.rs +++ b/core/src/smartcontracts/isi/world.rs @@ -22,6 +22,7 @@ pub mod isi { use eyre::Result; use iroha_data_model::{ isi::error::{InvalidParameterError, RepetitionError}, + permission::PermissionTokenId, prelude::*, query::error::FindError, }; @@ -264,21 +265,24 @@ pub mod isi { /// Remove all tokens with specified definition id from all accounts in all domains fn remove_token_from_accounts( wsv: &mut WorldStateView, - target_definition_id: &::Id, + target_definition_id: &PermissionTokenId, ) -> Result<(), Error> { let mut accounts_with_token = std::collections::HashMap::new(); - for domain in wsv.domains().values() { - let account_ids = domain.accounts.values().map(|account| { - ( - account.id().clone(), - wsv.account_inherent_permission_tokens(account) - .filter(|token| token.definition_id == *target_definition_id) - .collect::>(), - ) - }); - - accounts_with_token.extend(account_ids); + let account_ids = wsv + .domains() + .values() + .flat_map(|domain| domain.accounts.values()) + .map(|account| &account.id); + + for account_id in account_ids { + accounts_with_token.insert( + account_id.clone(), + wsv.account_inherent_permission_tokens(account_id)? + .filter(|token| token.definition_id == *target_definition_id) + .cloned() + .collect::>(), + ); } let mut events = Vec::new(); @@ -417,31 +421,40 @@ pub mod query { }; use super::*; + use crate::smartcontracts::query::Lazy; impl ValidQuery for FindAllRoles { #[metrics(+"find_all_roles")] - fn execute(&self, wsv: &WorldStateView) -> Result { - Ok(wsv.world.roles.values().cloned().collect()) + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { + Ok(Box::new(wsv.world.roles.values().cloned())) } } impl ValidQuery for FindAllRoleIds { #[metrics(+"find_all_role_ids")] - fn execute(&self, wsv: &WorldStateView) -> Result { - Ok(wsv + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { + Ok(Box::new(wsv .world .roles .values() // To me, this should probably be a method, not a field. .map(Role::id) - .cloned() - .collect()) + .cloned())) } } impl ValidQuery for FindRoleByRoleId { #[metrics(+"find_role_by_role_id")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let role_id = wsv .evaluate(&self.id) .map_err(|e| Error::Evaluate(e.to_string()))?; @@ -456,40 +469,49 @@ pub mod query { impl ValidQuery for FindAllPeers { #[metrics("find_all_peers")] - fn execute(&self, wsv: &WorldStateView) -> Result { - Ok(wsv.peers()) + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { + Ok(Box::new(wsv.peers().cloned().map(Peer::new))) } } impl ValidQuery for FindAllPermissionTokenDefinitions { #[metrics("find_all_token_ids")] - fn execute(&self, wsv: &WorldStateView) -> Result { - Ok(wsv - .permission_token_definitions() - .values() - .cloned() - .collect()) + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { + Ok(Box::new( + wsv.permission_token_definitions().values().cloned(), + )) } } impl ValidQuery for FindAllParameters { #[metrics("find_all_parameters")] - fn execute(&self, wsv: &WorldStateView) -> Result { - Ok(wsv.parameters()) + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { + Ok(Box::new(wsv.parameters().cloned())) } } impl ValidQuery for DoesAccountHavePermissionToken { #[metrics("does_account_have_permission")] - fn execute(&self, wsv: &WorldStateView) -> Result { + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, Error> { let authority = wsv .evaluate(&self.account_id) .map_err(|e| Error::Evaluate(e.to_string()))?; - wsv.map_account(&authority, |account| { - wsv.account_permission_tokens(account) - .contains(&self.permission_token) - }) + Ok(wsv + .account_permission_tokens(&authority)? + .any(|permission_token| *permission_token == self.permission_token)) } } } diff --git a/core/src/smartcontracts/mod.rs b/core/src/smartcontracts/mod.rs index 6580b1c766a..ebab9f86afe 100644 --- a/core/src/smartcontracts/mod.rs +++ b/core/src/smartcontracts/mod.rs @@ -15,6 +15,7 @@ use iroha_data_model::{ }; pub use isi::*; +use self::query::{Lazy, LazyValue}; use crate::wsv::WorldStateView; /// Trait implementations should provide actions to apply changes on [`WorldStateView`]. @@ -27,7 +28,10 @@ pub trait Execute { } /// This trait should be implemented for all Iroha Queries. -pub trait ValidQuery: Query { +pub trait ValidQuery: Query +where + Self::Output: Lazy, +{ /// Execute query on the [`WorldStateView`]. /// Should not mutate [`WorldStateView`]! /// @@ -35,7 +39,10 @@ pub trait ValidQuery: Query { /// /// # Errors /// Concrete to each implementer - fn execute(&self, wsv: &WorldStateView) -> Result; + fn execute<'wsv>( + &self, + wsv: &'wsv WorldStateView, + ) -> Result<::Lazy<'wsv>, QueryExecutionFail>; } impl ExpressionEvaluator for WorldStateView { @@ -65,7 +72,15 @@ impl<'a> Context<'a> { impl iroha_data_model::evaluate::Context for Context<'_> { fn query(&self, query: &QueryBox) -> Result { - query.execute(self.wsv).map_err(Into::into) + query + .execute(self.wsv) + .map(|value| match value { + LazyValue::Value(value) => value, + // NOTE: This will only be executed from the validator/executor. + // Handing out references to the host system is a security risk + LazyValue::Iter(iter) => Value::Vec(iter.collect()), + }) + .map_err(Into::into) } fn get(&self, name: &Name) -> Option<&Value> { diff --git a/core/src/smartcontracts/wasm.rs b/core/src/smartcontracts/wasm.rs index f65644f6e1a..92faba0dbc2 100644 --- a/core/src/smartcontracts/wasm.rs +++ b/core/src/smartcontracts/wasm.rs @@ -25,6 +25,7 @@ use wasmtime::{ Caller, Config, Engine, Linker, Module, Store, StoreLimits, StoreLimitsBuilder, Trap, TypedFunc, }; +use super::query::LazyValue; use crate::{ smartcontracts::{Execute, ValidQuery as _}, wsv::WorldStateView, @@ -692,7 +693,14 @@ impl<'wrld, S: state::GetCommon<'wrld>, R: DefaultExecute> ExecuteOperations<'wr .clone() // Cloning validator is a cheap operation .validate(wsv, &common_state.authority, query.clone())?; - query.execute(wsv).map_err(Into::into) + query + .execute(wsv) + .map_err(Into::into) + .map(|lazy_value| match lazy_value { + LazyValue::Value(value) => value, + // NOTE: Returning references to the host system is a security risk + LazyValue::Iter(iter) => Value::Vec(iter.collect::>()), + }) } /// Default implementation of [`execute_instruction()`] @@ -868,7 +876,14 @@ impl<'wrld> ExecuteOperations<'wrld, state::Validator<'wrld>> for Runtime Result { iroha_logger::debug!(%query, "Executing as validator"); - query.execute(state.common_mut().wsv).map_err(Into::into) + query + .execute(state.common_mut().wsv) + .map_err(Into::into) + .map(|lazy_value| match lazy_value { + LazyValue::Value(value) => value, + // NOTE: Returning references to the host system is a security risk + LazyValue::Iter(iter) => Value::Vec(iter.collect::>()), + }) } #[codec::wrap] diff --git a/core/src/sumeragi/main_loop.rs b/core/src/sumeragi/main_loop.rs index 4798e5c88d5..ec2835fb9d8 100644 --- a/core/src/sumeragi/main_loop.rs +++ b/core/src/sumeragi/main_loop.rs @@ -2,7 +2,7 @@ #![allow(clippy::cognitive_complexity)] use std::sync::mpsc; -use iroha_data_model::{block::*, transaction::error::TransactionRejectionReason}; +use iroha_data_model::{block::*, peer::PeerId, transaction::error::TransactionRejectionReason}; use iroha_p2p::UpdateTopology; use tracing::{span, Level}; @@ -329,17 +329,11 @@ impl Sumeragi { self.update_state::(block, new_wsv); } - fn update_topology(&mut self, committed_block: &VersionedCommittedBlock) { - let mut topology = Topology::new( - committed_block - .as_v1() - .header() - .committed_with_topology - .clone(), - ); + fn update_topology(&mut self, block_signees: &[PublicKey], peers: Vec) { + let mut topology = Topology::new(peers); topology.update_topology( - committed_block, + block_signees, self.wsv.peers_ids().iter().cloned().collect(), ); @@ -352,7 +346,7 @@ impl Sumeragi { block: impl Into, mut new_wsv: WorldStateView, ) { - let committed_block = Arc::new(block.into()); + let committed_block = block.into(); info!( addr=%self.peer_id.address, @@ -365,7 +359,7 @@ impl Sumeragi { Strategy::before_update_hook(self); new_wsv - .apply_without_execution(committed_block.as_ref()) + .apply_without_execution(&committed_block) .expect("Failed to apply block on WSV. Bailing."); self.wsv = new_wsv; @@ -375,10 +369,23 @@ impl Sumeragi { // Parameters are updated before updating public copy of sumeragi self.update_params(); + let events: Vec<_> = (&committed_block).into(); + let topology = committed_block + .as_v1() + .header() + .committed_with_topology + .clone(); + let block_signees = committed_block + .signatures() + .map(|s| s.public_key()) + .cloned() + .collect::>(); + // https://github.com/hyperledger/iroha/issues/3396 // Kura should store the block only upon successful application to the internal WSV to avoid storing a corrupted block. // Public-facing WSV update should happen after that and be followed by `BlockCommited` event to prevent client access to uncommitted data. - Strategy::kura_store_block(&self.kura, Arc::clone(&committed_block)); + // TODO: Redundant clone + Strategy::kura_store_block(&self.kura, committed_block); // Update WSV copy that is public facing self.public_wsv_sender @@ -386,9 +393,9 @@ impl Sumeragi { // This sends "Block committed" event, so it should be done // AFTER public facing WSV update - self.send_events(committed_block.as_ref()); + self.send_events(events); - self.update_topology(committed_block.as_ref()); + self.update_topology(&block_signees, topology); self.cache_transaction() } @@ -1104,7 +1111,7 @@ trait ApplyBlockStrategy { fn before_update_hook(sumeragi: &mut Sumeragi); /// Operation to invoke in kura to store block. - fn kura_store_block(kura: &Kura, block: Arc); + fn kura_store_block(kura: &Kura, block: VersionedCommittedBlock); } /// Commit new block strategy. Used during normal consensus rounds. @@ -1121,7 +1128,7 @@ impl ApplyBlockStrategy for NewBlockStrategy { } #[inline] - fn kura_store_block(kura: &Kura, block: Arc) { + fn kura_store_block(kura: &Kura, block: VersionedCommittedBlock) { kura.store_block(block) } } @@ -1138,7 +1145,7 @@ impl ApplyBlockStrategy for ReplaceTopBlockStrategy { } #[inline] - fn kura_store_block(kura: &Kura, block: Arc) { + fn kura_store_block(kura: &Kura, block: VersionedCommittedBlock) { kura.replace_top_block(block) } } diff --git a/core/src/sumeragi/network_topology.rs b/core/src/sumeragi/network_topology.rs index d7a45eda694..9ed33183879 100644 --- a/core/src/sumeragi/network_topology.rs +++ b/core/src/sumeragi/network_topology.rs @@ -184,14 +184,8 @@ impl Topology { } /// Perform sequence of actions after block committed. - pub fn update_topology(&mut self, block: &VersionedCommittedBlock, new_peers: HashSet) { - self.lift_up_peers( - &block - .signatures() - .map(|s| s.public_key()) - .cloned() - .collect::>(), - ); + pub fn update_topology(&mut self, block_signees: &[PublicKey], new_peers: HashSet) { + self.lift_up_peers(block_signees); self.rotate_set_a(); self.update_peer_list(new_peers); } @@ -203,8 +197,13 @@ impl Topology { new_peers: HashSet, ) -> Self { let mut topology = Topology::new(block.as_v1().header().committed_with_topology.clone()); + let block_signees = block + .signatures() + .map(|s| s.public_key()) + .cloned() + .collect::>(); - topology.update_topology(block, new_peers); + topology.update_topology(&block_signees, new_peers); // Rotate all once for every view_change topology.rotate_all_n(view_change_index); diff --git a/core/src/wsv.rs b/core/src/wsv.rs index bb7b9e23ff6..ff451240c61 100644 --- a/core/src/wsv.rs +++ b/core/src/wsv.rs @@ -10,7 +10,6 @@ use std::{ borrow::Borrow, collections::{BTreeSet, HashMap}, - convert::Infallible, fmt::Debug, sync::Arc, time::Duration, @@ -156,39 +155,58 @@ impl WorldStateView { /// /// # Errors /// Fails if there is no domain or account - pub fn account_assets(&self, id: &AccountId) -> Result, QueryExecutionFail> { - self.map_account(id, |account| account.assets.values().cloned().collect()) + pub fn account_assets( + &self, + id: &AccountId, + ) -> Result + '_, QueryExecutionFail> { + self.map_account(id, |account| account.assets.values().cloned()) } /// Return a set of all permission tokens granted to this account. - pub fn account_permission_tokens(&self, account: &Account) -> BTreeSet { - let mut tokens: BTreeSet = - self.account_inherent_permission_tokens(account).collect(); + /// + /// # Errors + /// + /// - if `account_id` is not found in `self` + pub fn account_permission_tokens( + &self, + account_id: &AccountId, + ) -> Result, FindError> { + let account = self.account(account_id)?; + + let mut tokens = self + .account_inherent_permission_tokens(account_id)? + .collect::>(); + for role_id in &account.roles { if let Some(role) = self.world.roles.get(role_id) { - tokens.append(&mut role.permissions.clone()); + tokens.extend(role.permissions.iter()); } } - tokens + + Ok(tokens.into_iter()) } /// Return a set of permission tokens granted to this account not as part of any role. + /// + /// # Errors + /// + /// - `account_id` is not found in `self.world`. pub fn account_inherent_permission_tokens( &self, - account: &Account, - ) -> impl ExactSizeIterator { + account_id: &AccountId, + ) -> Result, FindError> { self.world .account_permission_tokens - .get(&account.id) - .map_or_else(Default::default, Clone::clone) - .into_iter() + .get(account_id) + .ok_or_else(|| FindError::Account(account_id.clone())) + .map(std::collections::BTreeSet::iter) } /// Return `true` if [`Account`] contains a permission token not associated with any role. #[inline] pub fn account_contains_inherent_permission( &self, - account: &::Id, + account: &AccountId, token: &PermissionToken, ) -> bool { self.world @@ -200,11 +218,7 @@ impl WorldStateView { /// Add [`permission`](PermissionToken) to the [`Account`] if the account does not have this permission yet. /// /// Return a Boolean value indicating whether or not the [`Account`] already had this permission. - pub fn add_account_permission( - &mut self, - account: &::Id, - token: PermissionToken, - ) -> bool { + pub fn add_account_permission(&mut self, account: &AccountId, token: PermissionToken) -> bool { // `match` here instead of `map_or_else` to avoid cloning token into each closure match self.world.account_permission_tokens.get_mut(account) { None => { @@ -227,7 +241,7 @@ impl WorldStateView { /// Return a Boolean value indicating whether the [`Account`] had this permission. pub fn remove_account_permission( &mut self, - account: &::Id, + account: &AccountId, token: &PermissionToken, ) -> bool { self.world @@ -333,6 +347,7 @@ impl WorldStateView { /// you likely have data corruption. /// - If trigger execution fails /// - If timestamp conversion to `u64` fails + #[cfg(debug_assertions)] #[iroha_logger::log(skip_all, fields(block_height))] pub fn apply(&mut self, block: &VersionedCommittedBlock) -> Result<()> { self.execute_transactions(block.as_v1())?; @@ -358,7 +373,7 @@ impl WorldStateView { block .transactions .iter() - .map(|tx| &tx.tx) + .map(|tx| &tx.value) .map(VersionedSignedTransaction::hash) .for_each(|tx_hash| { self.transactions.insert(tx_hash, block_height); @@ -467,7 +482,7 @@ impl WorldStateView { /// - No such [`Asset`] /// - The [`Account`] with which the [`Asset`] is associated doesn't exist. /// - The [`Domain`] with which the [`Account`] is associated doesn't exist. - pub fn asset(&self, id: &::Id) -> Result { + pub fn asset(&self, id: &AssetId) -> Result { self.map_account( &id.account_id, |account| -> Result { @@ -487,7 +502,7 @@ impl WorldStateView { #[allow(clippy::missing_panics_doc)] pub fn asset_or_insert( &mut self, - id: &::Id, + id: &AssetId, default_asset_value: impl Into, ) -> Result { if let Ok(asset) = self.asset(id) { @@ -564,7 +579,7 @@ impl WorldStateView { /// /// # Errors /// Fails if there is no domain - pub fn domain(&self, id: &::Id) -> Result<&Domain, FindError> { + pub fn domain<'wsv>(&'wsv self, id: &DomainId) -> Result<&'wsv Domain, FindError> { let domain = self .world .domains @@ -577,10 +592,7 @@ impl WorldStateView { /// /// # Errors /// Fails if there is no domain - pub fn domain_mut( - &mut self, - id: &::Id, - ) -> Result<&mut Domain, FindError> { + pub fn domain_mut(&mut self, id: &DomainId) -> Result<&mut Domain, FindError> { let domain = self .world .domains @@ -600,16 +612,13 @@ impl WorldStateView { /// # Errors /// Fails if there is no domain #[allow(clippy::panic_in_result_fn)] - pub fn map_domain( - &self, - id: &::Id, - f: impl FnOnce(&Domain) -> Result, + pub fn map_domain<'wsv, T>( + &'wsv self, + id: &DomainId, + f: impl FnOnce(&'wsv Domain) -> T, ) -> Result { let domain = self.domain(id)?; - let value = f(domain).map_or_else( - |_infallible| unreachable!("Returning `Infallible` should not be possible"), - |value| value, - ); + let value = f(domain); Ok(value) } @@ -693,10 +702,10 @@ impl WorldStateView { /// /// # Errors /// Fails if there is no domain or account - pub fn map_account( - &self, + pub fn map_account<'wsv, T>( + &'wsv self, id: &AccountId, - f: impl FnOnce(&Account) -> T, + f: impl FnOnce(&'wsv Account) -> T, ) -> Result { let domain = self.domain(&id.domain_id)?; let account = domain @@ -706,6 +715,15 @@ impl WorldStateView { Ok(f(account)) } + fn account(&self, id: &AccountId) -> Result<&Account, FindError> { + self.domain(&id.domain_id).and_then(|domain| { + domain + .accounts + .get(id) + .ok_or_else(|| FindError::Account(id.clone())) + }) + } + /// Get mutable reference to [`Account`] /// /// # Errors @@ -748,25 +766,14 @@ impl WorldStateView { }) } - /// Get all `PeerId`s without an ability to modify them. - pub fn peers(&self) -> Vec { - let mut vec = self - .world - .trusted_peers_ids - .iter() - .map(|peer| Peer::new((*peer).clone())) - .collect::>(); - vec.sort(); - vec + /// Get an immutable iterator over the [`PeerId`]s. + pub fn peers(&self) -> impl ExactSizeIterator { + self.world.trusted_peers_ids.iter() } /// Get all `Parameter`s registered in the world. - pub fn parameters(&self) -> Vec { - self.world - .parameters - .iter() - .cloned() - .collect::>() + pub fn parameters(&self) -> impl ExactSizeIterator { + self.world.parameters.iter() } /// Query parameter and convert it to a proper type @@ -792,7 +799,7 @@ impl WorldStateView { /// - Asset definition entry not found pub fn asset_definition( &self, - asset_id: &::Id, + asset_id: &AssetDefinitionId, ) -> Result { self.domain(&asset_id.domain_id)? .asset_definitions @@ -807,7 +814,7 @@ impl WorldStateView { /// - Asset definition not found pub fn asset_total_amount( &self, - definition_id: &::Id, + definition_id: &AssetDefinitionId, ) -> Result { self.domain(&definition_id.domain_id)? .asset_total_quantities @@ -823,7 +830,7 @@ impl WorldStateView { /// - Overflow pub fn increase_asset_total_amount( &mut self, - definition_id: &::Id, + definition_id: &AssetDefinitionId, increment: I, ) -> Result<(), Error> where @@ -862,7 +869,7 @@ impl WorldStateView { /// - Not enough quantity pub fn decrease_asset_total_amount( &mut self, - definition_id: &::Id, + definition_id: &AssetDefinitionId, decrement: I, ) -> Result<(), Error> where @@ -894,72 +901,13 @@ impl WorldStateView { Ok(()) } - /// Get all transactions - pub fn transaction_values(&self) -> Vec { - let mut txs = self - .all_blocks() - .flat_map(|block| { - let block = block.as_v1(); - block - .transactions - .iter() - .cloned() - .map(|versioned_tx| TransactionQueryResult { - transaction: versioned_tx, - block_hash: block.hash(), - }) - .collect::>() - }) - .collect::>(); - txs.sort(); - txs - } - /// Find a [`VersionedSignedTransaction`] by hash. - pub fn transaction_value_by_hash( + pub fn block_with_tx( &self, hash: &HashOf, - ) -> Option { + ) -> Option> { let height = *self.transactions.get(hash)?; - let block = self.kura.get_block_by_height(height)?; - let block_hash = block.as_v1().hash(); - block - .as_v1() - .transactions - .iter() - .find(|e| e.tx.hash() == *hash) - .cloned() - .map(|tx| TransactionQueryResult { - transaction: tx, - block_hash, - }) - } - - /// Get committed and rejected transaction of the account. - pub fn transactions_values_by_account_id( - &self, - account_id: &AccountId, - ) -> Vec { - let mut transactions = self - .all_blocks() - .flat_map(|block_entry| { - let block = block_entry.as_v1(); - let block_hash = block.hash(); - - block - .transactions - .iter() - .filter(|tx| &tx.payload().authority == account_id) - .cloned() - .map(|tx| TransactionQueryResult { - transaction: tx, - block_hash, - }) - .collect::>() - }) - .collect::>(); - transactions.sort(); - transactions + self.kura.get_block_by_height(height) } /// Get an immutable view of the `World`. diff --git a/core/test_network/src/lib.rs b/core/test_network/src/lib.rs index 00c61b24fdb..161a15c489c 100644 --- a/core/test_network/src/lib.rs +++ b/core/test_network/src/lib.rs @@ -20,7 +20,7 @@ use iroha_config::{ sumeragi::Configuration as SumeragiConfiguration, torii::Configuration as ToriiConfiguration, }; -use iroha_core::prelude::*; +use iroha_core::{prelude::*, smartcontracts::query::Lazy}; use iroha_data_model::{peer::Peer as DataModelPeer, prelude::*}; use iroha_genesis::{GenesisNetwork, RawGenesisBlock}; use iroha_logger::{Configuration as LoggerConfiguration, InstrumentFutures}; @@ -717,7 +717,7 @@ pub trait TestClient: Sized { where R: ValidQuery + Into + Debug + Clone, >::Error: Into, - R::Output: Clone + Debug; + R::Output: Lazy + Clone + Debug; /// Submits instructions with polling /// @@ -732,7 +732,7 @@ pub trait TestClient: Sized { where R: ValidQuery + Into + Debug + Clone, >::Error: Into, - R::Output: Clone + Debug; + R::Output: Lazy + Clone + Debug; /// Polls request till predicate `f` is satisfied, with default period and max attempts. /// @@ -746,7 +746,7 @@ pub trait TestClient: Sized { where R: ValidQuery + Into + Debug + Clone, >::Error: Into, - R::Output: Clone + Debug; + R::Output: Lazy + Clone + Debug; /// Polls request till predicate `f` is satisfied with `period` and `max_attempts` supplied. /// @@ -762,7 +762,7 @@ pub trait TestClient: Sized { where R: ValidQuery + Into + Debug + Clone, >::Error: Into, - R::Output: Clone + Debug; + R::Output: Lazy + Clone + Debug; } impl TestRuntime for Runtime { @@ -857,7 +857,7 @@ impl TestClient for Client { where R: ValidQuery + Into + Debug + Clone, >::Error: Into, - R::Output: Clone + Debug, + R::Output: Lazy + Clone + Debug, { self.submit(instruction) .expect("Failed to submit instruction."); @@ -873,7 +873,7 @@ impl TestClient for Client { where R: ValidQuery + Into + Debug + Clone, >::Error: Into, - R::Output: Clone + Debug, + R::Output: Lazy + Clone + Debug, { self.submit_all(instructions) .expect("Failed to submit instruction."); @@ -890,7 +890,7 @@ impl TestClient for Client { where R: ValidQuery + Into + Debug + Clone, >::Error: Into, - R::Output: Clone + Debug, + R::Output: Lazy + Clone + Debug, { let mut query_result = None; for _ in 0..max_attempts { @@ -911,7 +911,7 @@ impl TestClient for Client { where R: ValidQuery + Into + Debug + Clone, >::Error: Into, - R::Output: Clone + Debug, + R::Output: Lazy + Clone + Debug, { self.poll_request_with_period(request, Configuration::pipeline_time() / 2, 10, f) } diff --git a/data_model/src/metadata.rs b/data_model/src/metadata.rs index ebc8d1438cc..42ac8e61fa6 100644 --- a/data_model/src/metadata.rs +++ b/data_model/src/metadata.rs @@ -32,10 +32,10 @@ pub mod model { Eq, PartialOrd, Ord, - Deserialize, - Serialize, Decode, Encode, + Deserialize, + Serialize, IntoSchema, )] #[display(fmt = "{max_len},{max_entry_byte_size}_ML")] @@ -82,10 +82,10 @@ pub mod model { Eq, PartialOrd, Ord, - Deserialize, - Serialize, Decode, Encode, + Deserialize, + Serialize, IntoSchema, )] #[cfg_attr(feature = "std", derive(thiserror::Error))] @@ -117,10 +117,10 @@ pub enum MetadataError { Eq, PartialOrd, Ord, - Deserialize, - Serialize, Decode, Encode, + Deserialize, + Serialize, IntoSchema, )] #[display(fmt = "Limits are {limits}, while the actual value is {actual}")] diff --git a/data_model/src/predicate.rs b/data_model/src/predicate.rs index 4c905f9e9dc..49f89440b7a 100644 --- a/data_model/src/predicate.rs +++ b/data_model/src/predicate.rs @@ -211,25 +211,6 @@ where /// Predicate combinator for predicates operating on `Value` pub type PredicateBox = GenericPredicateBox; -impl PredicateBox { - #[must_use] - #[inline] - /// Filter [`Value`] using `self`. - pub fn filter(&self, value: Value) -> Value { - match value { - Value::Vec(v) => Value::Vec(v.into_iter().filter(|val| self.applies(val)).collect()), - other => other, - // We're not handling the LimitedMetadata case, because - // the predicate when applied to it is ambiguous. We could - // pattern match on that case, but we should assume that - // metadata (since it's limited) isn't going to be too - // difficult to filter client-side. I actually think that - // Metadata should be restricted in what types it can - // contain. - } - } -} - impl Default for PredicateBox { fn default() -> Self { PredicateBox::Raw(value::ValuePredicate::Pass) diff --git a/data_model/src/query.rs b/data_model/src/query.rs index 9bd3fd1323c..7629bc36640 100644 --- a/data_model/src/query.rs +++ b/data_model/src/query.rs @@ -22,7 +22,7 @@ use self::{ }; use crate::{ account::Account, - block::CommittedBlock, + block::VersionedCommittedBlock, seal, transaction::{TransactionPayload, TransactionValue, VersionedSignedTransaction}, Identifiable, Value, @@ -164,7 +164,7 @@ pub mod model { FindAllParameters(FindAllParameters), } - /// `TransactionQueryResult` is used in `FindAllTransactions` query + /// Output of [`FindAllTransactions`] query #[derive( Debug, Clone, PartialEq, Eq, Getters, Decode, Encode, Deserialize, Serialize, IntoSchema, )] @@ -174,7 +174,22 @@ pub mod model { /// Transaction pub transaction: TransactionValue, /// The hash of the block to which `tx` belongs to - pub block_hash: HashOf, + pub block_hash: HashOf, + } +} + +/// Type returned from [`Metadata`] queries +pub struct MetadataValue(Value); + +impl From for Value { + fn from(source: MetadataValue) -> Self { + source.0 + } +} + +impl From for MetadataValue { + fn from(source: Value) -> Self { + Self(source) } } @@ -384,6 +399,7 @@ pub mod account { use derive_more::Display; + use super::MetadataValue; use crate::prelude::*; queries! { @@ -464,7 +480,7 @@ pub mod account { } impl Query for FindAccountKeyValueByIdAndKey { - type Output = Value; + type Output = MetadataValue; } impl Query for FindAccountsByName { @@ -545,6 +561,7 @@ pub mod asset { use iroha_data_model_derive::model; pub use self::model::*; + use super::MetadataValue; use crate::prelude::*; queries! { @@ -749,11 +766,11 @@ pub mod asset { } impl Query for FindAssetKeyValueByIdAndKey { - type Output = Value; + type Output = MetadataValue; } impl Query for FindAssetDefinitionKeyValueByIdAndKey { - type Output = Value; + type Output = MetadataValue; } impl Query for IsAssetDefinitionOwner { @@ -893,6 +910,7 @@ pub mod domain { use derive_more::Display; + use super::MetadataValue; use crate::prelude::*; queries! { @@ -936,7 +954,7 @@ pub mod domain { } impl Query for FindDomainKeyValueByIdAndKey { - type Output = Value; + type Output = MetadataValue; } impl FindDomainById { @@ -1013,7 +1031,7 @@ pub mod trigger { use derive_more::Display; - use super::Query; + use super::{MetadataValue, Query}; use crate::{ domain::prelude::*, events::FilterBox, @@ -1077,7 +1095,7 @@ pub mod trigger { } impl Query for FindTriggerKeyValueByIdAndKey { - type Output = Value; + type Output = MetadataValue; } impl Query for FindTriggersByDomainId { @@ -1193,7 +1211,7 @@ pub mod transaction { } impl FindTransactionByHash { - ///Construct [`FindTransactionByHash`]. + /// Construct [`FindTransactionByHash`]. pub fn new(hash: impl Into>>) -> Self { Self { hash: hash.into() } } @@ -1288,7 +1306,6 @@ pub mod http { declare_versioned_with_scale!(VersionedSignedQuery 1..2, Debug, Clone, iroha_macro::FromVariant, IntoSchema); declare_versioned_with_scale!(VersionedQueryResult 1..2, Debug, Clone, iroha_macro::FromVariant, IntoSchema); - declare_versioned_with_scale!(VersionedPaginatedQueryResult 1..2, Debug, Clone, iroha_macro::FromVariant, IntoSchema); #[model] pub mod model { @@ -1326,18 +1343,6 @@ pub mod http { /// Signature of the client who sends this query. pub signature: SignatureOf, } - - /// Sized container for all possible Query results. - #[derive( - Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, - )] - #[version_with_scale(n = 1, versioned = "VersionedQueryResult")] - #[serde(transparent)] - #[repr(transparent)] - // TODO: This should be a separate type, not just wrap Value because it infects Value - // with variants that can only ever be returned, i.e. can't be used in instructions - // enum QueryResult { ... } - pub struct QueryResult(pub Value); } mod candidate { @@ -1412,16 +1417,14 @@ pub mod http { /// Paginated Query Result // TODO: This is the only structure whose inner fields are exposed. Wrap it in model macro? #[derive(Debug, Clone, Decode, Encode, Deserialize, Serialize, IntoSchema)] - #[version_with_scale(n = 1, versioned = "VersionedPaginatedQueryResult")] - pub struct PaginatedQueryResult { + #[version_with_scale(n = 1, versioned = "VersionedQueryResult")] + pub struct QueryResult { /// The result of the query execution. - pub result: QueryResult, + pub result: Value, /// pagination pub pagination: Pagination, /// sorting pub sorting: Sorting, - /// Total query amount (if applicable) else 0. - pub total: u64, } impl QueryBuilder { @@ -1462,18 +1465,11 @@ pub mod http { } } - impl From for Value { - fn from(source: QueryResult) -> Self { - source.0 - } - } - pub mod prelude { //! The prelude re-exports most commonly used traits, structs and macros from this crate. pub use super::{ - PaginatedQueryResult, QueryBuilder, QueryResult, SignedQuery, - VersionedPaginatedQueryResult, VersionedQueryResult, VersionedSignedQuery, + QueryBuilder, QueryResult, SignedQuery, VersionedQueryResult, VersionedSignedQuery, }; } } diff --git a/data_model/src/transaction.rs b/data_model/src/transaction.rs index 80712cc6aa4..b49775f0e46 100644 --- a/data_model/src/transaction.rs +++ b/data_model/src/transaction.rs @@ -171,7 +171,7 @@ pub mod model { #[ffi_type] pub struct TransactionValue { /// Committed transaction - pub tx: VersionedSignedTransaction, + pub value: VersionedSignedTransaction, /// Reason of rejection pub error: Option, } @@ -344,7 +344,7 @@ impl TransactionValue { /// Used to return payload of the transaction #[inline] pub fn payload(&self) -> &TransactionPayload { - self.tx.payload() + self.value.payload() } } diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index b35b4421063..725369c0324 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -2116,7 +2116,6 @@ ] }, "Hash": "Array", - "HashOf": "Hash", "HashOf>": "Hash", "HashOf": "Hash", "HashOf": "Hash", @@ -3042,30 +3041,6 @@ "OriginFilter": "PeerId", "OriginFilter": "RoleId", "OriginFilter": "TriggerId", - "PaginatedQueryResult": { - "Struct": [ - { - "name": "result", - "type": "QueryResult" - }, - { - "name": "filter", - "type": "GenericPredicateBox" - }, - { - "name": "pagination", - "type": "Pagination" - }, - { - "name": "sorting", - "type": "Sorting" - }, - { - "name": "total", - "type": "u64" - } - ] - }, "Pagination": { "Struct": [ { @@ -3603,7 +3578,22 @@ } ] }, - "QueryResult": "Value", + "QueryResult": { + "Struct": [ + { + "name": "result", + "type": "Value" + }, + { + "name": "pagination", + "type": "Pagination" + }, + { + "name": "sorting", + "type": "Sorting" + } + ] + }, "RaiseTo": { "Struct": [ { @@ -4196,7 +4186,7 @@ }, { "name": "block_hash", - "type": "HashOf" + "type": "HashOf" } ] }, @@ -4805,12 +4795,12 @@ } ] }, - "VersionedPaginatedQueryResult": { + "VersionedQueryResult": { "Enum": [ { "tag": "V1", "discriminant": 1, - "type": "PaginatedQueryResult" + "type": "QueryResult" } ] }, diff --git a/schema/gen/src/lib.rs b/schema/gen/src/lib.rs index 906bb6f60fd..8543817b32b 100644 --- a/schema/gen/src/lib.rs +++ b/schema/gen/src/lib.rs @@ -47,7 +47,7 @@ pub fn build_schemas() -> MetaMap { VersionedBlockSubscriptionRequest, VersionedEventMessage, VersionedEventSubscriptionRequest, - VersionedPaginatedQueryResult, + VersionedQueryResult, VersionedSignedQuery, // Never referenced, but present in type signature. Like `PhantomData` @@ -282,7 +282,7 @@ types!( OriginFilter, OriginFilter, OriginFilter, - PaginatedQueryResult, + QueryResult, Pagination, Pair, Parameter, @@ -308,7 +308,6 @@ types!( QueryBox, QueryExecutionFail, QueryPayload, - QueryResult, RaiseTo, RegisterBox, RegistrableBox, @@ -379,7 +378,7 @@ types!( VersionedCommittedBlockWrapper, VersionedEventMessage, VersionedEventSubscriptionRequest, - VersionedPaginatedQueryResult, + VersionedQueryResult, VersionedSignedQuery, VersionedSignedTransaction, WasmExecutionFail,