Skip to content

Commit 8feb8dc

Browse files
authored
Implement channel query and ChanOpenInit message (#65)
* Add channel module and query * Add MsgChanOpenInit message * Added more tests and error handling Use of 'map_err' to convert errors when parsing Return Resuls for constructor functions for channel and endpoint, and perform validation * Fixed connection hops validation Fixed some clippy errors Allow too_many_arguments for message parameters * review comments
1 parent fee17f8 commit 8feb8dc

File tree

26 files changed

+997
-55
lines changed

26 files changed

+997
-55
lines changed

modules/src/ics02_client/msgs.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::client_type::ClientType;
22
use super::header::Header;
33
use super::state::ConsensusState;
4-
use crate::ics24_host::client::ClientId;
4+
use crate::ics24_host::identifier::ClientId;
55

66
// FIXME: Remove Header associated type and use Box<dyn tendermint::lite::Header> instead of
77
// Self::Header?

modules/src/ics02_client/query.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::ics23_commitment::{CommitmentPath, CommitmentProof};
77

88
use crate::error;
99
use crate::ics02_client::state::{ClientState, ConsensusState};
10-
use crate::ics24_host::client::ClientId;
10+
use crate::ics24_host::identifier::ClientId;
1111
use crate::path::{ClientStatePath, ConsensusStatePath, Path};
1212
use crate::query::{IbcQuery, IbcResponse};
1313
use crate::Height;

modules/src/ics02_client/state.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::client_type::ClientType;
22

33
use crate::ics23_commitment::CommitmentRoot;
4-
use crate::ics24_host::client::ClientId;
4+
use crate::ics24_host::identifier::ClientId;
55
use crate::Height;
66

77
pub trait ConsensusState {

modules/src/ics04_channel/channel.rs

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use super::exported::*;
2+
use crate::ics04_channel::error;
3+
use crate::ics04_channel::error::Kind;
4+
use crate::ics24_host::identifier::{ChannelId, ConnectionId, PortId};
5+
use serde_derive::{Deserialize, Serialize};
6+
7+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
8+
pub struct Channel {
9+
state: State,
10+
ordering: Order,
11+
remote: Endpoint,
12+
connection_hops: Vec<ConnectionId>,
13+
version: String,
14+
}
15+
16+
impl Channel {
17+
pub fn new(
18+
ordering: Order,
19+
remote: Endpoint,
20+
connection_hops: Vec<ConnectionId>,
21+
version: String,
22+
) -> Self {
23+
Self {
24+
state: State::Uninitialized,
25+
ordering,
26+
remote,
27+
connection_hops,
28+
version,
29+
}
30+
}
31+
}
32+
33+
impl ChannelI for Channel {
34+
type ValidationError = crate::ics04_channel::error::Error;
35+
36+
fn state(&self) -> State {
37+
self.state.clone()
38+
}
39+
40+
fn ordering(&self) -> Order {
41+
self.ordering.clone()
42+
}
43+
44+
fn counterparty(&self) -> Box<dyn CounterpartyI<ValidationError = Self::ValidationError>> {
45+
Box::new(self.remote.clone())
46+
}
47+
48+
fn connection_hops(&self) -> Vec<ConnectionId> {
49+
self.connection_hops.clone()
50+
}
51+
52+
fn version(&self) -> String {
53+
self.version.parse().unwrap()
54+
}
55+
56+
fn validate_basic(&self) -> Result<(), Self::ValidationError> {
57+
if self.connection_hops.len() != 1 {
58+
return Err(error::Kind::InvalidConnectionHopsLength
59+
.context("validate channel")
60+
.into());
61+
}
62+
if self.version().trim() == "" {
63+
return Err(error::Kind::InvalidVersion
64+
.context("empty version string")
65+
.into());
66+
}
67+
self.counterparty().validate_basic()
68+
}
69+
}
70+
71+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
72+
pub struct Endpoint {
73+
port_id: PortId,
74+
channel_id: ChannelId,
75+
}
76+
77+
impl Endpoint {
78+
pub fn new(
79+
port_id: String,
80+
channel_id: String,
81+
) -> Result<Self, crate::ics04_channel::error::Error> {
82+
Ok(Self {
83+
port_id: port_id
84+
.parse()
85+
.map_err(|e| Kind::IdentifierError.context(e))?,
86+
channel_id: channel_id
87+
.parse()
88+
.map_err(|e| Kind::IdentifierError.context(e))?,
89+
})
90+
}
91+
}
92+
93+
impl CounterpartyI for Endpoint {
94+
type ValidationError = crate::ics04_channel::error::Error;
95+
96+
fn port_id(&self) -> String {
97+
String::from(self.port_id.as_str())
98+
}
99+
100+
fn channel_id(&self) -> String {
101+
String::from(self.channel_id.as_str())
102+
}
103+
104+
fn validate_basic(&self) -> Result<(), Self::ValidationError> {
105+
Ok(())
106+
}
107+
}

modules/src/ics04_channel/error.rs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use anomaly::{BoxError, Context};
2+
use thiserror::Error;
3+
4+
pub type Error = anomaly::Error<Kind>;
5+
6+
#[derive(Clone, Debug, Error)]
7+
pub enum Kind {
8+
#[error("channel state unknown")]
9+
UnknownState,
10+
11+
#[error("identifier error")]
12+
IdentifierError,
13+
14+
#[error("channel order type unknown")]
15+
UnknownOrderType,
16+
17+
#[error("invalid connection hops length")]
18+
InvalidConnectionHopsLength,
19+
20+
#[error("invalid version")]
21+
InvalidVersion,
22+
}
23+
24+
impl Kind {
25+
pub fn context(self, source: impl Into<BoxError>) -> Context<Self> {
26+
Context::new(self, Some(source.into()))
27+
}
28+
}

modules/src/ics04_channel/exported.rs

+194
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
use super::error;
2+
use crate::ics24_host::identifier::ConnectionId;
3+
use anomaly::fail;
4+
use serde_derive::{Deserialize, Serialize};
5+
6+
pub trait ChannelI {
7+
type ValidationError: std::error::Error;
8+
fn state(&self) -> State;
9+
fn ordering(&self) -> Order;
10+
fn counterparty(&self) -> Box<dyn CounterpartyI<ValidationError = super::error::Error>>;
11+
fn connection_hops(&self) -> Vec<ConnectionId>;
12+
fn version(&self) -> String;
13+
fn validate_basic(&self) -> Result<(), Self::ValidationError>;
14+
}
15+
16+
pub trait CounterpartyI {
17+
type ValidationError: std::error::Error;
18+
fn port_id(&self) -> String;
19+
fn channel_id(&self) -> String;
20+
fn validate_basic(&self) -> Result<(), Self::ValidationError>;
21+
}
22+
23+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
24+
pub enum State {
25+
Uninitialized = 0,
26+
Init,
27+
TryOpen,
28+
Open,
29+
Closed,
30+
}
31+
32+
impl State {
33+
/// Yields the state as a string
34+
pub fn as_string(&self) -> &'static str {
35+
match self {
36+
Self::Uninitialized => "UNINITIALIZED",
37+
Self::Init => "INIT",
38+
Self::TryOpen => "TRYOPEN",
39+
Self::Open => "OPEN",
40+
Self::Closed => "CLOSED",
41+
}
42+
}
43+
}
44+
45+
impl std::str::FromStr for State {
46+
type Err = error::Error;
47+
48+
fn from_str(s: &str) -> Result<Self, Self::Err> {
49+
match s {
50+
"UNINITIALIZED" => Ok(Self::Uninitialized),
51+
"INIT" => Ok(Self::Init),
52+
"TRYOPEN" => Ok(Self::TryOpen),
53+
"OPEN" => Ok(Self::Open),
54+
"CLOSED" => Ok(Self::Closed),
55+
_ => fail!(error::Kind::UnknownState, s),
56+
}
57+
}
58+
}
59+
60+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
61+
pub enum Order {
62+
None = 0,
63+
Unordered,
64+
Ordered,
65+
}
66+
67+
impl Order {
68+
/// Yields the Order as a string
69+
pub fn as_string(&self) -> &'static str {
70+
match self {
71+
Self::None => "UNINITIALIZED",
72+
Self::Unordered => "UNORDERED",
73+
Self::Ordered => "ORDERED",
74+
}
75+
}
76+
}
77+
78+
impl std::str::FromStr for Order {
79+
type Err = error::Error;
80+
81+
fn from_str(s: &str) -> Result<Self, Self::Err> {
82+
match s {
83+
"UNINITIALIZED" => Ok(Self::None),
84+
"UNORDERED" => Ok(Self::Unordered),
85+
"ORDERED" => Ok(Self::Ordered),
86+
_ => fail!(error::Kind::UnknownOrderType, s),
87+
}
88+
}
89+
}
90+
91+
#[cfg(test)]
92+
mod tests {
93+
use std::str::FromStr;
94+
95+
#[test]
96+
fn parse_channel_ordering_type() {
97+
use super::Order;
98+
99+
struct Test {
100+
ordering: &'static str,
101+
want_res: Order,
102+
want_err: bool,
103+
}
104+
let tests: Vec<Test> = vec![
105+
Test {
106+
ordering: "UNINITIALIZED",
107+
want_res: Order::None,
108+
want_err: false,
109+
},
110+
Test {
111+
ordering: "UNORDERED",
112+
want_res: Order::Unordered,
113+
want_err: false,
114+
},
115+
Test {
116+
ordering: "ORDERED",
117+
want_res: Order::Ordered,
118+
want_err: false,
119+
},
120+
Test {
121+
ordering: "UNKNOWN_ORDER",
122+
want_res: Order::None,
123+
want_err: true,
124+
},
125+
]
126+
.into_iter()
127+
.collect();
128+
129+
for test in tests {
130+
match Order::from_str(test.ordering) {
131+
Ok(res) => {
132+
assert!(!test.want_err);
133+
assert_eq!(test.want_res, res);
134+
}
135+
Err(_) => assert!(test.want_err, "parse failed"),
136+
}
137+
}
138+
}
139+
140+
#[test]
141+
fn parse_channel_state() {
142+
use super::State;
143+
144+
struct Test {
145+
state: &'static str,
146+
want_res: State,
147+
want_err: bool,
148+
}
149+
let tests: Vec<Test> = vec![
150+
Test {
151+
state: "UNINITIALIZED",
152+
want_res: State::Uninitialized,
153+
want_err: false,
154+
},
155+
Test {
156+
state: "INIT",
157+
want_res: State::Init,
158+
want_err: false,
159+
},
160+
Test {
161+
state: "TRYOPEN",
162+
want_res: State::TryOpen,
163+
want_err: false,
164+
},
165+
Test {
166+
state: "OPEN",
167+
want_res: State::Open,
168+
want_err: false,
169+
},
170+
Test {
171+
state: "CLOSED",
172+
want_res: State::Closed,
173+
want_err: false,
174+
},
175+
Test {
176+
state: "INVALID_STATE",
177+
want_res: State::Open,
178+
want_err: true,
179+
},
180+
]
181+
.into_iter()
182+
.collect();
183+
184+
for test in tests {
185+
match State::from_str(test.state) {
186+
Ok(res) => {
187+
assert!(!test.want_err);
188+
assert_eq!(test.want_res, res);
189+
}
190+
Err(_) => assert!(test.want_err, "parse failed"),
191+
}
192+
}
193+
}
194+
}

modules/src/ics04_channel/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//! ICS 04: IBC Channel implementation
2+
3+
pub mod channel;
4+
pub mod error;
5+
pub mod exported;
6+
pub mod msgs;
7+
pub mod query;

0 commit comments

Comments
 (0)