Skip to content

Commit

Permalink
Implement QueryClientConsensusState IBC query (informalsystems#23)
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
romac authored Mar 9, 2020
1 parent e6e9cb5 commit 48e321a
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 3 deletions.
5 changes: 3 additions & 2 deletions relayer/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 3 additions & 1 deletion relayer/relay/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
15 changes: 15 additions & 0 deletions relayer/relay/src/chain.rs
Original file line number Diff line number Diff line change
@@ -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?
}
34 changes: 34 additions & 0 deletions relayer/relay/src/chain/tendermint.rs
Original file line number Diff line number Diff line change
@@ -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<Self, error::Error> {
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
}
}
3 changes: 3 additions & 0 deletions relayer/relay/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ pub enum Kind {
/// Invalid configuration
#[error("invalid configuration")]
Config,

#[error("RPC error")]
Rpc,
}

impl Kind {
Expand Down
2 changes: 2 additions & 0 deletions relayer/relay/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@

//! IBC Relayer implementation

pub mod chain;
pub mod config;
pub mod error;
pub mod query;
56 changes: 56 additions & 0 deletions relayer/relay/src/query.rs
Original file line number Diff line number Diff line change
@@ -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<u8>;
}

pub async fn ibc_query<C, Q>(chain: &C, query: Q) -> Result<AbciQuery, rpc::Error>
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 /<queryType>/<storeName>/<subpath>,
/// 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!()
}
117 changes: 117 additions & 0 deletions relayer/relay/src/query/client_consensus_state.rs
Original file line number Diff line number Diff line change
@@ -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<CS> {
pub height: Height,
pub consensus_state_path: ConsensusStatePath,
marker: PhantomData<CS>,
}

impl<CS> QueryClientConsensusState<CS> {
pub fn new(height: Height, client_id: ClientId) -> Self {
Self {
height,
consensus_state_path: ConsensusStatePath { client_id, height },
marker: PhantomData,
}
}
}

impl<CS> IbcQuery for QueryClientConsensusState<CS>
where
CS: ConsensusState,
{
type Response = ConsensusStateResponse<CS>;

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<u8> {
self.consensus_state_path.to_key().into()
}
}

pub struct ConsensusStateResponse<CS> {
pub consensus_state: CS,
pub proof: CommitmentProof,
pub proof_path: CommitmentPath,
pub proof_height: Height,
}

impl<CS> ConsensusStateResponse<CS> {
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<CS> IbcResponse for ConsensusStateResponse<CS>
where
CS: ConsensusState,
{
// fn from_bytes(_bytes: &[u8]) -> Result<Self, rpc::Error> {
// todo!()
// }
}

pub async fn query_client_consensus_state<C>(
chain: &C,
client_id: ClientId,
height: Height,
) -> Result<ConsensusStateResponse<C::ConsensusState>, error::Error>
where
C: Chain,
{
let query = QueryClientConsensusState::<C::ConsensusState>::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<T>(_bytes: &[u8]) -> Result<T, error::Error> {
todo!()
}

0 comments on commit 48e321a

Please sign in to comment.