Skip to content

Commit

Permalink
Implement ValidationContext and ExecutionContext for connections (IC…
Browse files Browse the repository at this point in the history
…S-3) (#257)

* `ConnOpenInit::validate`

* conn_open_init: execute

* conn_open_try `validate` and `execute`

* conn_open_ack::validate

* conn_open_ack::execute

* conn_open_confirm::validate

* conn_open_confirm::execute

* changelog

* LocalVars

* validate_impl and execute_impl

* Remove useless clone

* fix ConnOpenInit::validate

* fix conn_open_ack (as in #272)

* conn_open_try: LocalVars

* conn_open_try validate/execute impl

* conn_open_ack LocalVars

* conn_open_ack impl

* Track code coverage with `cargo-llvm-cov` and codecov.io (#277)

* Track code coverage with `cargo-llvm-cov`

* Add changelog entry

* Add coverage badge to the README

* Misbehaviour handling implementation (#215)

* Add ClientState::check_misbehaviour_and_update_state()

* Implement misbehaviour handler

* impl Protobuf<Any> for Misbehaviour

* Remove redundant definition of decode_header()

* Implement ChainId::with_version()

* Getters for Tm Misbehaviour

* Add missing checks for conversion from RawMisbehaviour

* Make TmClientState::with_frozen_height() infallible

* Implement TmClientState::check_misbehaviour_and_update_state()

* Cleanup inner functions

* Cleanup errors

* Clippy fix

* Ctor for TmMisbehaviour

* Use git dependencies for tendermint crates

* Add VerifyCommitLightTrusting check

* Add VerifyCommit check

* Clippy fix

* Patch tendermint deps for no-std-check

* Convert Tendermint VerificationError

* Add helpers `Header::as_{un}trusted_block_state()`

* Reorder untrusted verification logic

* Reorder trusted verification logic

* Misbehavior -> misbehaviour

* Check for matching chain-ids

* cargo update ci/no-std-check

* Fix build failure after merge with main

* Update for API changes in tm PR

* Delete ci/no-std-check/Cargo.lock

* Add changelog entry

* Cleanup (naming & comments)

* Rename check_trusted_header() -> check_header_validator_set()

* Rename check_misbehaviour_header() -> check_header_and_validator_set()

* Rename         MisbehaviourConsensusStateTimestampGteTrustingPeriod
 -> ConsensusStateTimestampGteTrustingPeriod

* Rename verify_misbehaviour_header_commit() -> verify_header_commit_against_trusted()

* Remove redundant client state expired check

* Impl Protobuf conversions for mock Misbehaviour

* Impl check_misbehaviour_and_update_state() for mock Misbehaviour

* Remove cargo patches

* Fixes after tendermint-rs bump

* Fix typo

* Add tests

* MockClientState::with_frozen_height()

* Provide MockContext helper to set client chain-id

* Conversions from HostBlock -> TmLightBlock -> TmHeader

* Fix tests

* Clippy fix

* Cleanup tests

* Add comments for tests

* Clippy fix

* Rustfmt

* Fix wrong main branch name in code coverage job (#280)

* implement `ValidationContext` for `MockContext`

* conn_open_init: test validate()

* Add `execute` entrypoint

* re-export validate and execute

* test validate() in connection handlers

* Use into() instead of ContextError directly

* reexport ContextError

* fmt

Co-authored-by: Romain Ruetschi <romain@informal.systems>
Co-authored-by: Shoaib Ahmed <sufialhussaini@gmail.com>
  • Loading branch information
3 people authored Dec 6, 2022
1 parent 4848db0 commit f887725
Show file tree
Hide file tree
Showing 10 changed files with 961 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Implement `ValidationContext::validate` and `ExecutionContext::execute` for connections (ICS-3)
([#251](https://github.com/cosmos/ibc-rs/issues/251))
2 changes: 1 addition & 1 deletion crates/ibc/src/clients/ics07_tendermint/host_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::Height;

use tendermint::trust_threshold::TrustThresholdFraction as TendermintTrustThresholdFraction;

/// Provides an implementation of `ConnectionReader::validate_self_client` for
/// Provides an implementation of `ValidationContext::validate_self_client` for
/// Tendermint-based hosts.
pub trait ValidateSelfClientContext {
fn validate_self_client(&self, counterparty_client_state: Any) -> Result<(), ConnectionError> {
Expand Down
50 changes: 37 additions & 13 deletions crates/ibc/src/core/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ use ibc_proto::google::protobuf::Any;
use super::ics02_client::client_type::ClientType;
use super::ics02_client::handler::{create_client, update_client, upgrade_client};
use super::ics02_client::msgs::ClientMsg;
use super::ics03_connection::handler::{
conn_open_ack, conn_open_confirm, conn_open_init, conn_open_try,
};
use super::ics03_connection::msgs::ConnectionMsg;
use super::ics24_host::path::{
ClientConnectionsPath, ClientConsensusStatePath, ClientStatePath, ClientTypePath,
CommitmentsPath, ConnectionsPath, ReceiptsPath,
Expand Down Expand Up @@ -88,21 +92,33 @@ impl std::error::Error for ContextError {

pub trait ValidationContext {
/// Validation entrypoint.
fn validate(&self, message: Any) -> Result<(), RouterError>
fn validate(&self, message: MsgEnvelope) -> Result<(), RouterError>
where
Self: Sized,
{
let envelope: MsgEnvelope = message.try_into()?;

match envelope {
match message {
MsgEnvelope::ClientMsg(message) => match message {
ClientMsg::CreateClient(message) => create_client::validate(self, message),
ClientMsg::UpdateClient(message) => update_client::validate(self, message),
ClientMsg::Misbehaviour(_message) => unimplemented!(),
ClientMsg::UpgradeClient(message) => upgrade_client::validate(self, message),
}
.map_err(RouterError::ContextError),
MsgEnvelope::ConnectionMsg(_message) => todo!(),
MsgEnvelope::ConnectionMsg(message) => match message {
ConnectionMsg::ConnectionOpenInit(message) => {
conn_open_init::validate(self, message)
}
ConnectionMsg::ConnectionOpenTry(message) => {
conn_open_try::validate(self, *message)
}
ConnectionMsg::ConnectionOpenAck(message) => {
conn_open_ack::validate(self, *message)
}
ConnectionMsg::ConnectionOpenConfirm(ref message) => {
conn_open_confirm::validate(self, message)
}
}
.map_err(RouterError::ContextError),
MsgEnvelope::ChannelMsg(_message) => todo!(),
MsgEnvelope::PacketMsg(_message) => todo!(),
}
Expand Down Expand Up @@ -163,8 +179,8 @@ pub trait ValidationContext {
/// Returns the ConnectionEnd for the given identifier `conn_id`.
fn connection_end(&self, conn_id: &ConnectionId) -> Result<ConnectionEnd, ContextError>;

/// Returns the oldest height available on the local chain.
fn host_oldest_height(&self) -> Height;
/// Validates the `ClientState` of the client on the counterparty chain.
fn validate_self_client(&self, counterparty_client_state: Any) -> Result<(), ConnectionError>;

/// Returns the prefix that the local chain uses in the KV store.
fn commitment_prefix(&self) -> CommitmentPrefix;
Expand Down Expand Up @@ -293,21 +309,29 @@ pub trait ValidationContext {

pub trait ExecutionContext: ValidationContext {
/// Execution entrypoint
fn execute(&mut self, message: Any) -> Result<(), RouterError>
fn execute(&mut self, message: MsgEnvelope) -> Result<(), RouterError>
where
Self: Sized,
{
let envelope: MsgEnvelope = message.try_into()?;

match envelope {
match message {
MsgEnvelope::ClientMsg(message) => match message {
ClientMsg::CreateClient(message) => create_client::execute(self, message),
ClientMsg::UpdateClient(message) => update_client::execute(self, message),
ClientMsg::Misbehaviour(_message) => unimplemented!(),
ClientMsg::UpgradeClient(message) => upgrade_client::execute(self, message),
}
.map_err(RouterError::ContextError),
MsgEnvelope::ConnectionMsg(_message) => todo!(),
MsgEnvelope::ConnectionMsg(message) => match message {
ConnectionMsg::ConnectionOpenInit(message) => {
conn_open_init::execute(self, message)
}
ConnectionMsg::ConnectionOpenTry(message) => conn_open_try::execute(self, *message),
ConnectionMsg::ConnectionOpenAck(message) => conn_open_ack::execute(self, *message),
ConnectionMsg::ConnectionOpenConfirm(ref message) => {
conn_open_confirm::execute(self, message)
}
}
.map_err(RouterError::ContextError),
MsgEnvelope::ChannelMsg(_message) => todo!(),
MsgEnvelope::PacketMsg(_message) => todo!(),
}
Expand Down Expand Up @@ -370,7 +394,7 @@ pub trait ExecutionContext: ValidationContext {
fn store_connection_to_client(
&mut self,
client_connections_path: ClientConnectionsPath,
client_id: &ClientId,
conn_id: &ConnectionId,
) -> Result<(), ContextError>;

/// Called upon connection identifier creation (Init or Try process).
Expand Down
24 changes: 24 additions & 0 deletions crates/ibc/src/core/handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use ibc_proto::google::protobuf::Any;

use super::{
ics26_routing::{error::RouterError, msgs::MsgEnvelope},
ExecutionContext, ValidationContext,
};

/// Entrypoint which only performs message validation
pub fn validate<Ctx>(ctx: &Ctx, message: Any) -> Result<(), RouterError>
where
Ctx: ValidationContext,
{
let envelope: MsgEnvelope = message.try_into()?;
ctx.validate(envelope)
}

/// Entrypoint which only performs message execution
pub fn execute<Ctx>(ctx: &mut Ctx, message: Any) -> Result<(), RouterError>
where
Ctx: ExecutionContext,
{
let envelope: MsgEnvelope = message.try_into()?;
ctx.execute(envelope)
}
218 changes: 218 additions & 0 deletions crates/ibc/src/core/ics03_connection/handler/conn_open_ack.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,206 @@
//! Protocol logic specific to processing ICS3 messages of type `MsgConnectionOpenAck`.
use crate::core::context::ContextError;
use crate::core::ics03_connection::connection::{ConnectionEnd, Counterparty, State};
use crate::core::ics03_connection::context::ConnectionReader;
use crate::core::ics03_connection::error::ConnectionError;
use crate::core::ics03_connection::events::OpenAck;
use crate::core::ics03_connection::handler::ConnectionResult;
use crate::core::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck;
use crate::core::ics24_host::identifier::ClientId;
use crate::core::ics24_host::path::ConnectionsPath;
use crate::core::{ExecutionContext, ValidationContext};
use crate::events::IbcEvent;
use crate::handler::{HandlerOutput, HandlerResult};
use crate::prelude::*;

use super::ConnectionIdState;

pub(crate) fn validate<Ctx>(ctx_a: &Ctx, msg: MsgConnectionOpenAck) -> Result<(), ContextError>
where
Ctx: ValidationContext,
{
let vars = LocalVars::new(ctx_a, &msg)?;
validate_impl(ctx_a, &msg, &vars)
}

fn validate_impl<Ctx>(
ctx_a: &Ctx,
msg: &MsgConnectionOpenAck,
vars: &LocalVars,
) -> Result<(), ContextError>
where
Ctx: ValidationContext,
{
let host_height = ctx_a.host_height().map_err(|_| ConnectionError::Other {
description: "failed to get host height".to_string(),
})?;
if msg.consensus_height_of_a_on_b > host_height {
return Err(ConnectionError::InvalidConsensusHeight {
target_height: msg.consensus_height_of_a_on_b,
current_height: host_height,
}
.into());
}

ctx_a.validate_self_client(msg.client_state_of_a_on_b.clone())?;

if !(vars.conn_end_on_a.state_matches(&State::Init)
&& vars.conn_end_on_a.versions().contains(&msg.version))
{
return Err(ConnectionError::ConnectionMismatch {
connection_id: msg.conn_id_on_a.clone(),
}
.into());
}

// Proof verification.
{
let client_state_of_b_on_a =
ctx_a
.client_state(vars.client_id_on_a())
.map_err(|_| ConnectionError::Other {
description: "failed to fetch client state".to_string(),
})?;
let consensus_state_of_b_on_a = ctx_a
.consensus_state(vars.client_id_on_a(), msg.proofs_height_on_b)
.map_err(|_| ConnectionError::Other {
description: "failed to fetch client consensus state".to_string(),
})?;

let prefix_on_a = ctx_a.commitment_prefix();
let prefix_on_b = vars.conn_end_on_a.counterparty().prefix();

{
let expected_conn_end_on_b = ConnectionEnd::new(
State::TryOpen,
vars.client_id_on_b().clone(),
Counterparty::new(
vars.client_id_on_a().clone(),
Some(msg.conn_id_on_a.clone()),
prefix_on_a,
),
vec![msg.version.clone()],
vars.conn_end_on_a.delay_period(),
);

client_state_of_b_on_a
.verify_connection_state(
msg.proofs_height_on_b,
prefix_on_b,
&msg.proof_conn_end_on_b,
consensus_state_of_b_on_a.root(),
&msg.conn_id_on_b,
&expected_conn_end_on_b,
)
.map_err(ConnectionError::VerifyConnectionState)?;
}

client_state_of_b_on_a
.verify_client_full_state(
msg.proofs_height_on_b,
prefix_on_b,
&msg.proof_client_state_of_a_on_b,
consensus_state_of_b_on_a.root(),
vars.client_id_on_b(),
msg.client_state_of_a_on_b.clone(),
)
.map_err(|e| ConnectionError::ClientStateVerificationFailure {
client_id: vars.client_id_on_a().clone(),
client_error: e,
})?;

let expected_consensus_state_of_a_on_b = ctx_a
.host_consensus_state(msg.consensus_height_of_a_on_b)
.map_err(|_| ConnectionError::Other {
description: "failed to fetch host consensus state".to_string(),
})?;

client_state_of_b_on_a
.verify_client_consensus_state(
msg.proofs_height_on_b,
prefix_on_b,
&msg.proof_consensus_state_of_a_on_b,
consensus_state_of_b_on_a.root(),
vars.client_id_on_b(),
msg.consensus_height_of_a_on_b,
expected_consensus_state_of_a_on_b.as_ref(),
)
.map_err(|e| ConnectionError::ConsensusStateVerificationFailure {
height: msg.proofs_height_on_b,
client_error: e,
})?;
}

Ok(())
}

pub(crate) fn execute<Ctx>(ctx_a: &mut Ctx, msg: MsgConnectionOpenAck) -> Result<(), ContextError>
where
Ctx: ExecutionContext,
{
let vars = LocalVars::new(ctx_a, &msg)?;
execute_impl(ctx_a, msg, vars)
}

fn execute_impl<Ctx>(
ctx_a: &mut Ctx,
msg: MsgConnectionOpenAck,
vars: LocalVars,
) -> Result<(), ContextError>
where
Ctx: ExecutionContext,
{
ctx_a.emit_ibc_event(IbcEvent::OpenAckConnection(OpenAck::new(
msg.conn_id_on_a.clone(),
vars.client_id_on_a().clone(),
msg.conn_id_on_b.clone(),
vars.client_id_on_b().clone(),
)));

ctx_a.log_message("success: conn_open_ack verification passed".to_string());

{
let new_conn_end_on_a = {
let mut counterparty = vars.conn_end_on_a.counterparty().clone();
counterparty.connection_id = Some(msg.conn_id_on_b.clone());

let mut new_conn_end_on_a = vars.conn_end_on_a;
new_conn_end_on_a.set_state(State::Open);
new_conn_end_on_a.set_version(msg.version.clone());
new_conn_end_on_a.set_counterparty(counterparty);
new_conn_end_on_a
};

ctx_a.store_connection(ConnectionsPath(msg.conn_id_on_a), &new_conn_end_on_a)?;
}

Ok(())
}

struct LocalVars {
conn_end_on_a: ConnectionEnd,
}

impl LocalVars {
fn new<Ctx>(ctx_a: &Ctx, msg: &MsgConnectionOpenAck) -> Result<Self, ContextError>
where
Ctx: ValidationContext,
{
Ok(LocalVars {
conn_end_on_a: ctx_a.connection_end(&msg.conn_id_on_a)?,
})
}

fn client_id_on_a(&self) -> &ClientId {
self.conn_end_on_a.client_id()
}

fn client_id_on_b(&self) -> &ClientId {
self.conn_end_on_a.counterparty().client_id()
}
}

/// Per our convention, this message is processed on chain A.
pub(crate) fn process(
ctx_a: &dyn ConnectionReader,
Expand Down Expand Up @@ -139,6 +328,8 @@ pub(crate) fn process(

#[cfg(test)]
mod tests {
use crate::core::ics26_routing::msgs::MsgEnvelope;
use crate::core::ValidationContext;
use crate::prelude::*;

use core::str::FromStr;
Expand Down Expand Up @@ -267,6 +458,33 @@ mod tests {
];

for test in tests {
let res = ValidationContext::validate(
&test.ctx,
MsgEnvelope::ConnectionMsg(test.msg.clone()),
);

match res {
Ok(_) => {
assert!(
test.want_pass,
"conn_open_ack: test passed but was supposed to fail for test: {}, \nparams {:?} {:?}",
test.name,
test.msg.clone(),
test.ctx.clone()
)
}
Err(e) => {
assert!(
!test.want_pass,
"conn_open_ack: did not pass test: {}, \nparams {:?} {:?} error: {:?}",
test.name,
test.msg,
test.ctx.clone(),
e,
);
}
}

let res = dispatch(&test.ctx, test.msg.clone());
// Additionally check the events and the output objects in the result.
match res {
Expand Down
Loading

0 comments on commit f887725

Please sign in to comment.