Skip to content

Commit 691b27f

Browse files
plaferromachu55a1n1
authored
Implement ValidationContext and ExecutionContext for connections (ICS-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 <[email protected]> Co-authored-by: Shoaib Ahmed <[email protected]>
1 parent 3464923 commit 691b27f

File tree

10 files changed

+961
-22
lines changed

10 files changed

+961
-22
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Implement `ValidationContext::validate` and `ExecutionContext::execute` for connections (ICS-3)
2+
([#251](https://github.com/cosmos/ibc-rs/issues/251))

crates/ibc/src/clients/ics07_tendermint/host_helpers.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::Height;
1313

1414
use tendermint::trust_threshold::TrustThresholdFraction as TendermintTrustThresholdFraction;
1515

16-
/// Provides an implementation of `ConnectionReader::validate_self_client` for
16+
/// Provides an implementation of `ValidationContext::validate_self_client` for
1717
/// Tendermint-based hosts.
1818
pub trait ValidateSelfClientContext {
1919
fn validate_self_client(&self, counterparty_client_state: Any) -> Result<(), ConnectionError> {

crates/ibc/src/core/context.rs

+37-13
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ use ibc_proto::google::protobuf::Any;
1010
use super::ics02_client::client_type::ClientType;
1111
use super::ics02_client::handler::{create_client, update_client, upgrade_client};
1212
use super::ics02_client::msgs::ClientMsg;
13+
use super::ics03_connection::handler::{
14+
conn_open_ack, conn_open_confirm, conn_open_init, conn_open_try,
15+
};
16+
use super::ics03_connection::msgs::ConnectionMsg;
1317
use super::ics24_host::path::{
1418
ClientConnectionsPath, ClientConsensusStatePath, ClientStatePath, ClientTypePath,
1519
CommitmentsPath, ConnectionsPath, ReceiptsPath,
@@ -88,21 +92,33 @@ impl std::error::Error for ContextError {
8892

8993
pub trait ValidationContext {
9094
/// Validation entrypoint.
91-
fn validate(&self, message: Any) -> Result<(), RouterError>
95+
fn validate(&self, message: MsgEnvelope) -> Result<(), RouterError>
9296
where
9397
Self: Sized,
9498
{
95-
let envelope: MsgEnvelope = message.try_into()?;
96-
97-
match envelope {
99+
match message {
98100
MsgEnvelope::ClientMsg(message) => match message {
99101
ClientMsg::CreateClient(message) => create_client::validate(self, message),
100102
ClientMsg::UpdateClient(message) => update_client::validate(self, message),
101103
ClientMsg::Misbehaviour(_message) => unimplemented!(),
102104
ClientMsg::UpgradeClient(message) => upgrade_client::validate(self, message),
103105
}
104106
.map_err(RouterError::ContextError),
105-
MsgEnvelope::ConnectionMsg(_message) => todo!(),
107+
MsgEnvelope::ConnectionMsg(message) => match message {
108+
ConnectionMsg::ConnectionOpenInit(message) => {
109+
conn_open_init::validate(self, message)
110+
}
111+
ConnectionMsg::ConnectionOpenTry(message) => {
112+
conn_open_try::validate(self, *message)
113+
}
114+
ConnectionMsg::ConnectionOpenAck(message) => {
115+
conn_open_ack::validate(self, *message)
116+
}
117+
ConnectionMsg::ConnectionOpenConfirm(ref message) => {
118+
conn_open_confirm::validate(self, message)
119+
}
120+
}
121+
.map_err(RouterError::ContextError),
106122
MsgEnvelope::ChannelMsg(_message) => todo!(),
107123
MsgEnvelope::PacketMsg(_message) => todo!(),
108124
}
@@ -163,8 +179,8 @@ pub trait ValidationContext {
163179
/// Returns the ConnectionEnd for the given identifier `conn_id`.
164180
fn connection_end(&self, conn_id: &ConnectionId) -> Result<ConnectionEnd, ContextError>;
165181

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

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

294310
pub trait ExecutionContext: ValidationContext {
295311
/// Execution entrypoint
296-
fn execute(&mut self, message: Any) -> Result<(), RouterError>
312+
fn execute(&mut self, message: MsgEnvelope) -> Result<(), RouterError>
297313
where
298314
Self: Sized,
299315
{
300-
let envelope: MsgEnvelope = message.try_into()?;
301-
302-
match envelope {
316+
match message {
303317
MsgEnvelope::ClientMsg(message) => match message {
304318
ClientMsg::CreateClient(message) => create_client::execute(self, message),
305319
ClientMsg::UpdateClient(message) => update_client::execute(self, message),
306320
ClientMsg::Misbehaviour(_message) => unimplemented!(),
307321
ClientMsg::UpgradeClient(message) => upgrade_client::execute(self, message),
308322
}
309323
.map_err(RouterError::ContextError),
310-
MsgEnvelope::ConnectionMsg(_message) => todo!(),
324+
MsgEnvelope::ConnectionMsg(message) => match message {
325+
ConnectionMsg::ConnectionOpenInit(message) => {
326+
conn_open_init::execute(self, message)
327+
}
328+
ConnectionMsg::ConnectionOpenTry(message) => conn_open_try::execute(self, *message),
329+
ConnectionMsg::ConnectionOpenAck(message) => conn_open_ack::execute(self, *message),
330+
ConnectionMsg::ConnectionOpenConfirm(ref message) => {
331+
conn_open_confirm::execute(self, message)
332+
}
333+
}
334+
.map_err(RouterError::ContextError),
311335
MsgEnvelope::ChannelMsg(_message) => todo!(),
312336
MsgEnvelope::PacketMsg(_message) => todo!(),
313337
}
@@ -370,7 +394,7 @@ pub trait ExecutionContext: ValidationContext {
370394
fn store_connection_to_client(
371395
&mut self,
372396
client_connections_path: ClientConnectionsPath,
373-
client_id: &ClientId,
397+
conn_id: &ConnectionId,
374398
) -> Result<(), ContextError>;
375399

376400
/// Called upon connection identifier creation (Init or Try process).

crates/ibc/src/core/handler.rs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use ibc_proto::google::protobuf::Any;
2+
3+
use super::{
4+
ics26_routing::{error::RouterError, msgs::MsgEnvelope},
5+
ExecutionContext, ValidationContext,
6+
};
7+
8+
/// Entrypoint which only performs message validation
9+
pub fn validate<Ctx>(ctx: &Ctx, message: Any) -> Result<(), RouterError>
10+
where
11+
Ctx: ValidationContext,
12+
{
13+
let envelope: MsgEnvelope = message.try_into()?;
14+
ctx.validate(envelope)
15+
}
16+
17+
/// Entrypoint which only performs message execution
18+
pub fn execute<Ctx>(ctx: &mut Ctx, message: Any) -> Result<(), RouterError>
19+
where
20+
Ctx: ExecutionContext,
21+
{
22+
let envelope: MsgEnvelope = message.try_into()?;
23+
ctx.execute(envelope)
24+
}

crates/ibc/src/core/ics03_connection/handler/conn_open_ack.rs

+218
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,206 @@
11
//! Protocol logic specific to processing ICS3 messages of type `MsgConnectionOpenAck`.
22
3+
use crate::core::context::ContextError;
34
use crate::core::ics03_connection::connection::{ConnectionEnd, Counterparty, State};
45
use crate::core::ics03_connection::context::ConnectionReader;
56
use crate::core::ics03_connection::error::ConnectionError;
67
use crate::core::ics03_connection::events::OpenAck;
78
use crate::core::ics03_connection::handler::ConnectionResult;
89
use crate::core::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck;
10+
use crate::core::ics24_host::identifier::ClientId;
11+
use crate::core::ics24_host::path::ConnectionsPath;
12+
use crate::core::{ExecutionContext, ValidationContext};
913
use crate::events::IbcEvent;
1014
use crate::handler::{HandlerOutput, HandlerResult};
1115
use crate::prelude::*;
1216

1317
use super::ConnectionIdState;
1418

19+
pub(crate) fn validate<Ctx>(ctx_a: &Ctx, msg: MsgConnectionOpenAck) -> Result<(), ContextError>
20+
where
21+
Ctx: ValidationContext,
22+
{
23+
let vars = LocalVars::new(ctx_a, &msg)?;
24+
validate_impl(ctx_a, &msg, &vars)
25+
}
26+
27+
fn validate_impl<Ctx>(
28+
ctx_a: &Ctx,
29+
msg: &MsgConnectionOpenAck,
30+
vars: &LocalVars,
31+
) -> Result<(), ContextError>
32+
where
33+
Ctx: ValidationContext,
34+
{
35+
let host_height = ctx_a.host_height().map_err(|_| ConnectionError::Other {
36+
description: "failed to get host height".to_string(),
37+
})?;
38+
if msg.consensus_height_of_a_on_b > host_height {
39+
return Err(ConnectionError::InvalidConsensusHeight {
40+
target_height: msg.consensus_height_of_a_on_b,
41+
current_height: host_height,
42+
}
43+
.into());
44+
}
45+
46+
ctx_a.validate_self_client(msg.client_state_of_a_on_b.clone())?;
47+
48+
if !(vars.conn_end_on_a.state_matches(&State::Init)
49+
&& vars.conn_end_on_a.versions().contains(&msg.version))
50+
{
51+
return Err(ConnectionError::ConnectionMismatch {
52+
connection_id: msg.conn_id_on_a.clone(),
53+
}
54+
.into());
55+
}
56+
57+
// Proof verification.
58+
{
59+
let client_state_of_b_on_a =
60+
ctx_a
61+
.client_state(vars.client_id_on_a())
62+
.map_err(|_| ConnectionError::Other {
63+
description: "failed to fetch client state".to_string(),
64+
})?;
65+
let consensus_state_of_b_on_a = ctx_a
66+
.consensus_state(vars.client_id_on_a(), msg.proofs_height_on_b)
67+
.map_err(|_| ConnectionError::Other {
68+
description: "failed to fetch client consensus state".to_string(),
69+
})?;
70+
71+
let prefix_on_a = ctx_a.commitment_prefix();
72+
let prefix_on_b = vars.conn_end_on_a.counterparty().prefix();
73+
74+
{
75+
let expected_conn_end_on_b = ConnectionEnd::new(
76+
State::TryOpen,
77+
vars.client_id_on_b().clone(),
78+
Counterparty::new(
79+
vars.client_id_on_a().clone(),
80+
Some(msg.conn_id_on_a.clone()),
81+
prefix_on_a,
82+
),
83+
vec![msg.version.clone()],
84+
vars.conn_end_on_a.delay_period(),
85+
);
86+
87+
client_state_of_b_on_a
88+
.verify_connection_state(
89+
msg.proofs_height_on_b,
90+
prefix_on_b,
91+
&msg.proof_conn_end_on_b,
92+
consensus_state_of_b_on_a.root(),
93+
&msg.conn_id_on_b,
94+
&expected_conn_end_on_b,
95+
)
96+
.map_err(ConnectionError::VerifyConnectionState)?;
97+
}
98+
99+
client_state_of_b_on_a
100+
.verify_client_full_state(
101+
msg.proofs_height_on_b,
102+
prefix_on_b,
103+
&msg.proof_client_state_of_a_on_b,
104+
consensus_state_of_b_on_a.root(),
105+
vars.client_id_on_b(),
106+
msg.client_state_of_a_on_b.clone(),
107+
)
108+
.map_err(|e| ConnectionError::ClientStateVerificationFailure {
109+
client_id: vars.client_id_on_a().clone(),
110+
client_error: e,
111+
})?;
112+
113+
let expected_consensus_state_of_a_on_b = ctx_a
114+
.host_consensus_state(msg.consensus_height_of_a_on_b)
115+
.map_err(|_| ConnectionError::Other {
116+
description: "failed to fetch host consensus state".to_string(),
117+
})?;
118+
119+
client_state_of_b_on_a
120+
.verify_client_consensus_state(
121+
msg.proofs_height_on_b,
122+
prefix_on_b,
123+
&msg.proof_consensus_state_of_a_on_b,
124+
consensus_state_of_b_on_a.root(),
125+
vars.client_id_on_b(),
126+
msg.consensus_height_of_a_on_b,
127+
expected_consensus_state_of_a_on_b.as_ref(),
128+
)
129+
.map_err(|e| ConnectionError::ConsensusStateVerificationFailure {
130+
height: msg.proofs_height_on_b,
131+
client_error: e,
132+
})?;
133+
}
134+
135+
Ok(())
136+
}
137+
138+
pub(crate) fn execute<Ctx>(ctx_a: &mut Ctx, msg: MsgConnectionOpenAck) -> Result<(), ContextError>
139+
where
140+
Ctx: ExecutionContext,
141+
{
142+
let vars = LocalVars::new(ctx_a, &msg)?;
143+
execute_impl(ctx_a, msg, vars)
144+
}
145+
146+
fn execute_impl<Ctx>(
147+
ctx_a: &mut Ctx,
148+
msg: MsgConnectionOpenAck,
149+
vars: LocalVars,
150+
) -> Result<(), ContextError>
151+
where
152+
Ctx: ExecutionContext,
153+
{
154+
ctx_a.emit_ibc_event(IbcEvent::OpenAckConnection(OpenAck::new(
155+
msg.conn_id_on_a.clone(),
156+
vars.client_id_on_a().clone(),
157+
msg.conn_id_on_b.clone(),
158+
vars.client_id_on_b().clone(),
159+
)));
160+
161+
ctx_a.log_message("success: conn_open_ack verification passed".to_string());
162+
163+
{
164+
let new_conn_end_on_a = {
165+
let mut counterparty = vars.conn_end_on_a.counterparty().clone();
166+
counterparty.connection_id = Some(msg.conn_id_on_b.clone());
167+
168+
let mut new_conn_end_on_a = vars.conn_end_on_a;
169+
new_conn_end_on_a.set_state(State::Open);
170+
new_conn_end_on_a.set_version(msg.version.clone());
171+
new_conn_end_on_a.set_counterparty(counterparty);
172+
new_conn_end_on_a
173+
};
174+
175+
ctx_a.store_connection(ConnectionsPath(msg.conn_id_on_a), &new_conn_end_on_a)?;
176+
}
177+
178+
Ok(())
179+
}
180+
181+
struct LocalVars {
182+
conn_end_on_a: ConnectionEnd,
183+
}
184+
185+
impl LocalVars {
186+
fn new<Ctx>(ctx_a: &Ctx, msg: &MsgConnectionOpenAck) -> Result<Self, ContextError>
187+
where
188+
Ctx: ValidationContext,
189+
{
190+
Ok(LocalVars {
191+
conn_end_on_a: ctx_a.connection_end(&msg.conn_id_on_a)?,
192+
})
193+
}
194+
195+
fn client_id_on_a(&self) -> &ClientId {
196+
self.conn_end_on_a.client_id()
197+
}
198+
199+
fn client_id_on_b(&self) -> &ClientId {
200+
self.conn_end_on_a.counterparty().client_id()
201+
}
202+
}
203+
15204
/// Per our convention, this message is processed on chain A.
16205
pub(crate) fn process(
17206
ctx_a: &dyn ConnectionReader,
@@ -139,6 +328,8 @@ pub(crate) fn process(
139328

140329
#[cfg(test)]
141330
mod tests {
331+
use crate::core::ics26_routing::msgs::MsgEnvelope;
332+
use crate::core::ValidationContext;
142333
use crate::prelude::*;
143334

144335
use core::str::FromStr;
@@ -267,6 +458,33 @@ mod tests {
267458
];
268459

269460
for test in tests {
461+
let res = ValidationContext::validate(
462+
&test.ctx,
463+
MsgEnvelope::ConnectionMsg(test.msg.clone()),
464+
);
465+
466+
match res {
467+
Ok(_) => {
468+
assert!(
469+
test.want_pass,
470+
"conn_open_ack: test passed but was supposed to fail for test: {}, \nparams {:?} {:?}",
471+
test.name,
472+
test.msg.clone(),
473+
test.ctx.clone()
474+
)
475+
}
476+
Err(e) => {
477+
assert!(
478+
!test.want_pass,
479+
"conn_open_ack: did not pass test: {}, \nparams {:?} {:?} error: {:?}",
480+
test.name,
481+
test.msg,
482+
test.ctx.clone(),
483+
e,
484+
);
485+
}
486+
}
487+
270488
let res = dispatch(&test.ctx, test.msg.clone());
271489
// Additionally check the events and the output objects in the result.
272490
match res {

0 commit comments

Comments
 (0)