From 48e321a42794aaa28a8636d368c036b414fa7c6c Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 9 Mar 2020 14:10:19 +0100 Subject: [PATCH] Implement QueryClientConsensusState IBC query (#23) Summary: Implement the `QueryClientConsensusState` IBC query, some of its dependencies, and a mostly type-safe IBC wrapper around `tendermint::rpc::Client`. Notes: - Comments/documentation is currently missing. - There is still a bunch of function/trait stubs with `todo!()` bodies. - Some functions contains calls to `unwrap()` which may panic. - Commitment proofs verification is not implemented. The related data structures are only stubs before we link against the [`ics23`](https://github.com/confio/ics23) crate. - Amino deserialization of the IBC query response value is not implemented. - This commit depends on some changes to the `tendermint-rs` crate, which are available in [my fork](https://github.com/romac/tendermint-rs/compare/rpc-client-new-sync). --- relayer/cli/Cargo.toml | 5 +- relayer/relay/Cargo.toml | 4 +- relayer/relay/src/chain.rs | 15 +++ relayer/relay/src/chain/tendermint.rs | 34 +++++ relayer/relay/src/error.rs | 3 + relayer/relay/src/lib.rs | 2 + relayer/relay/src/query.rs | 56 +++++++++ .../relay/src/query/client_consensus_state.rs | 117 ++++++++++++++++++ 8 files changed, 233 insertions(+), 3 deletions(-) create mode 100644 relayer/relay/src/chain.rs create mode 100644 relayer/relay/src/chain/tendermint.rs create mode 100644 relayer/relay/src/query.rs create mode 100644 relayer/relay/src/query/client_consensus_state.rs diff --git a/relayer/cli/Cargo.toml b/relayer/cli/Cargo.toml index e164c00009..7050e1d129 100644 --- a/relayer/cli/Cargo.toml +++ b/relayer/cli/Cargo.toml @@ -8,11 +8,12 @@ authors = [ ] [dependencies] +relayer = { path = "../relay" } + +anomaly = "0.2.0" gumdrop = "0.7" serde = { version = "1", features = ["serde_derive"] } thiserror = "1" -anomaly = "0.2.0" -relayer = { path = "../relay" } [dependencies.abscissa_core] version = "0.5.2" diff --git a/relayer/relay/Cargo.toml b/relayer/relay/Cargo.toml index 6a75899d5b..ec13a37fe1 100644 --- a/relayer/relay/Cargo.toml +++ b/relayer/relay/Cargo.toml @@ -10,7 +10,9 @@ authors = [ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tendermint = { git = "https://github.com/interchainio/tendermint-rs.git" } +relayer-modules = { path = "../../modules" } +tendermint = { git = "https://github.com/romac/tendermint-rs.git", branch = "rpc-client-new-sync" } + anomaly = "0.2.0" humantime-serde = "1.0.0" serde = "1.0.97" diff --git a/relayer/relay/src/chain.rs b/relayer/relay/src/chain.rs new file mode 100644 index 0000000000..95d5327598 --- /dev/null +++ b/relayer/relay/src/chain.rs @@ -0,0 +1,15 @@ +use ::tendermint::rpc; + +use relayer_modules::ics02_client::state::ConsensusState; + +use crate::config::ChainConfig; + +pub mod tendermint; + +pub trait Chain { + type Type; + type ConsensusState: ConsensusState; + + fn config(&self) -> &ChainConfig; + fn rpc_client(&self) -> &rpc::Client; // TODO: Define our own generic client interface? +} diff --git a/relayer/relay/src/chain/tendermint.rs b/relayer/relay/src/chain/tendermint.rs new file mode 100644 index 0000000000..5f28974be3 --- /dev/null +++ b/relayer/relay/src/chain/tendermint.rs @@ -0,0 +1,34 @@ +use tendermint::rpc; + +use relayer_modules::ics02_client::client_type::Tendermint; +use relayer_modules::ics07_tendermint::consensus_state::ConsensusState; + +use crate::config::ChainConfig; +use crate::error; + +use super::Chain; + +pub struct TendermintChain { + config: ChainConfig, + rpc_client: rpc::Client, +} + +impl TendermintChain { + pub fn from_config(config: ChainConfig) -> Result { + let rpc_client = rpc::Client::new(config.rpc_addr.clone()); + Ok(Self { config, rpc_client }) + } +} + +impl Chain for TendermintChain { + type Type = Tendermint; + type ConsensusState = ConsensusState; + + fn config(&self) -> &ChainConfig { + &self.config + } + + fn rpc_client(&self) -> &rpc::Client { + &self.rpc_client + } +} diff --git a/relayer/relay/src/error.rs b/relayer/relay/src/error.rs index 96a631d52c..42c045d0b3 100644 --- a/relayer/relay/src/error.rs +++ b/relayer/relay/src/error.rs @@ -16,6 +16,9 @@ pub enum Kind { /// Invalid configuration #[error("invalid configuration")] Config, + + #[error("RPC error")] + Rpc, } impl Kind { diff --git a/relayer/relay/src/lib.rs b/relayer/relay/src/lib.rs index 1e1b978712..fdf18567e8 100644 --- a/relayer/relay/src/lib.rs +++ b/relayer/relay/src/lib.rs @@ -11,5 +11,7 @@ //! IBC Relayer implementation +pub mod chain; pub mod config; pub mod error; +pub mod query; diff --git a/relayer/relay/src/query.rs b/relayer/relay/src/query.rs new file mode 100644 index 0000000000..634d6e6871 --- /dev/null +++ b/relayer/relay/src/query.rs @@ -0,0 +1,56 @@ +use tendermint::abci; +use tendermint::rpc::{self, endpoint::abci_query::AbciQuery}; + +use relayer_modules::Height; + +use crate::chain::Chain; + +mod client_consensus_state; +pub use client_consensus_state::*; + +pub trait IbcResponse: Sized {} + +pub trait IbcQuery { + type Response: IbcResponse; + + fn path(&self) -> abci::Path; + fn height(&self) -> Height; + fn prove(&self) -> bool; + fn data(&self) -> Vec; +} + +pub async fn ibc_query(chain: &C, query: Q) -> Result +where + C: Chain, + Q: IbcQuery, +{ + let abci_response = chain + .rpc_client() + .abci_query( + Some(query.path()), + query.data(), + Some(query.height().into()), + query.prove(), + ) + .await?; + + if !abci_response.code.is_ok() { + todo!() // fail with response log + } + + // Data from trusted node or subspace query doesn't need verification + if !is_query_store_with_proof(&query.path()) { + return Ok(abci_response); + } + + // TODO: Verify proof and return response + Ok(abci_response) +} + +/// Whether or not this path requires proof verification. +/// +/// is_query_store_with_proofxpects a format like ///, +/// where queryType must be "store" and subpath must be "key" to require a proof. +fn is_query_store_with_proof(_path: &abci::Path) -> bool { + todo!() +} diff --git a/relayer/relay/src/query/client_consensus_state.rs b/relayer/relay/src/query/client_consensus_state.rs new file mode 100644 index 0000000000..63cd890b8b --- /dev/null +++ b/relayer/relay/src/query/client_consensus_state.rs @@ -0,0 +1,117 @@ +use std::marker::PhantomData; + +use tendermint::abci; + +use relayer_modules::ics02_client::state::ConsensusState; +use relayer_modules::ics23_commitment::{CommitmentPath, CommitmentProof}; +use relayer_modules::ics24_host::client::ClientId; +use relayer_modules::path::{ConsensusStatePath, Path}; +use relayer_modules::Height; + +use super::{ibc_query, IbcQuery, IbcResponse}; +use crate::chain::Chain; +use crate::error; + +pub struct QueryClientConsensusState { + pub height: Height, + pub consensus_state_path: ConsensusStatePath, + marker: PhantomData, +} + +impl QueryClientConsensusState { + pub fn new(height: Height, client_id: ClientId) -> Self { + Self { + height, + consensus_state_path: ConsensusStatePath { client_id, height }, + marker: PhantomData, + } + } +} + +impl IbcQuery for QueryClientConsensusState +where + CS: ConsensusState, +{ + type Response = ConsensusStateResponse; + + fn path(&self) -> abci::Path { + "/store/ibc/key".parse().unwrap() + } + + fn height(&self) -> Height { + self.height + } + + fn prove(&self) -> bool { + true + } + + fn data(&self) -> Vec { + self.consensus_state_path.to_key().into() + } +} + +pub struct ConsensusStateResponse { + pub consensus_state: CS, + pub proof: CommitmentProof, + pub proof_path: CommitmentPath, + pub proof_height: Height, +} + +impl ConsensusStateResponse { + pub fn new( + client_id: ClientId, + consensus_state: CS, + abci_proof: abci::Proof, + proof_height: Height, + ) -> Self { + let proof = CommitmentProof::from_bytes(abci_proof.as_ref()); + let proof_path = + CommitmentPath::from_path(ConsensusStatePath::new(client_id, proof_height)); + + ConsensusStateResponse { + consensus_state, + proof, + proof_path, + proof_height, + } + } +} + +impl IbcResponse for ConsensusStateResponse +where + CS: ConsensusState, +{ + // fn from_bytes(_bytes: &[u8]) -> Result { + // todo!() + // } +} + +pub async fn query_client_consensus_state( + chain: &C, + client_id: ClientId, + height: Height, +) -> Result, error::Error> +where + C: Chain, +{ + let query = QueryClientConsensusState::::new(height, client_id.clone()); + + let response = ibc_query(chain, query) + .await + .map_err(|e| error::Kind::Rpc.context(e))?; + + // FIXME: Handle case where there is no value + let consensus_state = amino_unmarshal_binary_length_prefixed(&response.value.unwrap())?; + + Ok(ConsensusStateResponse::new( + client_id, + consensus_state, + response.proof.unwrap(), // FIXME: Handle case where there is no proof + response.height.into(), + )) +} + +fn amino_unmarshal_binary_length_prefixed(_bytes: &[u8]) -> Result { + todo!() +}