diff --git a/cli/src/torii/routing.rs b/cli/src/torii/routing.rs index 353c452e5fc..6c0fb872729 100644 --- a/cli/src/torii/routing.rs +++ b/cli/src/torii/routing.rs @@ -1,7 +1,6 @@ //! Routing functions for Torii. If you want to add an endpoint to //! Iroha you should add it here by creating a `handle_*` function, -//! and add it to impl Torii. This module also defines the `VerifiedQuery`, -//! which is the only kind of query that is permitted to execute. +//! and add it to impl Torii. // FIXME: This can't be fixed, because one trait in `warp` is private. #![allow(opaque_hidden_inferred_bound)] @@ -17,7 +16,6 @@ use iroha_config::{ GetConfiguration, PostConfiguration, }; use iroha_core::{smartcontracts::isi::query::ValidQueryRequest, sumeragi::SumeragiHandle}; -use iroha_crypto::SignatureOf; use iroha_data_model::{ block::{ stream::{ @@ -26,15 +24,12 @@ use iroha_data_model::{ }, VersionedCommittedBlock, }, - predicate::PredicateBox, prelude::*, - query, query::error::QueryExecutionFail, }; #[cfg(feature = "telemetry")] use iroha_telemetry::metrics::Status; use pagination::{paginate, Paginate}; -use parity_scale_codec::{Decode, Encode}; use tokio::task; use super::*; @@ -45,65 +40,6 @@ pub fn sorting() -> impl warp::Filter, -} - -impl VerifiedQuery { - /// Validate query. - /// - /// # Errors - /// - Account doesn't exist - /// - Account doesn't have the correct public key - /// - Account has incorrect permissions - pub fn validate( - self, - wsv: &mut WorldStateView, - ) -> Result<(ValidQueryRequest, PredicateBox), ValidationFail> { - let account_has_public_key = wsv - .map_account(&self.payload.authority, |account| { - account.signatories.contains(self.signature.public_key()) - }) - .map_err(QueryExecutionFail::from)?; - if !account_has_public_key { - return Err(QueryExecutionFail::Signature(String::from( - "Signature public key doesn't correspond to the account.", - )) - .into()); - } - wsv.validator_view().clone().validate( - wsv, - &self.payload.authority, - self.payload.query.clone(), - )?; - Ok(( - ValidQueryRequest::new(self.payload.query), - self.payload.filter, - )) - } -} - -impl TryFrom for VerifiedQuery { - type Error = QueryExecutionFail; - - fn try_from(query: SignedQuery) -> Result { - query - .signature - .verify(&query.payload) - .map(|_| Self { - payload: query.payload, - signature: query.signature, - }) - .map_err(|e| Self::Error::Signature(e.to_string())) - } -} - #[iroha_futures::telemetry_future] pub(crate) async fn handle_instructions( queue: Arc, @@ -135,19 +71,15 @@ pub(crate) async fn handle_queries( sorting: Sorting, request: VersionedSignedQuery, ) -> Result> { - let VersionedSignedQuery::V1(request) = request; - let request: VerifiedQuery = request.try_into().map_err(ValidationFail::from)?; - - let (result, filter) = { + let result = { let mut wsv = sumeragi.wsv_clone(); - let (valid_request, filter) = request.validate(&mut wsv)?; - let original_result = valid_request.execute(&wsv).map_err(ValidationFail::from)?; - (filter.filter(original_result), filter) + let valid_request = ValidQueryRequest::validate(request, &mut wsv)?; + valid_request.execute(&wsv).map_err(ValidationFail::from)? }; 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, &sorting, pagination); + let vec_of_val = apply_sorting_and_pagination(vec_of_val.into_iter(), &sorting, pagination); (len, Value::Vec(vec_of_val)) } else { @@ -163,20 +95,18 @@ pub(crate) async fn handle_queries( result, pagination, sorting, - filter, total, }; Ok(Scale(paginated_result.into())) } fn apply_sorting_and_pagination( - vec_of_val: Vec, + vec_of_val: 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 - .into_iter() .map(|value| { let key = match &value { Value::Identifiable(IdentifiableBox::Asset(asset)) => match asset.value() { @@ -206,7 +136,7 @@ fn apply_sorting_and_pagination( .paginate(pagination) .collect() } else { - vec_of_val.into_iter().paginate(pagination).collect() + vec_of_val.paginate(pagination).collect() } } diff --git a/client/src/client.rs b/client/src/client.rs index af06fe35ae4..afdee16ad5a 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -247,8 +247,6 @@ where { /// Query output pub output: R::Output, - /// The filter that was applied to the output. - pub filter: PredicateBox, /// See [`iroha_data_model::prelude::PaginatedQueryResult`] pub pagination: Pagination, /// See [`iroha_data_model::prelude::PaginatedQueryResult`] @@ -281,7 +279,6 @@ where pagination, sorting, total, - filter, }: PaginatedQueryResult, ) -> Result { let output = R::Output::try_from(result.into()) @@ -293,7 +290,6 @@ where pagination, sorting, total, - filter, }) } } diff --git a/core/src/smartcontracts/isi/asset.rs b/core/src/smartcontracts/isi/asset.rs index 5beedff387a..be39d8f8037 100644 --- a/core/src/smartcontracts/isi/asset.rs +++ b/core/src/smartcontracts/isi/asset.rs @@ -445,15 +445,19 @@ pub mod query { impl ValidQuery for FindAllAssets { #[metrics(+"find_all_assets")] fn execute(&self, wsv: &WorldStateView) -> Result { - let mut vec = Vec::new(); - for domain in wsv.domains().values() { - for account in domain.accounts.values() { - for asset in account.assets.values() { - vec.push(asset.clone()) - } - } - } - Ok(vec) + Ok(wsv + .domains() + .values() + .map(|domain| { + domain + .accounts + .values() + .map(|account| account.assets.values()) + .flatten() + }) + .flatten() + .cloned() + .collect()) } } diff --git a/core/src/smartcontracts/isi/query.rs b/core/src/smartcontracts/isi/query.rs index 68e5f1ea299..788ae195df0 100644 --- a/core/src/smartcontracts/isi/query.rs +++ b/core/src/smartcontracts/isi/query.rs @@ -13,24 +13,43 @@ use crate::{prelude::ValidQuery, WorldStateView}; /// Query Request statefully validated on the Iroha node side. #[derive(Debug, Decode, Encode)] -pub struct ValidQueryRequest { - query: QueryBox, -} +pub struct ValidQueryRequest(VersionedSignedQuery); impl ValidQueryRequest { + /// Validate query. + /// + /// # Errors + /// - Account doesn't exist + /// - Account doesn't have the correct public key + /// - Account has incorrect permissions + pub fn validate( + query: VersionedSignedQuery, + wsv: &mut WorldStateView, + ) -> Result { + let account_has_public_key = wsv + .map_account(query.authority(), |account| { + account.signatories.contains(query.signature().public_key()) + }) + .map_err(Error::from)?; + if !account_has_public_key { + return Err(Error::Signature(String::from( + "Signature public key doesn't correspond to the account.", + )) + .into()); + } + wsv.validator_view() + .clone() + .validate(wsv, query.authority(), query.query().clone())?; + Ok(ValidQueryRequest(query)) + } + /// Execute contained query on the [`WorldStateView`]. /// /// # Errors /// Forwards `self.query.execute` error. #[inline] pub fn execute(&self, wsv: &WorldStateView) -> Result { - self.query.execute(wsv) - } - - /// Construct `ValidQueryRequest` from a validated query - #[must_use] - pub const fn new(query: QueryBox) -> Self { - Self { query } + Ok(self.0.filter().filter(self.0.query().execute(wsv)?)) } } diff --git a/data_model/src/query.rs b/data_model/src/query.rs index 3491d24675b..da92b03fafd 100644 --- a/data_model/src/query.rs +++ b/data_model/src/query.rs @@ -1318,7 +1318,7 @@ pub mod http { } /// I/O ready structure to send queries. - #[derive(Debug, Clone, Decode, Encode, Deserialize, Serialize, IntoSchema)] + #[derive(Debug, Clone, Encode, Serialize, IntoSchema)] #[version_with_scale(n = 1, versioned = "VersionedSignedQuery")] pub struct SignedQuery { /// Payload @@ -1340,6 +1340,75 @@ pub mod http { pub struct QueryResult(pub Value); } + mod candidate { + use parity_scale_codec::Input; + + use super::*; + + #[derive(Decode, Deserialize)] + struct SignedQueryCandidate { + payload: QueryPayload, + signature: SignatureOf, + } + + impl SignedQueryCandidate { + fn validate(self) -> Result { + #[cfg(feature = "std")] + if self.signature.verify(&self.payload).is_err() { + return Err("Query signature not valid"); + } + + Ok(SignedQuery { + payload: self.payload, + signature: self.signature, + }) + } + } + impl Decode for SignedQuery { + fn decode(input: &mut I) -> Result { + SignedQueryCandidate::decode(input)? + .validate() + .map_err(Into::into) + } + } + impl<'de> Deserialize<'de> for SignedQuery { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error as _; + + SignedQueryCandidate::deserialize(deserializer)? + .validate() + .map_err(D::Error::custom) + } + } + } + + #[cfg(feature = "transparent_api")] + impl VersionedSignedQuery { + /// Return query signature + pub fn signature(&self) -> &SignatureOf { + let VersionedSignedQuery::V1(query) = self; + &query.signature + } + /// Return query payload + pub fn query(&self) -> &QueryBox { + let VersionedSignedQuery::V1(query) = self; + &query.payload.query + } + /// Return query authority + pub fn authority(&self) -> &AccountId { + let VersionedSignedQuery::V1(query) = self; + &query.payload.authority + } + /// Return query filter + pub fn filter(&self) -> &PredicateBox { + let VersionedSignedQuery::V1(query) = self; + &query.payload.filter + } + } + /// 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)] @@ -1347,8 +1416,6 @@ pub mod http { pub struct PaginatedQueryResult { /// The result of the query execution. pub result: QueryResult, - /// The filter that was applied to the Query result. Returned as a sanity check, but also to ease debugging on the front-end. - pub filter: PredicateBox, /// pagination pub pagination: Pagination, /// sorting