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 9 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<(), Error> {
Expand Down
41 changes: 36 additions & 5 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 @@ -53,7 +57,21 @@ pub trait ValidationContext {
ClientMsg::UpgradeClient(message) => upgrade_client::validate(self, message),
}
.map_err(RouterError::ics02_client),
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(message) => {
conn_open_confirm::validate(self, message)
}
}
.map_err(RouterError::ics03_connection),
MsgEnvelope::ChannelMsg(_message) => todo!(),
MsgEnvelope::PacketMsg(_message) => todo!(),
}
Expand Down Expand Up @@ -111,16 +129,19 @@ pub trait ValidationContext {
fn client_counter(&self) -> Result<u64, ClientError>;

/// Returns the ConnectionEnd for the given identifier `conn_id`.
fn connection_end(&self, conn_id: &ConnectionId) -> Result<ConnectionEnd, ClientError>;
fn connection_end(&self, conn_id: &ConnectionId) -> Result<ConnectionEnd, ConnectionError>;

/// 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;

/// Returns a counter on how many connections have been created thus far.
fn connection_counter(&self) -> Result<u64, ClientError>;
fn connection_counter(&self) -> Result<u64, ConnectionError>;

/// Function required by ICS 03. Returns the list of all possible versions that the connection
/// handshake protocol supports.
Expand Down Expand Up @@ -256,7 +277,17 @@ pub trait ExecutionContext: ValidationContext {
ClientMsg::UpgradeClient(message) => upgrade_client::execute(self, message),
}
.map_err(RouterError::ics02_client),
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(message) => {
conn_open_confirm::execute(self, message)
}
}
.map_err(RouterError::ics03_connection),
MsgEnvelope::ChannelMsg(_message) => todo!(),
MsgEnvelope::PacketMsg(_message) => todo!(),
}
Expand Down Expand Up @@ -319,7 +350,7 @@ pub trait ExecutionContext: ValidationContext {
fn store_connection_to_client(
&mut self,
client_connections_path: ClientConnectionsPath,
client_id: &ClientId,
conn_id: &ConnectionId,
) -> Result<(), ConnectionError>;

/// Called upon connection identifier creation (Init or Try process).
Expand Down
143 changes: 143 additions & 0 deletions crates/ibc/src/core/ics03_connection/handler/conn_open_ack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,155 @@ use crate::core::ics03_connection::error::Error;
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::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<(), Error>
where
Ctx: ValidationContext,
{
let host_height = ctx_a
.host_height()
.map_err(|_| Error::other("failed to get host height".to_string()))?;
if msg.consensus_height_of_a_on_b > host_height {
return Err(Error::invalid_consensus_height(
msg.consensus_height_of_a_on_b,
host_height,
));
}

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

let conn_end_on_a = ctx_a.connection_end(&msg.conn_id_on_a)?;
if !(conn_end_on_a.state_matches(&State::Init)
&& conn_end_on_a.versions().contains(&msg.version))
{
return Err(Error::connection_mismatch(msg.conn_id_on_a));
}

let client_id_on_a = conn_end_on_a.client_id();
let client_id_on_b = conn_end_on_a.counterparty().client_id();

let conn_id_on_b = conn_end_on_a
.counterparty()
.connection_id()
.ok_or_else(Error::invalid_counterparty)?;

// Proof verification.
{
let client_state_of_b_on_a = ctx_a
.client_state(client_id_on_a)
.map_err(|_| Error::other("failed to fetch client state".to_string()))?;
let consensus_state_of_b_on_a = ctx_a
.consensus_state(conn_end_on_a.client_id(), msg.proofs_height_on_b)
.map_err(|_| Error::other("failed to fetch client consensus state".to_string()))?;

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

{
let expected_conn_end_on_b = ConnectionEnd::new(
State::TryOpen,
client_id_on_b.clone(),
Counterparty::new(
client_id_on_a.clone(),
Some(msg.conn_id_on_a.clone()),
prefix_on_a,
),
vec![msg.version.clone()],
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(),
conn_id_on_b,
&expected_conn_end_on_b,
)
.map_err(Error::verify_connection_state)?;
}

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(),
client_id_on_b,
msg.client_state_of_a_on_b,
)
.map_err(|e| {
Error::client_state_verification_failure(conn_end_on_a.client_id().clone(), e)
})?;

let expected_consensus_state_of_a_on_b = ctx_a
.host_consensus_state(msg.consensus_height_of_a_on_b)
.map_err(|_| Error::other("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(),
conn_end_on_a.counterparty().client_id(),
msg.consensus_height_of_a_on_b,
expected_consensus_state_of_a_on_b.as_ref(),
)
.map_err(|e| Error::consensus_state_verification_failure(msg.proofs_height_on_b, e))?;
}

Ok(())
}

pub(crate) fn execute<Ctx>(ctx_a: &mut Ctx, msg: MsgConnectionOpenAck) -> Result<(), Error>
where
Ctx: ExecutionContext,
{
let conn_end_on_a = ctx_a.connection_end(&msg.conn_id_on_a)?;
let client_id_on_a = conn_end_on_a.client_id();
let client_id_on_b = conn_end_on_a.counterparty().client_id();

let conn_id_on_b = conn_end_on_a
.counterparty()
.connection_id()
.ok_or_else(Error::invalid_counterparty)?;

ctx_a.emit_ibc_event(IbcEvent::OpenAckConnection(OpenAck::new(
msg.conn_id_on_a.clone(),
client_id_on_a.clone(),
conn_id_on_b.clone(),
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 = conn_end_on_a.counterparty().clone();
counterparty.connection_id = Some(msg.conn_id_on_b.clone());

let mut new_conn_end_on_a = 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(())
}

/// Per our convention, this message is processed on chain A.
pub(crate) fn process(
ctx_a: &dyn ConnectionReader,
Expand Down
95 changes: 93 additions & 2 deletions crates/ibc/src/core/ics03_connection/handler/conn_open_confirm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,101 @@ use crate::core::ics03_connection::error::Error;
use crate::core::ics03_connection::events::OpenConfirm;
use crate::core::ics03_connection::handler::{ConnectionIdState, ConnectionResult};
use crate::core::ics03_connection::msgs::conn_open_confirm::MsgConnectionOpenConfirm;
use crate::core::ics24_host::path::ConnectionsPath;
use crate::core::{ExecutionContext, ValidationContext};
use crate::events::IbcEvent;
use crate::handler::{HandlerOutput, HandlerResult};
use crate::prelude::*;

pub(crate) fn validate<Ctx>(ctx_b: &Ctx, msg: MsgConnectionOpenConfirm) -> Result<(), Error>
plafer marked this conversation as resolved.
Show resolved Hide resolved
where
Ctx: ValidationContext,
{
let conn_end_on_b = ctx_b.connection_end(&msg.conn_id_on_b)?;
if !conn_end_on_b.state_matches(&State::TryOpen) {
return Err(Error::connection_mismatch(msg.conn_id_on_b));
}

let client_id_on_a = conn_end_on_b.counterparty().client_id();
let client_id_on_b = conn_end_on_b.client_id();
let conn_id_on_a = conn_end_on_b
.counterparty()
.connection_id()
.ok_or_else(Error::invalid_counterparty)?;

// Verify proofs
{
let client_state_of_a_on_b = ctx_b
.client_state(client_id_on_b)
.map_err(|_| Error::other("failed to fetch client state".to_string()))?;
let consensus_state_of_a_on_b = ctx_b
.consensus_state(client_id_on_b, msg.proof_height_on_a)
.map_err(|_| Error::other("failed to fetch client consensus state".to_string()))?;

let prefix_on_a = conn_end_on_b.counterparty().prefix();
let prefix_on_b = ctx_b.commitment_prefix();

let expected_conn_end_on_a = ConnectionEnd::new(
State::Open,
client_id_on_a.clone(),
Counterparty::new(
client_id_on_b.clone(),
Some(msg.conn_id_on_b.clone()),
prefix_on_b,
),
conn_end_on_b.versions().to_vec(),
conn_end_on_b.delay_period(),
);

client_state_of_a_on_b
.verify_connection_state(
msg.proof_height_on_a,
prefix_on_a,
&msg.proof_conn_end_on_a,
consensus_state_of_a_on_b.root(),
conn_id_on_a,
&expected_conn_end_on_a,
)
.map_err(Error::verify_connection_state)?;
}

Ok(())
}

pub(crate) fn execute<Ctx>(ctx_b: &mut Ctx, msg: MsgConnectionOpenConfirm) -> Result<(), Error>
where
Ctx: ExecutionContext,
{
let conn_end_on_b = ctx_b.connection_end(&msg.conn_id_on_b)?;
let client_id_on_a = conn_end_on_b.counterparty().client_id();
let client_id_on_b = conn_end_on_b.client_id();
let conn_id_on_a = conn_end_on_b
.counterparty()
.connection_id()
.ok_or_else(Error::invalid_counterparty)?;

ctx_b.emit_ibc_event(IbcEvent::OpenConfirmConnection(OpenConfirm::new(
msg.conn_id_on_b.clone(),
client_id_on_b.clone(),
conn_id_on_a.clone(),
client_id_on_a.clone(),
)));
ctx_b.log_message("success: conn_open_confirm verification passed".to_string());

{
let new_conn_end_on_b = {
let mut new_conn_end_on_b = conn_end_on_b;

new_conn_end_on_b.set_state(State::Open);
new_conn_end_on_b
};

ctx_b.store_connection(ConnectionsPath(msg.conn_id_on_b), &new_conn_end_on_b)?;
}

Ok(())
}

/// Per our convention, this message is processed on chain B.
pub(crate) fn process(
ctx_b: &dyn ConnectionReader,
Expand All @@ -30,9 +121,9 @@ pub(crate) fn process(

// Verify proofs
{
let client_state_of_a_on_b = ctx_b.client_state(conn_end_on_b.client_id())?;
let client_state_of_a_on_b = ctx_b.client_state(client_id_on_b)?;
let consensus_state_of_a_on_b =
ctx_b.client_consensus_state(conn_end_on_b.client_id(), msg.proof_height_on_a)?;
ctx_b.client_consensus_state(client_id_on_b, msg.proof_height_on_a)?;

let prefix_on_a = conn_end_on_b.counterparty().prefix();
let prefix_on_b = ctx_b.commitment_prefix();
Expand Down
Loading