Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ValidationContext and ExecutionContext for connections (ICS-3) #257

Merged
merged 32 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
739c50f
`ConnOpenInit::validate`
plafer Nov 22, 2022
79f17a8
Merge branch 'main' into plafer/251-validation-execution-ics3
plafer Nov 22, 2022
31ecda0
conn_open_init: execute
plafer Nov 22, 2022
5ad234e
conn_open_try `validate` and `execute`
plafer Nov 22, 2022
621adc5
conn_open_ack::validate
plafer Nov 22, 2022
aedcb28
conn_open_ack::execute
plafer Nov 22, 2022
c6cf9f2
conn_open_confirm::validate
plafer Nov 22, 2022
43009dc
conn_open_confirm::execute
plafer Nov 22, 2022
a39cfde
changelog
plafer Nov 22, 2022
f3620f2
LocalVars
plafer Nov 29, 2022
52fa8fd
validate_impl and execute_impl
plafer Nov 29, 2022
d28b6ae
Remove useless clone
plafer Dec 2, 2022
73c7393
Merge remote-tracking branch 'origin/main' into plafer/251-validation…
plafer Dec 2, 2022
feffb7e
fix ConnOpenInit::validate
plafer Dec 2, 2022
6d36e15
fix conn_open_ack (as in #272)
plafer Dec 2, 2022
08bf05a
conn_open_try: LocalVars
plafer Dec 2, 2022
e711a9a
conn_open_try validate/execute impl
plafer Dec 2, 2022
7556e4c
conn_open_ack LocalVars
plafer Dec 2, 2022
14fa3f8
conn_open_ack impl
plafer Dec 2, 2022
f13f480
Track code coverage with `cargo-llvm-cov` and codecov.io (#277)
romac Dec 2, 2022
3ff5d9a
Misbehaviour handling implementation (#215)
hu55a1n1 Dec 5, 2022
18b9006
Fix wrong main branch name in code coverage job (#280)
romac Dec 5, 2022
dc55479
Merge branch 'main' into plafer/251-validation-execution-ics3
plafer Dec 5, 2022
b9f3045
implement `ValidationContext` for `MockContext`
plafer Dec 5, 2022
9e66deb
conn_open_init: test validate()
plafer Dec 5, 2022
b81df87
Add `execute` entrypoint
plafer Dec 5, 2022
6dc1eb7
re-export validate and execute
plafer Dec 5, 2022
01673ae
test validate() in connection handlers
plafer Dec 5, 2022
5fd3661
Merge branch 'main' into plafer/251-validation-execution-ics3
plafer Dec 6, 2022
093cc9e
Use into() instead of ContextError directly
plafer Dec 6, 2022
a8eeff0
reexport ContextError
plafer Dec 6, 2022
b995322
fmt
plafer Dec 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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