From 4c0c3f6f91ec9ee3bb822a4a3eaa65426a8afbd3 Mon Sep 17 00:00:00 2001 From: Greg Szabo Date: Tue, 28 Jul 2020 04:53:28 -0400 Subject: [PATCH 1/3] Refactored and completed Path; added query to Chain trait and implemented it for Tendermint --- modules/src/error.rs | 28 ----- modules/src/ics02_client/mod.rs | 1 - modules/src/ics02_client/query.rs | 106 ----------------- modules/src/ics23_commitment/mod.rs | 10 -- modules/src/ics24_host/mod.rs | 2 + modules/src/ics24_host/path.rs | 100 ++++++++++++++++ modules/src/lib.rs | 2 - modules/src/path.rs | 113 ------------------- modules/src/path/cosmos.rs | 17 --- modules/src/path/ics.rs | 17 --- modules/src/try_from_raw.rs | 11 -- relayer/cli/src/commands/query/channel.rs | 25 ++-- relayer/cli/src/commands/query/connection.rs | 22 +--- relayer/relay/Cargo.toml | 2 + relayer/relay/src/chain.rs | 16 +++ relayer/relay/src/chain/tendermint.rs | 87 +++++++++++++- relayer/relay/src/error.rs | 11 ++ relayer/relay/src/lib.rs | 1 - relayer/relay/src/query.rs | 76 ------------- 19 files changed, 227 insertions(+), 420 deletions(-) delete mode 100644 modules/src/error.rs delete mode 100644 modules/src/ics02_client/query.rs create mode 100644 modules/src/ics24_host/path.rs delete mode 100644 modules/src/path.rs delete mode 100644 modules/src/path/cosmos.rs delete mode 100644 modules/src/path/ics.rs delete mode 100644 relayer/relay/src/query.rs diff --git a/modules/src/error.rs b/modules/src/error.rs deleted file mode 100644 index ecc7981a80..0000000000 --- a/modules/src/error.rs +++ /dev/null @@ -1,28 +0,0 @@ -use anomaly::{BoxError, Context}; -use thiserror::Error; - -pub type Error = anomaly::Error; - -#[derive(Clone, Debug, Error)] -pub enum Kind { - /// RPC error (typically raised by the RPC client or the RPC requester) - #[error("RPC error")] - Rpc, - - /// Event error (raised by the event monitor) - #[error("Bad Notification")] - Event, - - /// Response parsing error - #[error("Could not parse/unmarshall response")] - ResponseParsing, - - #[error("Empty response value")] - EmptyResponseValue, -} - -impl Kind { - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) - } -} diff --git a/modules/src/ics02_client/mod.rs b/modules/src/ics02_client/mod.rs index 4762466ab2..ad31cb3e38 100644 --- a/modules/src/ics02_client/mod.rs +++ b/modules/src/ics02_client/mod.rs @@ -5,5 +5,4 @@ pub mod error; pub mod events; pub mod header; pub mod msgs; -pub mod query; pub mod state; diff --git a/modules/src/ics02_client/query.rs b/modules/src/ics02_client/query.rs deleted file mode 100644 index 4c4d5e7f10..0000000000 --- a/modules/src/ics02_client/query.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::marker::PhantomData; - -use crate::ics23_commitment::{CommitmentPath, CommitmentProof}; - -//use crate::ics02_client::state::{ClientState, ConsensusState}; -use crate::ics24_host::identifier::ClientId; -use crate::path::{ClientStatePath, ConsensusStatePath}; -use crate::Height; - -pub struct QueryClientFullState { - pub chain_height: Height, - pub client_id: ClientId, - pub client_state_path: ClientStatePath, - pub prove: bool, - marker: PhantomData, -} - -impl QueryClientFullState { - pub fn new(chain_height: Height, client_id: ClientId, prove: bool) -> Self { - Self { - chain_height, - client_id: client_id.clone(), - client_state_path: ClientStatePath::new(client_id), - prove, - marker: PhantomData, - } - } -} - -pub struct ClientFullStateResponse { - pub client_state: CLS, - pub proof: Option, - pub proof_path: CommitmentPath, - pub proof_height: Height, -} - -impl ClientFullStateResponse { - pub fn new( - client_id: ClientId, - client_state: CLS, - abci_proof: Option, - proof_height: Height, - ) -> Self { - let proof_path = CommitmentPath::from_path(ClientStatePath::new(client_id)); - - ClientFullStateResponse { - client_state, - proof: abci_proof, - proof_path, - proof_height, - } - } -} - -pub struct QueryClientConsensusState { - pub chain_height: Height, - pub client_id: ClientId, - pub consensus_height: Height, - pub consensus_state_path: ConsensusStatePath, - pub prove: bool, - marker: PhantomData, -} - -impl QueryClientConsensusState { - pub fn new( - chain_height: Height, - client_id: ClientId, - consensus_height: Height, - prove: bool, - ) -> Self { - Self { - chain_height, - client_id: client_id.clone(), - consensus_height, - consensus_state_path: ConsensusStatePath::new(client_id, consensus_height), - prove, - marker: PhantomData, - } - } -} - -pub struct ConsensusStateResponse { - pub consensus_state: CS, - pub proof: Option, - pub proof_path: CommitmentPath, - pub proof_height: Height, -} - -impl ConsensusStateResponse { - pub fn new( - client_id: ClientId, - consensus_state: CS, - abci_proof: Option, - proof_height: Height, - ) -> Self { - let proof_path = - CommitmentPath::from_path(ConsensusStatePath::new(client_id, proof_height)); - - ConsensusStateResponse { - consensus_state, - proof: abci_proof, - proof_path, - proof_height, - } - } -} diff --git a/modules/src/ics23_commitment/mod.rs b/modules/src/ics23_commitment/mod.rs index e3ca922bc8..23251942ff 100644 --- a/modules/src/ics23_commitment/mod.rs +++ b/modules/src/ics23_commitment/mod.rs @@ -1,6 +1,5 @@ use serde_derive::{Deserialize, Serialize}; -use crate::path::Path; use std::fmt; use tendermint::merkle::proof::Proof; @@ -10,15 +9,6 @@ pub struct CommitmentRoot; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CommitmentPath; -impl CommitmentPath { - pub fn from_path

(_p: P) -> Self - where - P: Path, - { - CommitmentPath {} - } -} - pub type CommitmentProof = Proof; /* impl CommitmentProof { diff --git a/modules/src/ics24_host/mod.rs b/modules/src/ics24_host/mod.rs index e187cd7983..4dee293faf 100644 --- a/modules/src/ics24_host/mod.rs +++ b/modules/src/ics24_host/mod.rs @@ -2,4 +2,6 @@ pub mod error; pub mod identifier; +mod path; +pub use path::{Data, Path, IBC_QUERY_PATH}; pub mod validate; diff --git a/modules/src/ics24_host/path.rs b/modules/src/ics24_host/path.rs new file mode 100644 index 0000000000..870327eba4 --- /dev/null +++ b/modules/src/ics24_host/path.rs @@ -0,0 +1,100 @@ +/// Path-space as listed in ICS-024 +/// https://github.com/cosmos/ics/tree/master/spec/ics-024-host-requirements#path-space +/// Some of these are implemented in other ICSs, but ICS-024 has a nice summary table. +use crate::ics24_host::identifier::{ChannelId, ClientId, ConnectionId, PortId}; +use std::fmt::{Display, Formatter, Result}; + +/// IBC Query Path is hard-coded +pub const IBC_QUERY_PATH: &str = "store/ibc/key"; + +/// The Data enum abstracts out the different Path types +pub enum Data { + ClientType(ClientId), + ClientState(ClientId), + ConsensusState(ClientId, u64), + ClientConnections(ClientId), + Connections(ConnectionId), + Ports(PortId), + ChannelEnds(PortId, ChannelId), + SeqSends(PortId, ChannelId), + SeqRecvs(PortId, ChannelId), + SeqAcks(PortId, ChannelId), + Commitments(PortId, ChannelId, u64), + Acks(PortId, ChannelId, u64), +} + +impl Data { + /// Indication if the data type is provable. + pub fn is_provable(&self) -> bool { + match &self { + Data::ClientState(_) => false, + Data::ClientConnections(_) => false, + Data::Ports(_) => false, + _ => true, + } + } +} + +/// The Path struct converts the Data enum into the Paths defined by the ICS +pub struct Path { + data: Data, +} + +/// The Display trait adds the `.to_string()` method to the RawData struct +/// This is where the different path strings are constructed +impl Display for Path { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match &self.data { + Data::ClientType(id) => write!(f, "clients/{}/clientType", id), + Data::ClientState(id) => write!(f, "clients/{}/clientState", id), + Data::ConsensusState(id, height) => { + write!(f, "clients/{}/consensusState/{}", id, height) + } + Data::ClientConnections(id) => write!(f, "clients/{}/connections", id), + Data::Connections(id) => write!(f, "connections/{}", id), + Data::Ports(id) => write!(f, "ports/{}", id), + Data::ChannelEnds(port_id, channel_id) => { + write!(f, "channelEnds/ports/{}/channels/{}", port_id, channel_id) + } + Data::SeqSends(port_id, channel_id) => write!( + f, + "seqSends/ports/{}/channels/{}/nextSequenceSend", + port_id, channel_id + ), + Data::SeqRecvs(port_id, channel_id) => write!( + f, + "seqRecvs/ports/{}/channels/{}/nextSequenceRecv", + port_id, channel_id + ), + Data::SeqAcks(port_id, channel_id) => write!( + f, + "seqAcks/ports/{}/channels/{}/nextSequenceAck", + port_id, channel_id + ), + Data::Commitments(port_id, channel_id, seq) => write!( + f, + "commitments/ports/{}/channels/{}/packets/{}", + port_id, channel_id, seq + ), + Data::Acks(port_id, channel_id, seq) => write!( + f, + "acks/ports/{}/channels/{}/acknowledgements/{}", + port_id, channel_id, seq + ), + } + } +} + +impl Path { + /// into_bytes implementation + pub fn into_bytes(self) -> Vec { + self.to_string().into_bytes() + } +} + +/// Easily construct a new Path using the From trait +impl From for Path { + fn from(data: Data) -> Self { + Path { data } + } +} diff --git a/modules/src/lib.rs b/modules/src/lib.rs index 3e06fa9712..eef337d78d 100644 --- a/modules/src/lib.rs +++ b/modules/src/lib.rs @@ -19,7 +19,6 @@ //! - ICS 23: Vector Commitment Scheme //! - ICS 24: Host Requirements -pub mod error; pub mod events; pub mod ics02_client; pub mod ics03_connection; @@ -29,7 +28,6 @@ pub mod ics20_fungible_token_transfer; pub mod ics23_commitment; pub mod ics24_host; pub mod keys; -pub mod path; pub mod proofs; pub mod try_from_raw; pub mod tx_msg; diff --git a/modules/src/path.rs b/modules/src/path.rs deleted file mode 100644 index ca9059de76..0000000000 --- a/modules/src/path.rs +++ /dev/null @@ -1,113 +0,0 @@ -use std::fmt; - -use crate::ics24_host::identifier::{ChannelId, ClientId, ConnectionId, PortId}; -use crate::Height; - -mod cosmos; -mod ics; - -#[cfg(feature = "paths-cosmos")] -use cosmos as paths; -#[cfg(not(feature = "paths-cosmos"))] -use ics as paths; - -pub struct Key<'a, P>(&'a P); - -impl<'a, P> fmt::Display for Key<'a, P> -where - P: Path, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0.to_string()) - } -} - -impl<'a, P> Into> for Key<'a, P> -where - P: Path, -{ - fn into(self) -> Vec { - self.to_string().into_bytes() - } -} - -pub trait Path: Sized { - fn to_string(&self) -> String; - - fn to_key(&self) -> Key<'_, Self> { - Key(self) - } -} - -pub struct ConnectionPath { - pub connection_id: ConnectionId, -} - -impl ConnectionPath { - pub fn new(connection_id: ConnectionId) -> Self { - Self { connection_id } - } -} - -impl Path for ConnectionPath { - fn to_string(&self) -> String { - paths::connection_path(&self) - } -} - -pub struct ConsensusStatePath { - pub client_id: ClientId, - pub height: Height, -} - -impl ConsensusStatePath { - pub fn new(client_id: ClientId, height: Height) -> Self { - Self { client_id, height } - } -} - -impl Path for ConsensusStatePath { - fn to_string(&self) -> String { - paths::consensus_state_path(&self) - } -} - -pub struct ClientStatePath { - pub client_id: ClientId, -} - -impl ClientStatePath { - pub fn new(client_id: ClientId) -> Self { - Self { client_id } - } -} - -impl Path for ClientStatePath { - fn to_string(&self) -> String { - paths::client_state_path(&self) - } -} - -pub struct ChannelPath { - pub port_id: PortId, - channel_id: ChannelId, -} - -impl ChannelPath { - pub fn new(port_id: PortId, channel_id: ChannelId) -> Self { - Self { - port_id, - channel_id, - } - } -} - -const KEY_CHANNEL_PREFIX: &str = "channelEnds"; - -pub type ChannelEndsPath = ChannelPath; - -impl Path for ChannelEndsPath { - fn to_string(&self) -> String { - format!("{}/{}", KEY_CHANNEL_PREFIX, paths::channel_path(&self)) - } -} diff --git a/modules/src/path/cosmos.rs b/modules/src/path/cosmos.rs deleted file mode 100644 index 618b90204a..0000000000 --- a/modules/src/path/cosmos.rs +++ /dev/null @@ -1,17 +0,0 @@ -use super::{ChannelPath, ClientStatePath, ConnectionPath, ConsensusStatePath}; - -pub fn connection_path(path: &ConnectionPath) -> String { - format!("connection/{}", path.connection_id) -} - -pub fn consensus_state_path(path: &ConsensusStatePath) -> String { - format!("consensusState/{}/{}", path.client_id, path.height) -} - -pub fn client_state_path(path: &ClientStatePath) -> String { - format!("clientState/{}", path.client_id) -} - -pub fn channel_path(path: &ChannelPath) -> String { - format!("ports/{}/channels/{}", path.port_id, path.channel_id) -} diff --git a/modules/src/path/ics.rs b/modules/src/path/ics.rs deleted file mode 100644 index 624a8e24ed..0000000000 --- a/modules/src/path/ics.rs +++ /dev/null @@ -1,17 +0,0 @@ -use super::{ChannelPath, ClientStatePath, ConnectionPath, ConsensusStatePath}; - -pub fn consensus_state_path(path: &ConsensusStatePath) -> String { - format!("clients/{}/consensusState/{}", path.client_id, path.height) -} - -pub fn client_state_path(path: &ClientStatePath) -> String { - format!("clients/{}/clientState", path.client_id) -} - -pub fn connection_path(path: &ConnectionPath) -> String { - format!("connections/{}", path.connection_id) -} - -pub fn channel_path(path: &ChannelPath) -> String { - format!("ports/{}/channels/{}", path.port_id, path.channel_id) -} diff --git a/modules/src/try_from_raw.rs b/modules/src/try_from_raw.rs index 51f5eddf33..d8901091dd 100644 --- a/modules/src/try_from_raw.rs +++ b/modules/src/try_from_raw.rs @@ -2,14 +2,11 @@ //! This is similar to the pattern of using the #[serde(from="RawType") derive for automatic //! conversion with the TryFrom::try_from(value: RawType) trait for validation. //! Only serde does this for JSON and here we need to do it for protobuf. -use crate::error::{Error, Kind}; -use bytes::Bytes; use prost::Message; use std::convert::Into; use std::default::Default; use std::error::Error as StdError; use std::marker::Sized; -use std::vec::Vec; /// TryFromRaw trait needs to implement a try_from() function and an Error type for the return of that function. pub trait TryFromRaw: Sized { @@ -21,12 +18,4 @@ pub trait TryFromRaw: Sized { /// try_from function will validate the incoming RawType and convert it to our domain type fn try_from(value: Self::RawType) -> Result; - - /// deserialize function will deserialize from the protobuf-encoded bytes into the domain type - /// using the RawType and try_from() as an interim step. - fn deserialize(wire: Vec) -> Result { - Self::RawType::decode(Bytes::from(wire)) - .map_err(|e| Kind::ResponseParsing.context(e).into()) - .and_then(|r| Self::try_from(r).map_err(|e| Kind::ResponseParsing.context(e).into())) - } } diff --git a/relayer/cli/src/commands/query/channel.rs b/relayer/cli/src/commands/query/channel.rs index 0e56d3eccc..bdebf79943 100644 --- a/relayer/cli/src/commands/query/channel.rs +++ b/relayer/cli/src/commands/query/channel.rs @@ -2,18 +2,14 @@ use crate::prelude::*; use abscissa_core::{Command, Options, Runnable}; use relayer::config::{ChainConfig, Config}; -use relayer::query::{query, Request}; use relayer_modules::ics04_channel::channel::ChannelEnd; use relayer_modules::ics24_host::identifier::{ChannelId, PortId}; +use relayer_modules::ics24_host::Data::ChannelEnds; -use crate::commands::utils::block_on; use relayer::chain::tendermint::TendermintChain; -use relayer_modules::error::Error; +use relayer::chain::Chain; use relayer_modules::ics24_host::error::ValidationError; -use relayer_modules::path::{ChannelEndsPath, Path}; -use std::str::FromStr; -use tendermint::abci::Path as TendermintPath; use tendermint::chain::Id as ChainId; #[derive(Clone, Command, Debug, Options)] @@ -42,17 +38,6 @@ struct QueryChannelOptions { proof: bool, } -impl Into for QueryChannelOptions { - fn into(self) -> Request { - Request { - path: Some(TendermintPath::from_str(&"store/ibc/key").unwrap()), - data: ChannelEndsPath::new(self.port_id, self.channel_id).to_string(), - height: self.height, - prove: self.proof, - } - } -} - impl QueryChannelEndCmd { fn validate_options( &self, @@ -113,7 +98,11 @@ impl Runnable for QueryChannelEndCmd { // run without proof: // cargo run --bin relayer -- -c relayer/relay/tests/config/fixtures/simple_config.toml query channel end ibc-test firstport firstchannel --height 3 -p false let chain = TendermintChain::from_config(chain_config).unwrap(); - let res: Result = block_on(query(&chain, opts)); + let res = chain.query::( + ChannelEnds(opts.port_id, opts.channel_id), + opts.height, + opts.proof, + ); match res { Ok(cs) => status_info!("Result for channel end query: ", "{:?}", cs), diff --git a/relayer/cli/src/commands/query/connection.rs b/relayer/cli/src/commands/query/connection.rs index 4a2acedbaa..ea4229b22a 100644 --- a/relayer/cli/src/commands/query/connection.rs +++ b/relayer/cli/src/commands/query/connection.rs @@ -3,18 +3,14 @@ use crate::prelude::*; use abscissa_core::{Command, Options, Runnable}; use relayer::config::{ChainConfig, Config}; -use crate::commands::utils::block_on; use relayer::chain::tendermint::TendermintChain; -use relayer::query::{query, Request}; -use relayer_modules::error::Error; +use relayer::chain::Chain; use relayer_modules::ics24_host::error::ValidationError; use relayer_modules::ics24_host::identifier::ConnectionId; -use relayer_modules::path::{ConnectionPath, Path}; +use relayer_modules::ics24_host::Data::Connections; use tendermint::chain::Id as ChainId; use relayer_modules::ics03_connection::connection::ConnectionEnd; -use std::str::FromStr; -use tendermint::abci::Path as TendermintPath; #[derive(Clone, Command, Debug, Options)] pub struct QueryConnectionEndCmd { @@ -38,17 +34,6 @@ struct QueryConnectionOptions { proof: bool, } -impl Into for QueryConnectionOptions { - fn into(self) -> Request { - Request { - path: Some(TendermintPath::from_str(&"store/ibc/key").unwrap()), - data: ConnectionPath::new(self.connection_id).to_string(), - height: self.height, - prove: self.proof, - } - } -} - impl QueryConnectionEndCmd { fn validate_options( &self, @@ -101,7 +86,8 @@ impl Runnable for QueryConnectionEndCmd { let chain = TendermintChain::from_config(chain_config).unwrap(); // run without proof: // cargo run --bin relayer -- -c relayer/relay/tests/config/fixtures/simple_config.toml query connection end ibc-test connectionidone --height 3 -p false - let res: Result = block_on(query(&chain, opts)); + let res = + chain.query::(Connections(opts.connection_id), opts.height, opts.proof); match res { Ok(cs) => status_info!("connection query result: ", "{:?}", cs), diff --git a/relayer/relay/Cargo.toml b/relayer/relay/Cargo.toml index 31523aecea..99651ce3e6 100644 --- a/relayer/relay/Cargo.toml +++ b/relayer/relay/Cargo.toml @@ -23,5 +23,7 @@ toml = "0.5" tracing = "0.1.13" tokio = "0.2" serde_json = { version = "1" } +bytes = "0.5.6" +prost = "0.6.1" [dev-dependencies] diff --git a/relayer/relay/src/chain.rs b/relayer/relay/src/chain.rs index 83d82bcb20..a949f6e19c 100644 --- a/relayer/relay/src/chain.rs +++ b/relayer/relay/src/chain.rs @@ -9,9 +9,12 @@ use ::tendermint::lite::{self, Height, TrustThresholdFraction}; use ::tendermint_rpc::Client as RpcClient; use relayer_modules::ics02_client::state::{ClientState, ConsensusState}; +use relayer_modules::ics24_host::Data; +use relayer_modules::try_from_raw::TryFromRaw; use crate::config::ChainConfig; use crate::error; +use std::error::Error; pub mod tendermint; @@ -35,6 +38,19 @@ pub trait Chain { /// Type of RPC requester (wrapper around low-level RPC client) for this chain type Requester: tmlite::Requester; + /// Error types defined by this chain + type Error: Into>; + + /// Perform a generic `query`, and return the corresponding deserialized response data. + // This is going to be a blocking request. + // From the "Asynchronous Programming in Rust" book: + // Important extensions like `async fn` syntax in trait methods are still unimplemented + // https://rust-lang.github.io/async-book/01_getting_started/03_state_of_async_rust.html + // Todo: More generic chains might want to deal with domain types differently (no T). + fn query(&self, data: Data, height: u64, prove: bool) -> Result + where + T: TryFromRaw; + /// Returns the chain's identifier fn id(&self) -> &ChainId { &self.config().id diff --git a/relayer/relay/src/chain/tendermint.rs b/relayer/relay/src/chain/tendermint.rs index b781fc5e8c..3514152d80 100644 --- a/relayer/relay/src/chain/tendermint.rs +++ b/relayer/relay/src/chain/tendermint.rs @@ -1,18 +1,26 @@ use std::time::Duration; +use tendermint::abci::Path as TendermintPath; use tendermint::block::signed_header::SignedHeader as TMCommit; use tendermint::block::Header as TMHeader; +use tendermint::block::Height; use tendermint::lite::TrustThresholdFraction; use tendermint_rpc::Client as RpcClient; +use core::future::Future; use relayer_modules::ics07_tendermint::client_state::ClientState; use relayer_modules::ics07_tendermint::consensus_state::ConsensusState; +use relayer_modules::ics24_host::{Data, Path, IBC_QUERY_PATH}; +use relayer_modules::try_from_raw::TryFromRaw; use crate::client::rpc_requester::RpcRequester; use crate::config::ChainConfig; -use crate::error; +use crate::error::{Error, Kind}; use super::Chain; +use bytes::Bytes; +use prost::Message; +use std::str::FromStr; pub struct TendermintChain { config: ChainConfig, @@ -21,7 +29,7 @@ pub struct TendermintChain { } impl TendermintChain { - pub fn from_config(config: ChainConfig) -> Result { + pub fn from_config(config: ChainConfig) -> Result { // TODO: Derive Clone on RpcClient in tendermint-rs let requester = RpcRequester::new(RpcClient::new(config.rpc_addr.clone())); let rpc_client = RpcClient::new(config.rpc_addr.clone()); @@ -40,6 +48,36 @@ impl Chain for TendermintChain { type ConsensusState = ConsensusState; type Requester = RpcRequester; type ClientState = ClientState; + type Error = anomaly::Error; + + fn query(&self, data: Data, height: u64, prove: bool) -> Result + where + T: TryFromRaw, + { + let path = TendermintPath::from_str(IBC_QUERY_PATH).unwrap(); + if !data.is_provable() & prove { + return Err(Kind::Store + .context("requested proof for privateStore path") + .into()); + } + let response = block_on(abci_query( + &self, + path, + Path::from(data).to_string(), + height, + prove, + ))?; + + // Verify response proof, if requested. + if prove { + dbg!("Todo: implement proof verification."); // Todo: Verify proof + } + + // Deserialize response data. + T::RawType::decode(Bytes::from(response)) + .map_err(|e| Kind::ResponseParsing.context(e).into()) + .and_then(|r| T::try_from(r).map_err(|e| Kind::ResponseParsing.context(e).into())) + } fn config(&self) -> &ChainConfig { &self.config @@ -61,3 +99,48 @@ impl Chain for TendermintChain { TrustThresholdFraction::default() } } + +/// Perform a generic `abci_query`, and return the corresponding deserialized response data. +async fn abci_query( + chain: &TendermintChain, + path: TendermintPath, + data: String, + height: u64, + prove: bool, +) -> Result, anomaly::Error> { + // Use the Tendermint-rs RPC client to do the query. + let response = chain + .rpc_client() + .abci_query( + Some(path), + data.into_bytes(), + match height { + 0 => None, + _ => Some(Height::from(height)), + }, + prove, + ) + .await + .map_err(|e| Kind::Rpc.context(e))?; + + if !response.code.is_ok() { + // Fail with response log. + return Err(Kind::Rpc.context(response.log.to_string()).into()); + } + if response.value.is_empty() { + // Fail due to empty response value (nothing to decode). + return Err(Kind::EmptyResponseValue.into()); + } + + Ok(response.value) +} + +/// block on future +pub fn block_on(future: F) -> F::Output { + tokio::runtime::Builder::new() + .basic_scheduler() + .enable_all() + .build() + .unwrap() + .block_on(future) +} diff --git a/relayer/relay/src/error.rs b/relayer/relay/src/error.rs index 0ce2b1498b..b3438868a8 100644 --- a/relayer/relay/src/error.rs +++ b/relayer/relay/src/error.rs @@ -28,6 +28,17 @@ pub enum Kind { /// Trusted store error, raised by instances of `Store` #[error("store error")] Store, + + /// Event error (raised by the event monitor) + #[error("Bad Notification")] + Event, + + /// Response parsing error + #[error("Could not parse/unmarshall response")] + ResponseParsing, + + #[error("Empty response value")] + EmptyResponseValue, } impl Kind { diff --git a/relayer/relay/src/lib.rs b/relayer/relay/src/lib.rs index dc7214eebd..ea2e5c7797 100644 --- a/relayer/relay/src/lib.rs +++ b/relayer/relay/src/lib.rs @@ -17,6 +17,5 @@ pub mod config; pub mod error; pub mod event_handler; pub mod event_monitor; -pub mod query; pub mod store; pub mod util; diff --git a/relayer/relay/src/query.rs b/relayer/relay/src/query.rs deleted file mode 100644 index f0b4c0538a..0000000000 --- a/relayer/relay/src/query.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::chain::Chain; -use relayer_modules::error; -use relayer_modules::try_from_raw::TryFromRaw; -use tendermint::abci::Path as TendermintPath; -use tendermint::block; - -pub mod client; - -/// Query the ABCI application for information -pub struct Request { - /// Path to the data - pub path: Option, - - /// Data to query - pub data: String, - - /// Block height - pub height: u64, - - /// Include proof in response - pub prove: bool, -} - -/// Whether or not this path requires proof verification. -/// -/// is_query_store_with_proof expects a format like ///, -/// where queryType must be "store" and subpath must be "key" to require a proof. -fn is_query_store_with_proof(_path: &TendermintPath) -> bool { - false -} - -/// Perform a generic `abci_query` on the given `chain`, and return the corresponding deserialized response data. -pub async fn query(chain: &C, request: O) -> Result -where - C: Chain, // Chain configuration - T: TryFromRaw, // Internal Struct type (expected response) - O: Into, // Query Command configuration (opts) -{ - // RPC Request - let request: Request = request.into(); - let path = request.path.clone().unwrap(); // for the is_query_store_with_proof function - - // Use the Tendermint-rs RPC client to do the query. - // Todo: generalize further for other type of chains (#157). - let response = chain - .rpc_client() - .abci_query( - request.path, - request.data.to_string().into_bytes(), - match request.height { - 0 => None, - _ => Some(block::Height::from(request.height)), - }, - request.prove, - ) - .await - .map_err(|e| error::Kind::Rpc.context(e))?; - - if !response.code.is_ok() { - // Fail with response log. - return Err(error::Kind::Rpc.context(response.log.to_string()).into()); - } - if response.value.is_empty() { - // Fail due to empty response value (nothing to decode). - return Err(error::Kind::EmptyResponseValue.into()); - } - - // Verify response proof. - // Data that is not from trusted node or subspace query needs verification. - if is_query_store_with_proof(&path) { - todo!() // TODO: Verify proof - } - - // Deserialize response data. - T::deserialize(response.value) -} From b00762cd27c8c837c89af80fe6e19252513aa3c4 Mon Sep 17 00:00:00 2001 From: Greg Szabo Date: Tue, 28 Jul 2020 05:13:57 -0400 Subject: [PATCH 2/3] Slightly simpler Path implementation --- modules/src/ics24_host/mod.rs | 2 +- modules/src/ics24_host/path.rs | 64 ++++++++------------ relayer/cli/src/commands/query/channel.rs | 2 +- relayer/cli/src/commands/query/connection.rs | 2 +- relayer/relay/src/chain.rs | 4 +- relayer/relay/src/chain/tendermint.rs | 12 +--- 6 files changed, 33 insertions(+), 53 deletions(-) diff --git a/modules/src/ics24_host/mod.rs b/modules/src/ics24_host/mod.rs index 4dee293faf..90c28b9033 100644 --- a/modules/src/ics24_host/mod.rs +++ b/modules/src/ics24_host/mod.rs @@ -3,5 +3,5 @@ pub mod error; pub mod identifier; mod path; -pub use path::{Data, Path, IBC_QUERY_PATH}; +pub use path::{Path, IBC_QUERY_PATH}; pub mod validate; diff --git a/modules/src/ics24_host/path.rs b/modules/src/ics24_host/path.rs index 870327eba4..456bbbf75b 100644 --- a/modules/src/ics24_host/path.rs +++ b/modules/src/ics24_host/path.rs @@ -7,8 +7,8 @@ use std::fmt::{Display, Formatter, Result}; /// IBC Query Path is hard-coded pub const IBC_QUERY_PATH: &str = "store/ibc/key"; -/// The Data enum abstracts out the different Path types -pub enum Data { +/// The Path enum abstracts out the different sub-paths +pub enum Path { ClientType(ClientId), ClientState(ClientId), ConsensusState(ClientId, u64), @@ -23,60 +23,60 @@ pub enum Data { Acks(PortId, ChannelId, u64), } -impl Data { - /// Indication if the data type is provable. +impl Path { + /// Indication if the path is provable. pub fn is_provable(&self) -> bool { match &self { - Data::ClientState(_) => false, - Data::ClientConnections(_) => false, - Data::Ports(_) => false, + Path::ClientState(_) => false, + Path::ClientConnections(_) => false, + Path::Ports(_) => false, _ => true, } } -} -/// The Path struct converts the Data enum into the Paths defined by the ICS -pub struct Path { - data: Data, + /// into_bytes implementation + pub fn into_bytes(self) -> Vec { + self.to_string().into_bytes() + } } -/// The Display trait adds the `.to_string()` method to the RawData struct +/// The Display trait adds the `.to_string()` method to the Path struct /// This is where the different path strings are constructed impl Display for Path { fn fmt(&self, f: &mut Formatter<'_>) -> Result { - match &self.data { - Data::ClientType(id) => write!(f, "clients/{}/clientType", id), - Data::ClientState(id) => write!(f, "clients/{}/clientState", id), - Data::ConsensusState(id, height) => { + match &self { + Path::ClientType(id) => write!(f, "clients/{}/clientType", id), + Path::ClientState(id) => write!(f, "clients/{}/clientState", id), + Path::ConsensusState(id, height) => { write!(f, "clients/{}/consensusState/{}", id, height) } - Data::ClientConnections(id) => write!(f, "clients/{}/connections", id), - Data::Connections(id) => write!(f, "connections/{}", id), - Data::Ports(id) => write!(f, "ports/{}", id), - Data::ChannelEnds(port_id, channel_id) => { + Path::ClientConnections(id) => write!(f, "clients/{}/connections", id), + Path::Connections(id) => write!(f, "connections/{}", id), + Path::Ports(id) => write!(f, "ports/{}", id), + Path::ChannelEnds(port_id, channel_id) => { write!(f, "channelEnds/ports/{}/channels/{}", port_id, channel_id) } - Data::SeqSends(port_id, channel_id) => write!( + Path::SeqSends(port_id, channel_id) => write!( f, "seqSends/ports/{}/channels/{}/nextSequenceSend", port_id, channel_id ), - Data::SeqRecvs(port_id, channel_id) => write!( + Path::SeqRecvs(port_id, channel_id) => write!( f, "seqRecvs/ports/{}/channels/{}/nextSequenceRecv", port_id, channel_id ), - Data::SeqAcks(port_id, channel_id) => write!( + Path::SeqAcks(port_id, channel_id) => write!( f, "seqAcks/ports/{}/channels/{}/nextSequenceAck", port_id, channel_id ), - Data::Commitments(port_id, channel_id, seq) => write!( + Path::Commitments(port_id, channel_id, seq) => write!( f, "commitments/ports/{}/channels/{}/packets/{}", port_id, channel_id, seq ), - Data::Acks(port_id, channel_id, seq) => write!( + Path::Acks(port_id, channel_id, seq) => write!( f, "acks/ports/{}/channels/{}/acknowledgements/{}", port_id, channel_id, seq @@ -84,17 +84,3 @@ impl Display for Path { } } } - -impl Path { - /// into_bytes implementation - pub fn into_bytes(self) -> Vec { - self.to_string().into_bytes() - } -} - -/// Easily construct a new Path using the From trait -impl From for Path { - fn from(data: Data) -> Self { - Path { data } - } -} diff --git a/relayer/cli/src/commands/query/channel.rs b/relayer/cli/src/commands/query/channel.rs index bdebf79943..2ed02f2798 100644 --- a/relayer/cli/src/commands/query/channel.rs +++ b/relayer/cli/src/commands/query/channel.rs @@ -5,7 +5,7 @@ use relayer::config::{ChainConfig, Config}; use relayer_modules::ics04_channel::channel::ChannelEnd; use relayer_modules::ics24_host::identifier::{ChannelId, PortId}; -use relayer_modules::ics24_host::Data::ChannelEnds; +use relayer_modules::ics24_host::Path::ChannelEnds; use relayer::chain::tendermint::TendermintChain; use relayer::chain::Chain; diff --git a/relayer/cli/src/commands/query/connection.rs b/relayer/cli/src/commands/query/connection.rs index ea4229b22a..e51d6909a9 100644 --- a/relayer/cli/src/commands/query/connection.rs +++ b/relayer/cli/src/commands/query/connection.rs @@ -7,7 +7,7 @@ use relayer::chain::tendermint::TendermintChain; use relayer::chain::Chain; use relayer_modules::ics24_host::error::ValidationError; use relayer_modules::ics24_host::identifier::ConnectionId; -use relayer_modules::ics24_host::Data::Connections; +use relayer_modules::ics24_host::Path::Connections; use tendermint::chain::Id as ChainId; use relayer_modules::ics03_connection::connection::ConnectionEnd; diff --git a/relayer/relay/src/chain.rs b/relayer/relay/src/chain.rs index a949f6e19c..f4ebc4eb6a 100644 --- a/relayer/relay/src/chain.rs +++ b/relayer/relay/src/chain.rs @@ -9,7 +9,7 @@ use ::tendermint::lite::{self, Height, TrustThresholdFraction}; use ::tendermint_rpc::Client as RpcClient; use relayer_modules::ics02_client::state::{ClientState, ConsensusState}; -use relayer_modules::ics24_host::Data; +use relayer_modules::ics24_host::Path; use relayer_modules::try_from_raw::TryFromRaw; use crate::config::ChainConfig; @@ -47,7 +47,7 @@ pub trait Chain { // Important extensions like `async fn` syntax in trait methods are still unimplemented // https://rust-lang.github.io/async-book/01_getting_started/03_state_of_async_rust.html // Todo: More generic chains might want to deal with domain types differently (no T). - fn query(&self, data: Data, height: u64, prove: bool) -> Result + fn query(&self, data: Path, height: u64, prove: bool) -> Result where T: TryFromRaw; diff --git a/relayer/relay/src/chain/tendermint.rs b/relayer/relay/src/chain/tendermint.rs index 3514152d80..10664b759d 100644 --- a/relayer/relay/src/chain/tendermint.rs +++ b/relayer/relay/src/chain/tendermint.rs @@ -10,7 +10,7 @@ use tendermint_rpc::Client as RpcClient; use core::future::Future; use relayer_modules::ics07_tendermint::client_state::ClientState; use relayer_modules::ics07_tendermint::consensus_state::ConsensusState; -use relayer_modules::ics24_host::{Data, Path, IBC_QUERY_PATH}; +use relayer_modules::ics24_host::{Path, IBC_QUERY_PATH}; use relayer_modules::try_from_raw::TryFromRaw; use crate::client::rpc_requester::RpcRequester; @@ -50,7 +50,7 @@ impl Chain for TendermintChain { type ClientState = ClientState; type Error = anomaly::Error; - fn query(&self, data: Data, height: u64, prove: bool) -> Result + fn query(&self, data: Path, height: u64, prove: bool) -> Result where T: TryFromRaw, { @@ -60,13 +60,7 @@ impl Chain for TendermintChain { .context("requested proof for privateStore path") .into()); } - let response = block_on(abci_query( - &self, - path, - Path::from(data).to_string(), - height, - prove, - ))?; + let response = block_on(abci_query(&self, path, data.to_string(), height, prove))?; // Verify response proof, if requested. if prove { From 5fe3179ec9689f3c2f27ee9871ae21123f4cb266 Mon Sep 17 00:00:00 2001 From: Greg Szabo Date: Tue, 28 Jul 2020 19:53:43 -0400 Subject: [PATCH 3/3] Docstring fix --- relayer/relay/src/error.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/relayer/relay/src/error.rs b/relayer/relay/src/error.rs index b3438868a8..0028743b13 100644 --- a/relayer/relay/src/error.rs +++ b/relayer/relay/src/error.rs @@ -37,6 +37,7 @@ pub enum Kind { #[error("Could not parse/unmarshall response")] ResponseParsing, + /// Response does not contain data #[error("Empty response value")] EmptyResponseValue, }