-
Notifications
You must be signed in to change notification settings - Fork 352
/
mod.rs
252 lines (227 loc) · 9.42 KB
/
mod.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
pub mod modelator;
pub mod step;
use ibc::ics02_client::client_def::{AnyClientState, AnyConsensusState, AnyHeader};
use ibc::ics02_client::client_type::ClientType;
use ibc::ics02_client::error::Kind as ICS02ErrorKind;
use ibc::ics02_client::msgs::create_client::MsgCreateAnyClient;
use ibc::ics02_client::msgs::update_client::MsgUpdateAnyClient;
use ibc::ics02_client::msgs::ClientMsg;
use ibc::ics18_relayer::context::Ics18Context;
use ibc::ics18_relayer::error::{Error as ICS18Error, Kind as ICS18ErrorKind};
use ibc::ics24_host::identifier::{ChainId, ClientId};
use ibc::ics26_routing::error::{Error as ICS26Error, Kind as ICS26ErrorKind};
use ibc::ics26_routing::msgs::Ics26Envelope;
use ibc::mock::client_state::{MockClientState, MockConsensusState};
use ibc::mock::context::MockContext;
use ibc::mock::header::MockHeader;
use ibc::mock::host::HostType;
use ibc::Height;
use std::collections::HashMap;
use std::error::Error;
use std::fmt::{Debug, Display};
use step::{ActionOutcome, ActionType, Chain, Step};
use tendermint::account::Id as AccountId;
#[derive(Debug)]
pub struct IBCTestExecutor {
// mapping from chain identifier to its context
contexts: HashMap<ChainId, MockContext>,
}
impl IBCTestExecutor {
pub fn new() -> Self {
Self {
contexts: Default::default(),
}
}
/// Create a `MockContext` for a given `chain_id`.
/// Panic if a context for `chain_id` already exists.
fn init_chain_context(&mut self, chain_id: String, initial_height: u64) {
let chain_id = Self::chain_id(chain_id);
let max_history_size = 1;
let ctx = MockContext::new(
chain_id.clone(),
HostType::Mock,
max_history_size,
Height::new(Self::revision(), initial_height),
);
assert!(self.contexts.insert(chain_id, ctx).is_none());
}
/// Returns a reference to the `MockContext` of a given `chain_id`.
/// Panic if the context for `chain_id` is not found.
fn chain_context(&self, chain_id: String) -> &MockContext {
self.contexts
.get(&Self::chain_id(chain_id))
.expect("chain context should have been initialized")
}
/// Returns a mutable reference to the `MockContext` of a given `chain_id`.
/// Panic if the context for `chain_id` is not found.
fn chain_context_mut(&mut self, chain_id: String) -> &mut MockContext {
self.contexts
.get_mut(&Self::chain_id(chain_id))
.expect("chain context should have been initialized")
}
fn extract_handler_error_kind<K>(ics18_result: Result<(), ICS18Error>) -> K
where
K: Clone + Debug + Display + Into<anomaly::BoxError> + 'static,
{
let ics18_error = ics18_result.expect_err("ICS18 error expected");
assert!(matches!(
ics18_error.kind(),
ICS18ErrorKind::TransactionFailed
));
let ics26_error = ics18_error
.source()
.expect("expected source in ICS18 error")
.downcast_ref::<ICS26Error>()
.expect("ICS18 source should be an ICS26 error");
assert!(matches!(
ics26_error.kind(),
ICS26ErrorKind::HandlerRaisedError,
));
ics26_error
.source()
.expect("expected source in ICS26 error")
.downcast_ref::<anomaly::Error<K>>()
.expect("ICS26 source should be an handler error")
.kind()
.clone()
}
fn chain_id(chain_id: String) -> ChainId {
ChainId::new(chain_id, Self::revision())
}
fn revision() -> u64 {
0
}
fn client_id(client_id: u64) -> ClientId {
ClientId::new(ClientType::Mock, client_id)
.expect("it should be possible to create the client identifier")
}
fn height(height: u64) -> Height {
Height::new(Self::revision(), height)
}
fn mock_header(height: u64) -> MockHeader {
MockHeader(Self::height(height))
}
fn header(height: u64) -> AnyHeader {
AnyHeader::Mock(Self::mock_header(height))
}
fn client_state(height: u64) -> AnyClientState {
AnyClientState::Mock(MockClientState(Self::mock_header(height)))
}
fn consensus_state(height: u64) -> AnyConsensusState {
AnyConsensusState::Mock(MockConsensusState(Self::mock_header(height)))
}
fn signer() -> AccountId {
AccountId::new([0; 20])
}
/// Check that chain heights match the ones in the model.
fn check_chain_heights(&self, chains: HashMap<String, Chain>) -> bool {
chains.into_iter().all(|(chain_id, chain)| {
let ctx = self.chain_context(chain_id);
ctx.query_latest_height() == Self::height(chain.height)
})
}
}
impl modelator::TestExecutor<Step> for IBCTestExecutor {
fn initial_step(&mut self, step: Step) -> bool {
assert_eq!(
step.action.action_type,
ActionType::None,
"unexpected action type"
);
assert_eq!(
step.action_outcome,
ActionOutcome::None,
"unexpected action outcome"
);
// initiliaze all chains
for (chain_id, chain) in step.chains {
self.init_chain_context(chain_id, chain.height);
}
true
}
fn next_step(&mut self, step: Step) -> bool {
let outcome_matches = match step.action.action_type {
ActionType::None => panic!("unexpected action type"),
ActionType::ICS02CreateClient => {
// get action parameters
let chain_id = step
.action
.chain_id
.expect("create client action should have a chain identifier");
let client_height = step
.action
.client_height
.expect("create client action should have a client height");
// get chain's context
let ctx = self.chain_context_mut(chain_id);
// create ICS26 message and deliver it
let msg = Ics26Envelope::Ics2Msg(ClientMsg::CreateClient(MsgCreateAnyClient {
client_state: Self::client_state(client_height),
consensus_state: Self::consensus_state(client_height),
signer: Self::signer(),
}));
let result = ctx.deliver(msg);
// check the expected outcome: client create always succeeds
match step.action_outcome {
ActionOutcome::ICS02CreateOK => {
// the implementaion matches the model if no error occurs
result.is_ok()
}
action => panic!("unexpected action outcome {:?}", action),
}
}
ActionType::ICS02UpdateClient => {
// get action parameters
let chain_id = step
.action
.chain_id
.expect("update client action should have a chain identifier");
let client_id = step
.action
.client_id
.expect("update client action should have a client identifier");
let client_height = step
.action
.client_height
.expect("update client action should have a client height");
// get chain's context
let ctx = self.chain_context_mut(chain_id);
// create ICS26 message and deliver it
let msg = Ics26Envelope::Ics2Msg(ClientMsg::UpdateClient(MsgUpdateAnyClient {
client_id: Self::client_id(client_id),
header: Self::header(client_height),
signer: Self::signer(),
}));
let result = ctx.deliver(msg);
// check the expected outcome
match step.action_outcome {
ActionOutcome::ICS02UpdateOK => {
// the implementaion matches the model if no error occurs
result.is_ok()
}
ActionOutcome::ICS02ClientNotFound => {
let handler_error_kind =
Self::extract_handler_error_kind::<ICS02ErrorKind>(result);
// the implementaion matches the model if there's an
// error matching the expected outcome
matches!(
handler_error_kind,
ICS02ErrorKind::ClientNotFound(error_client_id)
if error_client_id == Self::client_id(client_id)
)
}
ActionOutcome::ICS02HeaderVerificationFailure => {
let handler_error_kind =
Self::extract_handler_error_kind::<ICS02ErrorKind>(result);
// the implementaion matches the model if there's an
// error matching the expected outcome
handler_error_kind == ICS02ErrorKind::HeaderVerificationFailure
}
action => panic!("unexpected action outcome {:?}", action),
}
}
};
// also check that chain heights match
outcome_matches && self.check_chain_heights(step.chains)
}
}