From 413d3f48c4bdebfa6c9c72f4161da4c1236d6444 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 2 Feb 2021 19:26:13 +0100 Subject: [PATCH 01/52] Add ICS02 model --- modules/tests/support/model_based/.gitignore | 7 + modules/tests/support/model_based/ICS02.cfg | 10 ++ modules/tests/support/model_based/ICS02.tla | 146 ++++++++++++++++++ .../tests/support/model_based/ICS02Tests.cfg | 12 ++ .../tests/support/model_based/ICS02Tests.tla | 22 +++ modules/tests/support/model_based/Makefile | 6 + .../support/model_based/counterexample.tla | 39 +++++ modules/tests/support/model_based/test.json | 14 ++ 8 files changed, 256 insertions(+) create mode 100644 modules/tests/support/model_based/.gitignore create mode 100644 modules/tests/support/model_based/ICS02.cfg create mode 100644 modules/tests/support/model_based/ICS02.tla create mode 100644 modules/tests/support/model_based/ICS02Tests.cfg create mode 100644 modules/tests/support/model_based/ICS02Tests.tla create mode 100644 modules/tests/support/model_based/Makefile create mode 100644 modules/tests/support/model_based/counterexample.tla create mode 100644 modules/tests/support/model_based/test.json diff --git a/modules/tests/support/model_based/.gitignore b/modules/tests/support/model_based/.gitignore new file mode 100644 index 0000000000..46e548cea0 --- /dev/null +++ b/modules/tests/support/model_based/.gitignore @@ -0,0 +1,7 @@ +*.out +states/ +x/ +detailed.log +bfs.csv +log0.smt +profile-rules.txt diff --git a/modules/tests/support/model_based/ICS02.cfg b/modules/tests/support/model_based/ICS02.cfg new file mode 100644 index 0000000000..ecddb236a6 --- /dev/null +++ b/modules/tests/support/model_based/ICS02.cfg @@ -0,0 +1,10 @@ +CONSTANTS + MaxClientId = 5 + MaxHeight = 5 + +INIT Init +NEXT Next + +INVARIANTS + TypeOK + ModelNeverErrors \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS02.tla b/modules/tests/support/model_based/ICS02.tla new file mode 100644 index 0000000000..a7be842874 --- /dev/null +++ b/modules/tests/support/model_based/ICS02.tla @@ -0,0 +1,146 @@ +--------------------------- MODULE ICS02 ---------------------------- + +EXTENDS Integers, FiniteSets + +\* max client identifier +CONSTANT MaxClientId +ASSUME MaxClientId > 0 +\* max height which clients can reach +CONSTANT MaxHeight +ASSUME MaxHeight > 0 + +\* set of client data +VARIABLE clients +\* counter used to generate client identifiers +VARIABLE nextClientId +\* last action performed +VARIABLE action +\* string with the outcome of the last operation +VARIABLE actionOutcome + +(********************* TYPE ANNOTATIONS FOR APALACHE ***********************) +\* operator for type annotations +a <: b == a + +ActionType == [ + type |-> STRING, + clientId |-> Int, + height |-> Int +] +AsAction(a) == a <: ActionType +(****************** END OF TYPE ANNOTATIONS FOR APALACHE ********************) + +\* set of possible client identifiers +ClientIds == 1..MaxClientId +\* set of possible heights +Heights == 1..MaxHeight +\* if a client has a null height then the client does not exist +NullHeight == 0 +\* set of possible actions +NullActions == [ + type: {"Null"} +] <: {ActionType} +CreateClientActions == [ + type: {"CreateClient"}, + height: Heights +] <: {ActionType} +UpdateClientActions == [ + type: {"UpdateClient"}, + clientId: ClientIds, + height: Heights +] <: {ActionType} +Actions == NullActions \union CreateClientActions \union UpdateClientActions +\* set of possible outcomes +ActionOutcomes == {"Null", "CreateOK", "UpdateOK", "UpdateClientNotFound", "UpdateHeightVerificationFailure", "ModelError"} + + +(*************************************************************************** + Specification + ***************************************************************************) + +\* check if a client exists +ClientExists(clientId) == + clients[clientId] /= NullHeight + +SetClientHeight(clientId, clientHeight) == + [clients EXCEPT ![clientId] = clientHeight] + +CreateClient(clientHeight) == + \* check if the client exists (it shouldn't) + IF ClientExists(nextClientId) THEN + \* if the client to be created already exists, + \* then there's an error in the model + /\ actionOutcome' = "ModelError" + /\ UNCHANGED <> + ELSE + \* set the new client's height to `clientHeight` + /\ clients' = SetClientHeight(nextClientId, clientHeight) + \* update `nextClientId` + /\ nextClientId' = nextClientId + 1 + \* set `outcome` + /\ actionOutcome' = "CreateOK" + +UpdateClient(clientId, clientHeight) == + \* check if the client exists + IF ClientExists(clientId) THEN + \* if the client exists, check its height + IF clients[clientId] < clientHeight THEN + \* if its height is lower than the one being updated to + \* then, update the client + /\ clients' = SetClientHeight(clientId, clientHeight) + \* set outcome + /\ actionOutcome' = "UpdateOK" + /\ UNCHANGED <> + ELSE + /\ actionOutcome' = "UpdateHeightVerificationFailure" + /\ UNCHANGED <> + ELSE + \* if the client does not exist, then return an error + /\ actionOutcome' = "UpdateClientNotFound" + /\ UNCHANGED <> + +CreateClientAction == + \* only create client if the model constant `MaxClientId` allows it + /\ nextClientId < MaxClientId + \* select a height for the client to be created at + /\ \E clientHeight \in Heights: + /\ action' = AsAction([type |-> "CreateClient", + height |-> clientHeight]) + /\ CreateClient(clientHeight) + +UpdateClientAction == + \* select a client to be updated (which may not exist) + \E clientId \in ClientIds: + \* select a height for the client to be updated + \E clientHeight \in Heights: + /\ action' = AsAction([type |-> "UpdateClient", + clientId |-> clientId, + height |-> clientHeight]) + /\ UpdateClient(clientId, clientHeight) + + Init == + /\ clients = [clientId \in ClientIds |-> NullHeight] + /\ nextClientId = 1 + /\ action = AsAction([type |-> "Null"]) + /\ actionOutcome = "Null" + +Next == + \/ CreateClientAction + \/ UpdateClientAction + \/ UNCHANGED <> + +(*************************************************************************** + Invariants + ***************************************************************************) + +TypeOK == + /\ nextClientId \in ClientIds \union {MaxClientId + 1} + /\ clients \in [ClientIds -> Heights \union {NullHeight}] + /\ action \in Actions + /\ actionOutcome \in ActionOutcomes + +\* the model never erros +ModelNeverErrors == + actionOutcome /= "ModelError" + +============================================================================= diff --git a/modules/tests/support/model_based/ICS02Tests.cfg b/modules/tests/support/model_based/ICS02Tests.cfg new file mode 100644 index 0000000000..3cad4d28ed --- /dev/null +++ b/modules/tests/support/model_based/ICS02Tests.cfg @@ -0,0 +1,12 @@ +CONSTANTS + MaxClientId = 5 + MaxHeight = 5 + +INIT Init +NEXT Next + +INVARIANTS + \* CreateOKTest + \* UpdateOKTest + \* UpdateClientNotFoundTest + UpdateHeightVerificationFailureTest \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS02Tests.tla b/modules/tests/support/model_based/ICS02Tests.tla new file mode 100644 index 0000000000..3664de67ab --- /dev/null +++ b/modules/tests/support/model_based/ICS02Tests.tla @@ -0,0 +1,22 @@ +------------------------- MODULE ICS02Tests --------------------------- + +EXTENDS ICS02 + +CreateOK == + /\ actionOutcome = "CreateOK" + +UpdateOK == + /\ actionOutcome = "UpdateOK" + +UpdateClientNotFound == + /\ actionOutcome = "UpdateClientNotFound" + +UpdateHeightVerificationFailure == + /\ actionOutcome = "UpdateHeightVerificationFailure" + +CreateOKTest == ~CreateOK +UpdateOKTest == ~UpdateOK +UpdateClientNotFoundTest == ~UpdateClientNotFound +UpdateHeightVerificationFailureTest == ~UpdateHeightVerificationFailure + +============================================================================= \ No newline at end of file diff --git a/modules/tests/support/model_based/Makefile b/modules/tests/support/model_based/Makefile new file mode 100644 index 0000000000..9e5318a5f2 --- /dev/null +++ b/modules/tests/support/model_based/Makefile @@ -0,0 +1,6 @@ +test: + apalache-mc check ICS02Tests.tla + +check: + apalache-mc check ICS02.tla + diff --git a/modules/tests/support/model_based/counterexample.tla b/modules/tests/support/model_based/counterexample.tla new file mode 100644 index 0000000000..f3c00c968d --- /dev/null +++ b/modules/tests/support/model_based/counterexample.tla @@ -0,0 +1,39 @@ +------------------------- MODULE counterexample ------------------------- + +EXTENDS ICS02Tests + +(* Initial state *) + +State1 == +TRUE +(* Transition 0 to State2 *) + +State2 == +/\ action = [type |-> "Null"] +/\ actionOutcome = "Null" +/\ clients = 1 :> 0 @@ 2 :> 0 @@ 3 :> 0 @@ 4 :> 0 @@ 5 :> 0 +/\ nextClientId = 1 + +(* Transition 1 to State3 *) + +State3 == +/\ action = [height |-> 1, type |-> "CreateClient"] +/\ actionOutcome = "CreateOK" +/\ clients = 1 :> 1 @@ 2 :> 0 @@ 3 :> 0 @@ 4 :> 0 @@ 5 :> 0 +/\ nextClientId = 2 + +(* Transition 5 to State4 *) + +State4 == +/\ action = [clientId |-> 1, height |-> 1, type |-> "UpdateClient"] +/\ actionOutcome = "UpdateHeightVerificationFailure" +/\ clients = 1 :> 1 @@ 2 :> 0 @@ 3 :> 0 @@ 4 :> 0 @@ 5 :> 0 +/\ nextClientId = 2 + +(* The following formula holds true in the last state and violates the invariant *) + +InvariantViolation == actionOutcome = "UpdateHeightVerificationFailure" + +================================================================================ +\* Created by Apalache on Tue Feb 02 19:12:27 CET 2021 +\* https://github.com/informalsystems/apalache diff --git a/modules/tests/support/model_based/test.json b/modules/tests/support/model_based/test.json new file mode 100644 index 0000000000..b39b88b238 --- /dev/null +++ b/modules/tests/support/model_based/test.json @@ -0,0 +1,14 @@ +[ + { + "action": {"type": "Null"}, + "actionOutcome": "Null" + }, + { + "action": {"height": 1, "type": "CreateClient"}, + "actionOutcome": "CreateOK" + }, + { + "action": {"clientId": 1, "height": 1, "type": "UpdateClient"}, + "actionOutcome": "UpdateHeightVerificationFailure" + } +] \ No newline at end of file From 065631ed45b0943a8b3a1bfb5c82ab3c02aefd3e Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 2 Feb 2021 20:44:19 +0100 Subject: [PATCH 02/52] Add MBT test driver --- modules/Cargo.toml | 2 + modules/tests/Makefile | 2 + modules/tests/model_based.rs | 62 +++++++++++++++++++++ modules/tests/modelator.rs | 41 ++++++++++++++ modules/tests/support/model_based/test.json | 15 ++++- 5 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 modules/tests/Makefile create mode 100644 modules/tests/model_based.rs create mode 100644 modules/tests/modelator.rs diff --git a/modules/Cargo.toml b/modules/Cargo.toml index 5c303f4512..00c4a51ae8 100644 --- a/modules/Cargo.toml +++ b/modules/Cargo.toml @@ -37,6 +37,8 @@ dyn-clonable = "0.9.0" regex = "1" bech32 = "0.7.2" +eyre = "0.6.5" + [dependencies.tendermint] version = "=0.18.0" diff --git a/modules/tests/Makefile b/modules/tests/Makefile new file mode 100644 index 0000000000..7ec1b1e4d1 --- /dev/null +++ b/modules/tests/Makefile @@ -0,0 +1,2 @@ +test: + cargo t -- --nocapture main diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs new file mode 100644 index 0000000000..841ecdae87 --- /dev/null +++ b/modules/tests/model_based.rs @@ -0,0 +1,62 @@ +mod modelator; + +use serde::Deserialize; +use std::fmt::Debug; + +#[derive(Debug, Clone, Deserialize)] +struct State { + action: Action, + + #[serde(alias = "actionOutcome")] + action_outcome: ActionOutcome, +} + +#[derive(Debug, Clone, Deserialize)] +struct Action { + #[serde(alias = "type")] + action_type: ActionType, + + #[serde(alias = "clientId")] + client_id: Option, + + height: Option, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +enum ActionType { + Null, + CreateClient, + UpdateClient, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +enum ActionOutcome { + Null, + CreateOK, + UpdateHeightVerificationFailure, +} + +struct ICS02TestExecutor; + +impl modelator::TestExecutor for ICS02TestExecutor { + fn check_initial_state(&mut self, state: State) -> bool { + let type_is_null = state.action.action_type == ActionType::Null; + let outcome_is_null = state.action_outcome == ActionOutcome::Null; + type_is_null && outcome_is_null + } + + fn check_next_state(&mut self, state: State) -> bool { + todo!() + } +} + +#[test] +fn main() { + let path = "tests/support/model_based/test.json"; + let test_executor = ICS02TestExecutor; + // we should be able to just return the `Result` once the following issue + // is fixed: https://github.com/rust-lang/rust/issues/43301 + if let Err(e) = modelator::test_driver(test_executor, path) { + panic!("{:?}", e); + } +} diff --git a/modules/tests/modelator.rs b/modules/tests/modelator.rs new file mode 100644 index 0000000000..bf16912319 --- /dev/null +++ b/modules/tests/modelator.rs @@ -0,0 +1,41 @@ +use eyre::{eyre, Context, Result}; +use serde::de::DeserializeOwned; +use std::fmt::Debug; +use std::fs::File; +use std::path::Path; + +pub trait TestExecutor { + fn check_initial_state(&mut self, state: S) -> bool; + + fn check_next_state(&mut self, state: S) -> bool; +} + +pub fn test_driver(mut test_executor: E, path: P) -> Result<()> +where + E: TestExecutor, + S: DeserializeOwned + Debug + Clone, + P: AsRef, +{ + let reader = File::open(path.as_ref()) + .wrap_err_with(|| format!("test file {:?} not found.", path.as_ref()))?; + let states: Vec = serde_json::de::from_reader(reader) + .wrap_err_with(|| format!("test file {:?} could not be deserialized", path.as_ref()))?; + + let mut states = states.into_iter(); + + if let Some(state) = states.next() { + if !test_executor.check_initial_state(state.clone()) { + return Err(eyre!("check failed on initial state {:?}", state)); + } + } else { + println!("WARNING: test file {:?} had 0 states", path.as_ref()); + return Ok(()); + } + + for state in states { + if !test_executor.check_next_state(state.clone()) { + return Err(eyre!("check failed on state {:?}", state)); + } + } + Ok(()) +} diff --git a/modules/tests/support/model_based/test.json b/modules/tests/support/model_based/test.json index b39b88b238..f826069b6e 100644 --- a/modules/tests/support/model_based/test.json +++ b/modules/tests/support/model_based/test.json @@ -1,14 +1,23 @@ [ { - "action": {"type": "Null"}, + "action": { + "type": "Null" + }, "actionOutcome": "Null" }, { - "action": {"height": 1, "type": "CreateClient"}, + "action": { + "height": 1, + "type": "CreateClient" + }, "actionOutcome": "CreateOK" }, { - "action": {"clientId": 1, "height": 1, "type": "UpdateClient"}, + "action": { + "clientId": 1, + "height": 1, + "type": "UpdateClient" + }, "actionOutcome": "UpdateHeightVerificationFailure" } ] \ No newline at end of file From b98c7d3ee07a8de60418b6f13bf35f8aba5879c5 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 2 Feb 2021 21:52:40 +0100 Subject: [PATCH 03/52] Add ICS02TestExecutor --- .../src/ics02_client/msgs/create_client.rs | 6 +- modules/tests/Makefile | 2 +- modules/tests/model_based.rs | 140 +++++++++++++----- modules/tests/modelator.rs | 16 +- modules/tests/state.rs | 35 +++++ 5 files changed, 155 insertions(+), 44 deletions(-) create mode 100644 modules/tests/state.rs diff --git a/modules/src/ics02_client/msgs/create_client.rs b/modules/src/ics02_client/msgs/create_client.rs index 114e2e30c4..35280124a8 100644 --- a/modules/src/ics02_client/msgs/create_client.rs +++ b/modules/src/ics02_client/msgs/create_client.rs @@ -22,9 +22,9 @@ pub const TYPE_URL: &str = "/ibc.core.client.v1.MsgCreateClient"; /// A type of message that triggers the creation of a new on-chain (IBC) client. #[derive(Clone, Debug, PartialEq, Eq)] pub struct MsgCreateAnyClient { - client_state: AnyClientState, - consensus_state: AnyConsensusState, - signer: AccountId, + pub client_state: AnyClientState, + pub consensus_state: AnyConsensusState, + pub signer: AccountId, } impl MsgCreateAnyClient { diff --git a/modules/tests/Makefile b/modules/tests/Makefile index 7ec1b1e4d1..7d64c99e0d 100644 --- a/modules/tests/Makefile +++ b/modules/tests/Makefile @@ -1,2 +1,2 @@ test: - cargo t -- --nocapture main + cargo t --features mocks -- --nocapture main diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index 841ecdae87..5320d0ee16 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -1,43 +1,50 @@ mod modelator; +mod state; -use serde::Deserialize; +use ibc::ics02_client::client_def::AnyHeader; +use ibc::ics02_client::client_def::{AnyClientState, AnyConsensusState}; +use ibc::ics02_client::client_type::ClientType; +use ibc::ics02_client::msgs::create_client::MsgCreateAnyClient; +use ibc::ics02_client::msgs::update_client::MsgUpdateAnyClient; +use ibc::ics02_client::msgs::ClientMsg; +use ibc::ics24_host::identifier::ChainId; +use ibc::ics24_host::identifier::ClientId; +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 state::{ActionOutcome, ActionType, State}; use std::fmt::Debug; +use tendermint::account::Id as AccountId; -#[derive(Debug, Clone, Deserialize)] -struct State { - action: Action, - - #[serde(alias = "actionOutcome")] - action_outcome: ActionOutcome, -} - -#[derive(Debug, Clone, Deserialize)] -struct Action { - #[serde(alias = "type")] - action_type: ActionType, - - #[serde(alias = "clientId")] - client_id: Option, - - height: Option, +#[derive(Debug)] +struct ICS02TestExecutor { + version: u64, + ctx: MockContext, } -#[derive(Debug, Clone, PartialEq, Deserialize)] -enum ActionType { - Null, - CreateClient, - UpdateClient, -} +impl ICS02TestExecutor { + fn new() -> Self { + let version = 1; + let ctx = MockContext::new( + ChainId::new("mock".to_string(), version), + HostType::Mock, + 1, + Height::new(version, 0), + ); + // let ctx = MockContext::new( + // ChainId::new("mock".to_string(), cv), + // HostType::SyntheticTendermint, + // 1, + // Height::new(cv, 0), + // ); -#[derive(Debug, Clone, PartialEq, Deserialize)] -enum ActionOutcome { - Null, - CreateOK, - UpdateHeightVerificationFailure, + Self { version, ctx } + } } -struct ICS02TestExecutor; - impl modelator::TestExecutor for ICS02TestExecutor { fn check_initial_state(&mut self, state: State) -> bool { let type_is_null = state.action.action_type == ActionType::Null; @@ -46,16 +53,81 @@ impl modelator::TestExecutor for ICS02TestExecutor { } fn check_next_state(&mut self, state: State) -> bool { - todo!() + match state.action.action_type { + ActionType::Null => panic!("next state action type cannot be null"), + ActionType::CreateClient => { + // get action parameters + let height = state + .action + .height + .expect("update client action should have a height"); + + // create client and consensus state from parameters + let client_state = AnyClientState::Mock(MockClientState(self.mock_header(height))); + let consensus_state = + AnyConsensusState::Mock(MockConsensusState(self.mock_header(height))); + + // create dummy signer + let signer = self.dummy_signer(); + + // create ICS26 message + let msg = ICS26Envelope::ICS2Msg(ClientMsg::CreateClient(MsgCreateAnyClient { + client_state, + consensus_state, + signer, + })); + self.ctx.deliver(msg).unwrap(); + true + } + ActionType::UpdateClient => { + // TODO: rename clientId to clientCounter in the model + // get action parameters + let client_id = state + .action + .client_id + .expect("update client action should have a client identifier"); + let height = state + .action + .height + .expect("update client action should have a height"); + + // create client id and header from action parameters + let client_id = ClientId::new(ClientType::Mock, client_id) + .expect("it should be possible to create the client identifier"); + let header = AnyHeader::Mock(self.mock_header(height)); + + // create dummy signer + let signer = self.dummy_signer(); + + // create ICS26 message + let msg = ICS26Envelope::ICS2Msg(ClientMsg::UpdateClient(MsgUpdateAnyClient { + client_id, + header, + signer, + })); + self.ctx.deliver(msg).unwrap(); + true + } + } + } +} + +impl ICS02TestExecutor { + fn dummy_signer(&self) -> AccountId { + AccountId::new([0; 20]) + } + + fn mock_header(&self, height: u64) -> MockHeader { + MockHeader(Height::new(self.version, height)) } } #[test] fn main() { let path = "tests/support/model_based/test.json"; - let test_executor = ICS02TestExecutor; + let test_executor = ICS02TestExecutor::new(); // we should be able to just return the `Result` once the following issue - // is fixed: https://github.com/rust-lang/rust/issues/43301 + // is fixed: https://github.com/rust-lang/rust/issues/43301 if let Err(e) = modelator::test_driver(test_executor, path) { panic!("{:?}", e); } diff --git a/modules/tests/modelator.rs b/modules/tests/modelator.rs index bf16912319..8663e205e8 100644 --- a/modules/tests/modelator.rs +++ b/modules/tests/modelator.rs @@ -10,9 +10,9 @@ pub trait TestExecutor { fn check_next_state(&mut self, state: S) -> bool; } -pub fn test_driver(mut test_executor: E, path: P) -> Result<()> +pub fn test_driver(mut executor: E, path: P) -> Result<()> where - E: TestExecutor, + E: TestExecutor + Debug, S: DeserializeOwned + Debug + Clone, P: AsRef, { @@ -24,8 +24,8 @@ where let mut states = states.into_iter(); if let Some(state) = states.next() { - if !test_executor.check_initial_state(state.clone()) { - return Err(eyre!("check failed on initial state {:?}", state)); + if !executor.check_initial_state(state.clone()) { + return Err(eyre!("check failed on initial state:\n{:#?}", state)); } } else { println!("WARNING: test file {:?} had 0 states", path.as_ref()); @@ -33,8 +33,12 @@ where } for state in states { - if !test_executor.check_next_state(state.clone()) { - return Err(eyre!("check failed on state {:?}", state)); + if !executor.check_next_state(state.clone()) { + return Err(eyre!( + "check failed on state:\n{:#?}\n\nexecutor:\n{:#?}", + state, + executor + )); } } Ok(()) diff --git a/modules/tests/state.rs b/modules/tests/state.rs new file mode 100644 index 0000000000..a88c303bd7 --- /dev/null +++ b/modules/tests/state.rs @@ -0,0 +1,35 @@ +use serde::Deserialize; +use std::fmt::Debug; + +#[derive(Debug, Clone, Deserialize)] +pub struct State { + pub action: Action, + + #[serde(alias = "actionOutcome")] + pub action_outcome: ActionOutcome, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Action { + #[serde(alias = "type")] + pub action_type: ActionType, + + #[serde(alias = "clientId")] + pub client_id: Option, + + pub height: Option, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub enum ActionType { + Null, + CreateClient, + UpdateClient, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub enum ActionOutcome { + Null, + CreateOK, + UpdateHeightVerificationFailure, +} From 6685cc103b6e4887d4a6395a5ef5a4218703e671 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 2 Feb 2021 22:02:58 +0100 Subject: [PATCH 04/52] Add another apalache counterexample --- ... UpdateHeightVerificationFailureTest.json} | 0 .../support/model_based/UpdateOKTest.json | 23 +++++++++++ .../support/model_based/counterexample.tla | 39 ------------------- 3 files changed, 23 insertions(+), 39 deletions(-) rename modules/tests/support/model_based/{test.json => UpdateHeightVerificationFailureTest.json} (100%) create mode 100644 modules/tests/support/model_based/UpdateOKTest.json delete mode 100644 modules/tests/support/model_based/counterexample.tla diff --git a/modules/tests/support/model_based/test.json b/modules/tests/support/model_based/UpdateHeightVerificationFailureTest.json similarity index 100% rename from modules/tests/support/model_based/test.json rename to modules/tests/support/model_based/UpdateHeightVerificationFailureTest.json diff --git a/modules/tests/support/model_based/UpdateOKTest.json b/modules/tests/support/model_based/UpdateOKTest.json new file mode 100644 index 0000000000..06a9531db5 --- /dev/null +++ b/modules/tests/support/model_based/UpdateOKTest.json @@ -0,0 +1,23 @@ +[ + { + "action": { + "type": "Null" + }, + "actionOutcome": "Null" + }, + { + "action": { + "height": 1, + "type": "CreateClient" + }, + "actionOutcome": "CreateOK" + }, + { + "action": { + "clientId": 1, + "height": 2, + "type": "UpdateClient" + }, + "actionOutcome": "UpdateOK" + } +] \ No newline at end of file diff --git a/modules/tests/support/model_based/counterexample.tla b/modules/tests/support/model_based/counterexample.tla deleted file mode 100644 index f3c00c968d..0000000000 --- a/modules/tests/support/model_based/counterexample.tla +++ /dev/null @@ -1,39 +0,0 @@ -------------------------- MODULE counterexample ------------------------- - -EXTENDS ICS02Tests - -(* Initial state *) - -State1 == -TRUE -(* Transition 0 to State2 *) - -State2 == -/\ action = [type |-> "Null"] -/\ actionOutcome = "Null" -/\ clients = 1 :> 0 @@ 2 :> 0 @@ 3 :> 0 @@ 4 :> 0 @@ 5 :> 0 -/\ nextClientId = 1 - -(* Transition 1 to State3 *) - -State3 == -/\ action = [height |-> 1, type |-> "CreateClient"] -/\ actionOutcome = "CreateOK" -/\ clients = 1 :> 1 @@ 2 :> 0 @@ 3 :> 0 @@ 4 :> 0 @@ 5 :> 0 -/\ nextClientId = 2 - -(* Transition 5 to State4 *) - -State4 == -/\ action = [clientId |-> 1, height |-> 1, type |-> "UpdateClient"] -/\ actionOutcome = "UpdateHeightVerificationFailure" -/\ clients = 1 :> 1 @@ 2 :> 0 @@ 3 :> 0 @@ 4 :> 0 @@ 5 :> 0 -/\ nextClientId = 2 - -(* The following formula holds true in the last state and violates the invariant *) - -InvariantViolation == actionOutcome = "UpdateHeightVerificationFailure" - -================================================================================ -\* Created by Apalache on Tue Feb 02 19:12:27 CET 2021 -\* https://github.com/informalsystems/apalache From f86348614c58770bcf4de617fb9a4d2f1025dcec Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 2 Feb 2021 22:14:50 +0100 Subject: [PATCH 05/52] Fix ICS02.tla: client counter starts at 0 --- modules/tests/model_based.rs | 19 +++++---- modules/tests/state.rs | 2 + modules/tests/support/model_based/.gitignore | 2 + modules/tests/support/model_based/ICS02.cfg | 2 +- modules/tests/support/model_based/ICS02.tla | 40 +++++++++---------- .../tests/support/model_based/ICS02Tests.cfg | 2 +- .../UpdateHeightVerificationFailureTest.json | 2 +- .../support/model_based/UpdateOKTest.json | 2 +- 8 files changed, 40 insertions(+), 31 deletions(-) diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index 5320d0ee16..6ed8d8c7cf 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -80,7 +80,6 @@ impl modelator::TestExecutor for ICS02TestExecutor { true } ActionType::UpdateClient => { - // TODO: rename clientId to clientCounter in the model // get action parameters let client_id = state .action @@ -122,13 +121,19 @@ impl ICS02TestExecutor { } } +const TESTS_DIR: &str = "tests/support/model_based"; + #[test] fn main() { - let path = "tests/support/model_based/test.json"; - let test_executor = ICS02TestExecutor::new(); - // we should be able to just return the `Result` once the following issue - // is fixed: https://github.com/rust-lang/rust/issues/43301 - if let Err(e) = modelator::test_driver(test_executor, path) { - panic!("{:?}", e); + let tests = vec!["UpdateOKTest", "UpdateHeightVerificationFailureTest"]; + + for test in tests { + let path = format!("{}/{}.json", TESTS_DIR, test); + let test_executor = ICS02TestExecutor::new(); + // we should be able to just return the `Result` once the following issue + // is fixed: https://github.com/rust-lang/rust/issues/43301 + if let Err(e) = modelator::test_driver(test_executor, path) { + panic!("{:?}", e); + } } } diff --git a/modules/tests/state.rs b/modules/tests/state.rs index a88c303bd7..7b4cfdb06d 100644 --- a/modules/tests/state.rs +++ b/modules/tests/state.rs @@ -31,5 +31,7 @@ pub enum ActionType { pub enum ActionOutcome { Null, CreateOK, + UpdateOK, + UpdateClientNotFound, UpdateHeightVerificationFailure, } diff --git a/modules/tests/support/model_based/.gitignore b/modules/tests/support/model_based/.gitignore index 46e548cea0..a8ddd534bf 100644 --- a/modules/tests/support/model_based/.gitignore +++ b/modules/tests/support/model_based/.gitignore @@ -5,3 +5,5 @@ detailed.log bfs.csv log0.smt profile-rules.txt +counterexample.json +counterexample.tla diff --git a/modules/tests/support/model_based/ICS02.cfg b/modules/tests/support/model_based/ICS02.cfg index ecddb236a6..c23bd845b2 100644 --- a/modules/tests/support/model_based/ICS02.cfg +++ b/modules/tests/support/model_based/ICS02.cfg @@ -1,5 +1,5 @@ CONSTANTS - MaxClientId = 5 + MaxClients = 5 MaxHeight = 5 INIT Init diff --git a/modules/tests/support/model_based/ICS02.tla b/modules/tests/support/model_based/ICS02.tla index a7be842874..5dd5690c50 100644 --- a/modules/tests/support/model_based/ICS02.tla +++ b/modules/tests/support/model_based/ICS02.tla @@ -2,9 +2,9 @@ EXTENDS Integers, FiniteSets -\* max client identifier -CONSTANT MaxClientId -ASSUME MaxClientId > 0 +\* max number of client to be created +CONSTANT MaxClients +ASSUME MaxClients > 0 \* max height which clients can reach CONSTANT MaxHeight ASSUME MaxHeight > 0 @@ -12,7 +12,7 @@ ASSUME MaxHeight > 0 \* set of client data VARIABLE clients \* counter used to generate client identifiers -VARIABLE nextClientId +VARIABLE clientIdCounter \* last action performed VARIABLE action \* string with the outcome of the last operation @@ -31,7 +31,7 @@ AsAction(a) == a <: ActionType (****************** END OF TYPE ANNOTATIONS FOR APALACHE ********************) \* set of possible client identifiers -ClientIds == 1..MaxClientId +ClientIds == 0..(MaxClients - 1) \* set of possible heights Heights == 1..MaxHeight \* if a client has a null height then the client does not exist @@ -67,16 +67,16 @@ SetClientHeight(clientId, clientHeight) == CreateClient(clientHeight) == \* check if the client exists (it shouldn't) - IF ClientExists(nextClientId) THEN + IF ClientExists(clientIdCounter) THEN \* if the client to be created already exists, \* then there's an error in the model /\ actionOutcome' = "ModelError" - /\ UNCHANGED <> + /\ UNCHANGED <> ELSE \* set the new client's height to `clientHeight` - /\ clients' = SetClientHeight(nextClientId, clientHeight) - \* update `nextClientId` - /\ nextClientId' = nextClientId + 1 + /\ clients' = SetClientHeight(clientIdCounter, clientHeight) + \* update `clientIdCounter` + /\ clientIdCounter' = clientIdCounter + 1 \* set `outcome` /\ actionOutcome' = "CreateOK" @@ -90,51 +90,51 @@ UpdateClient(clientId, clientHeight) == /\ clients' = SetClientHeight(clientId, clientHeight) \* set outcome /\ actionOutcome' = "UpdateOK" - /\ UNCHANGED <> + /\ UNCHANGED <> ELSE /\ actionOutcome' = "UpdateHeightVerificationFailure" - /\ UNCHANGED <> + /\ UNCHANGED <> ELSE \* if the client does not exist, then return an error /\ actionOutcome' = "UpdateClientNotFound" - /\ UNCHANGED <> + /\ UNCHANGED <> CreateClientAction == - \* only create client if the model constant `MaxClientId` allows it - /\ nextClientId < MaxClientId + \* only create client if the model constant `MaxClients` allows it + /\ clientIdCounter \in ClientIds \* select a height for the client to be created at /\ \E clientHeight \in Heights: + /\ CreateClient(clientHeight) /\ action' = AsAction([type |-> "CreateClient", height |-> clientHeight]) - /\ CreateClient(clientHeight) UpdateClientAction == \* select a client to be updated (which may not exist) \E clientId \in ClientIds: \* select a height for the client to be updated \E clientHeight \in Heights: + /\ UpdateClient(clientId, clientHeight) /\ action' = AsAction([type |-> "UpdateClient", clientId |-> clientId, height |-> clientHeight]) - /\ UpdateClient(clientId, clientHeight) Init == /\ clients = [clientId \in ClientIds |-> NullHeight] - /\ nextClientId = 1 + /\ clientIdCounter = 0 /\ action = AsAction([type |-> "Null"]) /\ actionOutcome = "Null" Next == \/ CreateClientAction \/ UpdateClientAction - \/ UNCHANGED <> + \/ UNCHANGED <> (*************************************************************************** Invariants ***************************************************************************) TypeOK == - /\ nextClientId \in ClientIds \union {MaxClientId + 1} + /\ clientIdCounter \in 0..MaxClients /\ clients \in [ClientIds -> Heights \union {NullHeight}] /\ action \in Actions /\ actionOutcome \in ActionOutcomes diff --git a/modules/tests/support/model_based/ICS02Tests.cfg b/modules/tests/support/model_based/ICS02Tests.cfg index 3cad4d28ed..79b45ebaf0 100644 --- a/modules/tests/support/model_based/ICS02Tests.cfg +++ b/modules/tests/support/model_based/ICS02Tests.cfg @@ -1,5 +1,5 @@ CONSTANTS - MaxClientId = 5 + MaxClients = 5 MaxHeight = 5 INIT Init diff --git a/modules/tests/support/model_based/UpdateHeightVerificationFailureTest.json b/modules/tests/support/model_based/UpdateHeightVerificationFailureTest.json index f826069b6e..430dbc02bb 100644 --- a/modules/tests/support/model_based/UpdateHeightVerificationFailureTest.json +++ b/modules/tests/support/model_based/UpdateHeightVerificationFailureTest.json @@ -14,7 +14,7 @@ }, { "action": { - "clientId": 1, + "clientId": 0, "height": 1, "type": "UpdateClient" }, diff --git a/modules/tests/support/model_based/UpdateOKTest.json b/modules/tests/support/model_based/UpdateOKTest.json index 06a9531db5..13a5f1ebab 100644 --- a/modules/tests/support/model_based/UpdateOKTest.json +++ b/modules/tests/support/model_based/UpdateOKTest.json @@ -14,7 +14,7 @@ }, { "action": { - "clientId": 1, + "clientId": 0, "height": 2, "type": "UpdateClient" }, From cd0d0a6ac976f443f19ac13c07651b7a39017843 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 2 Feb 2021 22:40:33 +0100 Subject: [PATCH 06/52] Check for errors in MockContext.deliver --- modules/tests/model_based.rs | 54 ++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index 6ed8d8c7cf..1288b49a72 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -7,6 +7,7 @@ use ibc::ics02_client::client_type::ClientType; 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::error::Kind as ICS18ErrorKind; use ibc::ics24_host::identifier::ChainId; use ibc::ics24_host::identifier::ClientId; use ibc::ics26_routing::msgs::ICS26Envelope; @@ -47,14 +48,22 @@ impl ICS02TestExecutor { impl modelator::TestExecutor for ICS02TestExecutor { fn check_initial_state(&mut self, state: State) -> bool { - let type_is_null = state.action.action_type == ActionType::Null; - let outcome_is_null = state.action_outcome == ActionOutcome::Null; - type_is_null && outcome_is_null + assert_eq!( + state.action.action_type, + ActionType::Null, + "unexpected action type" + ); + assert_eq!( + state.action_outcome, + ActionOutcome::Null, + "unexpected action outcome" + ); + true } fn check_next_state(&mut self, state: State) -> bool { match state.action.action_type { - ActionType::Null => panic!("next state action type cannot be null"), + ActionType::Null => panic!("unexpected action type"), ActionType::CreateClient => { // get action parameters let height = state @@ -70,13 +79,23 @@ impl modelator::TestExecutor for ICS02TestExecutor { // create dummy signer let signer = self.dummy_signer(); - // create ICS26 message + // create ICS26 message and deliver it let msg = ICS26Envelope::ICS2Msg(ClientMsg::CreateClient(MsgCreateAnyClient { client_state, consensus_state, signer, })); - self.ctx.deliver(msg).unwrap(); + let result = self.ctx.deliver(msg); + + // check the expected outcome: client create always succeeds + assert_eq!( + state.action_outcome, + ActionOutcome::CreateOK, + "unexpected action outcome" + ); + if let Err(e) = result { + panic!("{:?}", e); + } true } ActionType::UpdateClient => { @@ -98,13 +117,32 @@ impl modelator::TestExecutor for ICS02TestExecutor { // create dummy signer let signer = self.dummy_signer(); - // create ICS26 message + // create ICS26 message and deliver it let msg = ICS26Envelope::ICS2Msg(ClientMsg::UpdateClient(MsgUpdateAnyClient { client_id, header, signer, })); - self.ctx.deliver(msg).unwrap(); + let result = self.ctx.deliver(msg); + + match state.action_outcome { + ActionOutcome::Null | ActionOutcome::CreateOK => { + panic!("unexpected action outcome") + } + ActionOutcome::UpdateOK => { + // check that there were no errors + assert!(result.is_ok(), "UpdateOK outcome expected"); + } + ActionOutcome::UpdateClientNotFound => { + assert!(result.is_err(), "UpdateClientNotFound outcome expected"); + todo!() + } + ActionOutcome::UpdateHeightVerificationFailure => { + let error = + result.expect_err("UpdateHeightVerificationFailure outcome expected"); + assert!(matches!(error.kind(), ICS18ErrorKind::TransactionFailed)); + } + } true } } From 02c020b03e93b9f4e908a2ac2e050ab5bf740b08 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 2 Feb 2021 23:07:17 +0100 Subject: [PATCH 07/52] Handle errors in MBT tests --- modules/tests/model_based.rs | 46 ++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index 1288b49a72..bc9f2ef2ac 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -4,12 +4,14 @@ mod state; use ibc::ics02_client::client_def::AnyHeader; use ibc::ics02_client::client_def::{AnyClientState, AnyConsensusState}; use ibc::ics02_client::client_type::ClientType; +use ibc::ics02_client::error::{Error as ICS02Error, 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::error::Kind as ICS18ErrorKind; +use ibc::ics18_relayer::error::{Error as ICS18Error, Kind as ICS18ErrorKind}; use ibc::ics24_host::identifier::ChainId; use ibc::ics24_host::identifier::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; @@ -17,6 +19,7 @@ use ibc::mock::header::MockHeader; use ibc::mock::host::HostType; use ibc::Height; use state::{ActionOutcome, ActionType, State}; +use std::error::Error; use std::fmt::Debug; use tendermint::account::Id as AccountId; @@ -119,7 +122,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { // create ICS26 message and deliver it let msg = ICS26Envelope::ICS2Msg(ClientMsg::UpdateClient(MsgUpdateAnyClient { - client_id, + client_id: client_id.clone(), header, signer, })); @@ -134,13 +137,18 @@ impl modelator::TestExecutor for ICS02TestExecutor { assert!(result.is_ok(), "UpdateOK outcome expected"); } ActionOutcome::UpdateClientNotFound => { - assert!(result.is_err(), "UpdateClientNotFound outcome expected"); - todo!() + let handler_error_kind = Self::extract_ics02_handler_error_kind(result); + assert!(matches!( + handler_error_kind, + ICS02ErrorKind::ClientNotFound(id) if id == client_id + )); } ActionOutcome::UpdateHeightVerificationFailure => { - let error = - result.expect_err("UpdateHeightVerificationFailure outcome expected"); - assert!(matches!(error.kind(), ICS18ErrorKind::TransactionFailed)); + let handler_error_kind = Self::extract_ics02_handler_error_kind(result); + assert!(matches!( + handler_error_kind, + ICS02ErrorKind::HeaderVerificationFailure + )); } } true @@ -157,6 +165,30 @@ impl ICS02TestExecutor { fn mock_header(&self, height: u64) -> MockHeader { MockHeader(Height::new(self.version, height)) } + + fn extract_ics02_handler_error_kind(result: Result<(), ICS18Error>) -> ICS02ErrorKind { + let ics18_error = 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::() + .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::() + .expect("ICS26 source should be an ICS02 error") + .kind() + .clone() + } } const TESTS_DIR: &str = "tests/support/model_based"; From a5d06c53dccf822a5576a1248f1d09a7b6ece1eb Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 2 Feb 2021 23:09:31 +0100 Subject: [PATCH 08/52] Remove SyntheticTendermint mock context --- modules/tests/model_based.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index bc9f2ef2ac..89bc4c020b 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -32,19 +32,15 @@ struct ICS02TestExecutor { impl ICS02TestExecutor { fn new() -> Self { let version = 1; + let max_history_size = 1; + let initial_height = 0; let ctx = MockContext::new( ChainId::new("mock".to_string(), version), HostType::Mock, - 1, - Height::new(version, 0), + // HostType::SyntheticTendermint, + max_history_size, + Height::new(version, initial_height), ); - // let ctx = MockContext::new( - // ChainId::new("mock".to_string(), cv), - // HostType::SyntheticTendermint, - // 1, - // Height::new(cv, 0), - // ); - Self { version, ctx } } } From 356c6d231c2506ca61d3075336c6fccf8fcc3e31 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 2 Feb 2021 23:18:31 +0100 Subject: [PATCH 09/52] More idiomatic check_next_state --- modules/tests/model_based.rs | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index 89bc4c020b..bc672d9b03 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -68,7 +68,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { let height = state .action .height - .expect("update client action should have a height"); + .expect("create client action should have a height"); // create client and consensus state from parameters let client_state = AnyClientState::Mock(MockClientState(self.mock_header(height))); @@ -92,10 +92,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { ActionOutcome::CreateOK, "unexpected action outcome" ); - if let Err(e) = result { - panic!("{:?}", e); - } - true + result.is_ok() } ActionType::UpdateClient => { // get action parameters @@ -130,24 +127,20 @@ impl modelator::TestExecutor for ICS02TestExecutor { } ActionOutcome::UpdateOK => { // check that there were no errors - assert!(result.is_ok(), "UpdateOK outcome expected"); + result.is_ok() } ActionOutcome::UpdateClientNotFound => { - let handler_error_kind = Self::extract_ics02_handler_error_kind(result); - assert!(matches!( + let handler_error_kind = self.extract_ics02_handler_error_kind(result); + matches!( handler_error_kind, ICS02ErrorKind::ClientNotFound(id) if id == client_id - )); + ) } ActionOutcome::UpdateHeightVerificationFailure => { - let handler_error_kind = Self::extract_ics02_handler_error_kind(result); - assert!(matches!( - handler_error_kind, - ICS02ErrorKind::HeaderVerificationFailure - )); + let handler_error_kind = self.extract_ics02_handler_error_kind(result); + handler_error_kind == ICS02ErrorKind::HeaderVerificationFailure } } - true } } } @@ -162,7 +155,7 @@ impl ICS02TestExecutor { MockHeader(Height::new(self.version, height)) } - fn extract_ics02_handler_error_kind(result: Result<(), ICS18Error>) -> ICS02ErrorKind { + fn extract_ics02_handler_error_kind(&self, result: Result<(), ICS18Error>) -> ICS02ErrorKind { let ics18_error = result.expect_err("ICS18 error expected"); assert!(matches!( ics18_error.kind(), From 6ab8611cf6c34485a8a9cae46196afc9c60a2a97 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Wed, 3 Feb 2021 10:21:33 +0100 Subject: [PATCH 10/52] Buffered file reads --- modules/tests/modelator.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/tests/modelator.rs b/modules/tests/modelator.rs index 8663e205e8..d395119e20 100644 --- a/modules/tests/modelator.rs +++ b/modules/tests/modelator.rs @@ -2,6 +2,7 @@ use eyre::{eyre, Context, Result}; use serde::de::DeserializeOwned; use std::fmt::Debug; use std::fs::File; +use std::io::BufReader; use std::path::Path; pub trait TestExecutor { @@ -16,13 +17,18 @@ where S: DeserializeOwned + Debug + Clone, P: AsRef, { - let reader = File::open(path.as_ref()) + // open test file + let file = File::open(path.as_ref()) .wrap_err_with(|| format!("test file {:?} not found.", path.as_ref()))?; + let reader = BufReader::new(file); + + // parse test file let states: Vec = serde_json::de::from_reader(reader) .wrap_err_with(|| format!("test file {:?} could not be deserialized", path.as_ref()))?; let mut states = states.into_iter(); + // check the initial state if let Some(state) = states.next() { if !executor.check_initial_state(state.clone()) { return Err(eyre!("check failed on initial state:\n{:#?}", state)); @@ -32,6 +38,7 @@ where return Ok(()); } + // check all the remaining states for state in states { if !executor.check_next_state(state.clone()) { return Err(eyre!( From 490d77bd5a4218865398deef1024a2539e90de00 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Wed, 3 Feb 2021 16:23:58 +0100 Subject: [PATCH 11/52] Make extract_handler_error_kind generic over the IBC handler --- modules/tests/model_based.rs | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index bc672d9b03..1ee6408e86 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -4,7 +4,7 @@ mod state; use ibc::ics02_client::client_def::AnyHeader; use ibc::ics02_client::client_def::{AnyClientState, AnyConsensusState}; use ibc::ics02_client::client_type::ClientType; -use ibc::ics02_client::error::{Error as ICS02Error, Kind as ICS02ErrorKind}; +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; @@ -20,7 +20,7 @@ use ibc::mock::host::HostType; use ibc::Height; use state::{ActionOutcome, ActionType, State}; use std::error::Error; -use std::fmt::Debug; +use std::fmt::{Debug, Display}; use tendermint::account::Id as AccountId; #[derive(Debug)] @@ -92,6 +92,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { ActionOutcome::CreateOK, "unexpected action outcome" ); + // the implementaion matches the model if no error occurs result.is_ok() } ActionType::UpdateClient => { @@ -126,18 +127,24 @@ impl modelator::TestExecutor for ICS02TestExecutor { panic!("unexpected action outcome") } ActionOutcome::UpdateOK => { - // check that there were no errors + // the implementaion matches the model if no error occurs result.is_ok() } ActionOutcome::UpdateClientNotFound => { - let handler_error_kind = self.extract_ics02_handler_error_kind(result); + let handler_error_kind = + self.extract_handler_error_kind::(result); + // the implementaion matches the model if there's an + // error matching the expected outcome matches!( handler_error_kind, ICS02ErrorKind::ClientNotFound(id) if id == client_id ) } ActionOutcome::UpdateHeightVerificationFailure => { - let handler_error_kind = self.extract_ics02_handler_error_kind(result); + let handler_error_kind = + self.extract_handler_error_kind::(result); + // the implementaion matches the model if there's an + // error matching the expected outcome handler_error_kind == ICS02ErrorKind::HeaderVerificationFailure } } @@ -155,7 +162,10 @@ impl ICS02TestExecutor { MockHeader(Height::new(self.version, height)) } - fn extract_ics02_handler_error_kind(&self, result: Result<(), ICS18Error>) -> ICS02ErrorKind { + fn extract_handler_error_kind(&self, result: Result<(), ICS18Error>) -> K + where + K: Clone + Debug + Display + Into + 'static, + { let ics18_error = result.expect_err("ICS18 error expected"); assert!(matches!( ics18_error.kind(), @@ -173,8 +183,8 @@ impl ICS02TestExecutor { ics26_error .source() .expect("expected source in ICS26 error") - .downcast_ref::() - .expect("ICS26 source should be an ICS02 error") + .downcast_ref::>() + .expect("ICS26 source should be an error") .kind() .clone() } From 92b843c64d9660e7408d975222d95590340f0bdb Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Wed, 3 Feb 2021 16:44:22 +0100 Subject: [PATCH 12/52] Support multiple chains in MBT --- modules/tests/model_based.rs | 141 ++++++++------- modules/tests/state.rs | 3 + modules/tests/support/model_based/ICS02.cfg | 5 +- modules/tests/support/model_based/ICS02.tla | 164 ++++++++++++------ .../tests/support/model_based/ICS02Tests.cfg | 5 +- .../tests/support/model_based/ICS02Tests.tla | 2 +- modules/tests/support/model_based/Makefile | 6 +- .../UpdateHeightVerificationFailureTest.json | 2 + .../support/model_based/UpdateOKTest.json | 36 ++++ 9 files changed, 240 insertions(+), 124 deletions(-) diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index 1ee6408e86..fa57b0139a 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -19,29 +19,75 @@ use ibc::mock::header::MockHeader; use ibc::mock::host::HostType; use ibc::Height; use state::{ActionOutcome, ActionType, State}; +use std::collections::HashMap; use std::error::Error; use std::fmt::{Debug, Display}; use tendermint::account::Id as AccountId; +// all chains use the same version +const VERSION: u64 = 0; + #[derive(Debug)] struct ICS02TestExecutor { - version: u64, - ctx: MockContext, + // mapping from chain identifier to its context + contexts: HashMap, } impl ICS02TestExecutor { fn new() -> Self { - let version = 1; - let max_history_size = 1; - let initial_height = 0; - let ctx = MockContext::new( - ChainId::new("mock".to_string(), version), - HostType::Mock, - // HostType::SyntheticTendermint, - max_history_size, - Height::new(version, initial_height), - ); - Self { version, ctx } + Self { + contexts: Default::default(), + } + } + + /// Find the `MockContext` of a given `chain_id`. + /// If no context is found, a new one is created. + fn get_chain_context_or_init(&mut self, chain_id: String) -> &mut MockContext { + self.contexts.entry(chain_id.clone()).or_insert_with(|| { + let max_history_size = 1; + let initial_height = 0; + MockContext::new( + ChainId::new(chain_id, VERSION), + HostType::Mock, + max_history_size, + Height::new(VERSION, initial_height), + ) + }) + } + + fn dummy_signer() -> AccountId { + AccountId::new([0; 20]) + } + + fn mock_header(height: u64) -> MockHeader { + MockHeader(Height::new(VERSION, height)) + } + + fn extract_handler_error_kind(result: Result<(), ICS18Error>) -> K + where + K: Clone + Debug + Display + Into + 'static, + { + let ics18_error = 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::() + .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::>() + .expect("ICS26 source should be an error") + .kind() + .clone() } } @@ -65,18 +111,25 @@ impl modelator::TestExecutor for ICS02TestExecutor { ActionType::Null => panic!("unexpected action type"), ActionType::CreateClient => { // get action parameters + let chain_id = state + .action + .chain_id + .expect("create client action should have a chain identifier"); let height = state .action .height .expect("create client action should have a height"); + // get chain's context + let ctx = self.get_chain_context_or_init(chain_id); + // create client and consensus state from parameters - let client_state = AnyClientState::Mock(MockClientState(self.mock_header(height))); + let client_state = AnyClientState::Mock(MockClientState(Self::mock_header(height))); let consensus_state = - AnyConsensusState::Mock(MockConsensusState(self.mock_header(height))); + AnyConsensusState::Mock(MockConsensusState(Self::mock_header(height))); // create dummy signer - let signer = self.dummy_signer(); + let signer = Self::dummy_signer(); // create ICS26 message and deliver it let msg = ICS26Envelope::ICS2Msg(ClientMsg::CreateClient(MsgCreateAnyClient { @@ -84,7 +137,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { consensus_state, signer, })); - let result = self.ctx.deliver(msg); + let result = ctx.deliver(msg); // check the expected outcome: client create always succeeds assert_eq!( @@ -97,6 +150,10 @@ impl modelator::TestExecutor for ICS02TestExecutor { } ActionType::UpdateClient => { // get action parameters + let chain_id = state + .action + .chain_id + .expect("update client action should have a chain identifier"); let client_id = state .action .client_id @@ -106,13 +163,16 @@ impl modelator::TestExecutor for ICS02TestExecutor { .height .expect("update client action should have a height"); + // get chain's context + let ctx = self.get_chain_context_or_init(chain_id); + // create client id and header from action parameters let client_id = ClientId::new(ClientType::Mock, client_id) .expect("it should be possible to create the client identifier"); - let header = AnyHeader::Mock(self.mock_header(height)); + let header = AnyHeader::Mock(Self::mock_header(height)); // create dummy signer - let signer = self.dummy_signer(); + let signer = Self::dummy_signer(); // create ICS26 message and deliver it let msg = ICS26Envelope::ICS2Msg(ClientMsg::UpdateClient(MsgUpdateAnyClient { @@ -120,7 +180,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { header, signer, })); - let result = self.ctx.deliver(msg); + let result = ctx.deliver(msg); match state.action_outcome { ActionOutcome::Null | ActionOutcome::CreateOK => { @@ -132,7 +192,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { } ActionOutcome::UpdateClientNotFound => { let handler_error_kind = - self.extract_handler_error_kind::(result); + Self::extract_handler_error_kind::(result); // the implementaion matches the model if there's an // error matching the expected outcome matches!( @@ -142,7 +202,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { } ActionOutcome::UpdateHeightVerificationFailure => { let handler_error_kind = - self.extract_handler_error_kind::(result); + Self::extract_handler_error_kind::(result); // the implementaion matches the model if there's an // error matching the expected outcome handler_error_kind == ICS02ErrorKind::HeaderVerificationFailure @@ -153,43 +213,6 @@ impl modelator::TestExecutor for ICS02TestExecutor { } } -impl ICS02TestExecutor { - fn dummy_signer(&self) -> AccountId { - AccountId::new([0; 20]) - } - - fn mock_header(&self, height: u64) -> MockHeader { - MockHeader(Height::new(self.version, height)) - } - - fn extract_handler_error_kind(&self, result: Result<(), ICS18Error>) -> K - where - K: Clone + Debug + Display + Into + 'static, - { - let ics18_error = 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::() - .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::>() - .expect("ICS26 source should be an error") - .kind() - .clone() - } -} - const TESTS_DIR: &str = "tests/support/model_based"; #[test] diff --git a/modules/tests/state.rs b/modules/tests/state.rs index 7b4cfdb06d..eff0fd72a4 100644 --- a/modules/tests/state.rs +++ b/modules/tests/state.rs @@ -14,6 +14,9 @@ pub struct Action { #[serde(alias = "type")] pub action_type: ActionType, + #[serde(alias = "chainId")] + pub chain_id: Option, + #[serde(alias = "clientId")] pub client_id: Option, diff --git a/modules/tests/support/model_based/ICS02.cfg b/modules/tests/support/model_based/ICS02.cfg index c23bd845b2..5445bf7d9b 100644 --- a/modules/tests/support/model_based/ICS02.cfg +++ b/modules/tests/support/model_based/ICS02.cfg @@ -1,6 +1,7 @@ CONSTANTS - MaxClients = 5 - MaxHeight = 5 + ChainIds = {"chain-A", "chain-B"} + MaxClientsPerChain = 4 + MaxClientHeight = 4 INIT Init NEXT Next diff --git a/modules/tests/support/model_based/ICS02.tla b/modules/tests/support/model_based/ICS02.tla index 5dd5690c50..756d11cb76 100644 --- a/modules/tests/support/model_based/ICS02.tla +++ b/modules/tests/support/model_based/ICS02.tla @@ -2,17 +2,17 @@ EXTENDS Integers, FiniteSets +\* ids of existing chains +CONSTANT ChainIds \* max number of client to be created -CONSTANT MaxClients -ASSUME MaxClients > 0 +CONSTANT MaxClientsPerChain +ASSUME MaxClientsPerChain > 0 \* max height which clients can reach -CONSTANT MaxHeight -ASSUME MaxHeight > 0 +CONSTANT MaxClientHeight +ASSUME MaxClientHeight > 0 -\* set of client data -VARIABLE clients -\* counter used to generate client identifiers -VARIABLE clientIdCounter +\* mapping from chain id to its data +VARIABLE chains \* last action performed VARIABLE action \* string with the outcome of the last operation @@ -24,6 +24,7 @@ a <: b == a ActionType == [ type |-> STRING, + chainId |-> STRING, clientId |-> Int, height |-> Int ] @@ -31,111 +32,160 @@ AsAction(a) == a <: ActionType (****************** END OF TYPE ANNOTATIONS FOR APALACHE ********************) \* set of possible client identifiers -ClientIds == 0..(MaxClients - 1) -\* set of possible heights -Heights == 1..MaxHeight +ClientIds == 0..(MaxClientsPerChain - 1) +\* set of possible client heights +ClientHeights == 1..MaxClientHeight \* if a client has a null height then the client does not exist NullHeight == 0 + +Clients == [ + ClientIds -> ClientHeights \union {NullHeight} +] +Chain == [ + clientIdCounter: 0..MaxClientsPerChain, + clients: Clients +] + \* set of possible actions NullActions == [ type: {"Null"} ] <: {ActionType} CreateClientActions == [ type: {"CreateClient"}, - height: Heights + chainId: ChainIds, + height: ClientHeights ] <: {ActionType} UpdateClientActions == [ type: {"UpdateClient"}, + chainId: ChainIds, clientId: ClientIds, - height: Heights + height: ClientHeights ] <: {ActionType} -Actions == NullActions \union CreateClientActions \union UpdateClientActions -\* set of possible outcomes -ActionOutcomes == {"Null", "CreateOK", "UpdateOK", "UpdateClientNotFound", "UpdateHeightVerificationFailure", "ModelError"} - +Actions == + NullActions \union + CreateClientActions \union + UpdateClientActions + +\* set of possible action outcomes +ActionOutcomes == { + "Null", + "CreateOK", + "UpdateOK", + "UpdateClientNotFound", + "UpdateHeightVerificationFailure", + "ModelError" +} (*************************************************************************** Specification ***************************************************************************) -\* check if a client exists -ClientExists(clientId) == - clients[clientId] /= NullHeight +\* retrieves `clientId`'s height +GetClientHeight(clients, clientId) == + clients[clientId] -SetClientHeight(clientId, clientHeight) == +\* check if a `clientId` exists +ClientExists(clients, clientId) == + GetClientHeight(clients, clientId) /= NullHeight + +\* update the heigth of `clientId` to `clientHeight` +SetClientHeight(clients, clientId, clientHeight) == [clients EXCEPT ![clientId] = clientHeight] -CreateClient(clientHeight) == +CreateClient(chainId, clientHeight) == + LET clients == chains[chainId].clients + clientIdCounter == chains[chainId].clientIdCounter IN \* check if the client exists (it shouldn't) - IF ClientExists(clientIdCounter) THEN + IF ClientExists(clients, clientIdCounter) THEN \* if the client to be created already exists, \* then there's an error in the model /\ actionOutcome' = "ModelError" - /\ UNCHANGED <> + /\ UNCHANGED <> ELSE - \* set the new client's height to `clientHeight` - /\ clients' = SetClientHeight(clientIdCounter, clientHeight) - \* update `clientIdCounter` - /\ clientIdCounter' = clientIdCounter + 1 + LET chain == [ + \* set the new client's height to `clientHeight` + clients |-> SetClientHeight(clients, clientIdCounter, clientHeight), + \* update `clientIdCounter` + clientIdCounter |-> clientIdCounter + 1 + ] IN + \* update `chains` + /\ chains' = [chains EXCEPT ![chainId] = chain] \* set `outcome` /\ actionOutcome' = "CreateOK" -UpdateClient(clientId, clientHeight) == +UpdateClient(chainId, clientId, clientHeight) == + LET clients == chains[chainId].clients + clientIdCounter == chains[chainId].clientIdCounter IN \* check if the client exists - IF ClientExists(clientId) THEN + IF ClientExists(clients, clientId) THEN \* if the client exists, check its height - IF clients[clientId] < clientHeight THEN + IF GetClientHeight(clients, clientId) < clientHeight THEN \* if its height is lower than the one being updated to \* then, update the client - /\ clients' = SetClientHeight(clientId, clientHeight) + LET chain == [ + \* set the client's height to `clientHeight` + clients |-> SetClientHeight(clients, clientId, clientHeight), + \* keep `clientIdCounter` + clientIdCounter |-> clientIdCounter + ] IN + \* update `chains` + /\ chains' = [chains EXCEPT ![chainId] = chain] \* set outcome /\ actionOutcome' = "UpdateOK" - /\ UNCHANGED <> ELSE /\ actionOutcome' = "UpdateHeightVerificationFailure" - /\ UNCHANGED <> + /\ UNCHANGED <> ELSE \* if the client does not exist, then return an error /\ actionOutcome' = "UpdateClientNotFound" - /\ UNCHANGED <> + /\ UNCHANGED <> CreateClientAction == - \* only create client if the model constant `MaxClients` allows it - /\ clientIdCounter \in ClientIds - \* select a height for the client to be created at - /\ \E clientHeight \in Heights: - /\ CreateClient(clientHeight) - /\ action' = AsAction([type |-> "CreateClient", - height |-> clientHeight]) - -UpdateClientAction == - \* select a client to be updated (which may not exist) - \E clientId \in ClientIds: - \* select a height for the client to be updated - \E clientHeight \in Heights: - /\ UpdateClient(clientId, clientHeight) - /\ action' = AsAction([type |-> "UpdateClient", - clientId |-> clientId, + \* select a chain + \E chainId \in ChainIds: + \* only create client if the model constant `MaxClientsPerChain` allows it + /\ chains[chainId].clientIdCounter \in ClientIds + \* select a height for the client to be created at + /\ \E clientHeight \in ClientHeights: + /\ CreateClient(chainId, clientHeight) + /\ action' = AsAction([type |-> "CreateClient", + chainId |-> chainId, height |-> clientHeight]) - Init == - /\ clients = [clientId \in ClientIds |-> NullHeight] - /\ clientIdCounter = 0 +UpdateClientAction == + \* select a chain + \E chainId \in ChainIds: + \* select a client to be updated (which may not exist) + \E clientId \in ClientIds: + \* select a height for the client to be updated + \E clientHeight \in ClientHeights: + /\ UpdateClient(chainId, clientId, clientHeight) + /\ action' = AsAction([type |-> "UpdateClient", + chainId |-> chainId, + clientId |-> clientId, + height |-> clientHeight]) + +Init == + \* create an empty chain + LET emptyChain == [ + clients |-> [clientId \in ClientIds |-> NullHeight], + clientIdCounter |-> 0 + ] IN + /\ chains = [chainId \in ChainIds |-> emptyChain] /\ action = AsAction([type |-> "Null"]) /\ actionOutcome = "Null" Next == \/ CreateClientAction \/ UpdateClientAction - \/ UNCHANGED <> + \/ UNCHANGED <> (*************************************************************************** Invariants ***************************************************************************) TypeOK == - /\ clientIdCounter \in 0..MaxClients - /\ clients \in [ClientIds -> Heights \union {NullHeight}] + /\ chains \in [ChainIds -> Chain] /\ action \in Actions /\ actionOutcome \in ActionOutcomes diff --git a/modules/tests/support/model_based/ICS02Tests.cfg b/modules/tests/support/model_based/ICS02Tests.cfg index 79b45ebaf0..0950dd0e71 100644 --- a/modules/tests/support/model_based/ICS02Tests.cfg +++ b/modules/tests/support/model_based/ICS02Tests.cfg @@ -1,6 +1,7 @@ CONSTANTS - MaxClients = 5 - MaxHeight = 5 + ChainIds = {"chain-A", "chain-B"} + MaxClientsPerChain = 4 + MaxClientHeight = 4 INIT Init NEXT Next diff --git a/modules/tests/support/model_based/ICS02Tests.tla b/modules/tests/support/model_based/ICS02Tests.tla index 3664de67ab..f700a50624 100644 --- a/modules/tests/support/model_based/ICS02Tests.tla +++ b/modules/tests/support/model_based/ICS02Tests.tla @@ -1,6 +1,6 @@ ------------------------- MODULE ICS02Tests --------------------------- -EXTENDS ICS02 +EXTENDS IBC CreateOK == /\ actionOutcome = "CreateOK" diff --git a/modules/tests/support/model_based/Makefile b/modules/tests/support/model_based/Makefile index 9e5318a5f2..21e2f7c8a3 100644 --- a/modules/tests/support/model_based/Makefile +++ b/modules/tests/support/model_based/Makefile @@ -1,6 +1,6 @@ +check: + apalache-mc check --inv=ModelNeverErrors ICS02.tla + test: apalache-mc check ICS02Tests.tla -check: - apalache-mc check ICS02.tla - diff --git a/modules/tests/support/model_based/UpdateHeightVerificationFailureTest.json b/modules/tests/support/model_based/UpdateHeightVerificationFailureTest.json index 430dbc02bb..0ae9ca9289 100644 --- a/modules/tests/support/model_based/UpdateHeightVerificationFailureTest.json +++ b/modules/tests/support/model_based/UpdateHeightVerificationFailureTest.json @@ -7,6 +7,7 @@ }, { "action": { + "chainId": "chain-B", "height": 1, "type": "CreateClient" }, @@ -14,6 +15,7 @@ }, { "action": { + "chainId": "chain-B", "clientId": 0, "height": 1, "type": "UpdateClient" diff --git a/modules/tests/support/model_based/UpdateOKTest.json b/modules/tests/support/model_based/UpdateOKTest.json index 13a5f1ebab..a3d4867b14 100644 --- a/modules/tests/support/model_based/UpdateOKTest.json +++ b/modules/tests/support/model_based/UpdateOKTest.json @@ -7,6 +7,7 @@ }, { "action": { + "chainId": "chain-B", "height": 1, "type": "CreateClient" }, @@ -14,10 +15,45 @@ }, { "action": { + "chainId": "chain-B", "clientId": 0, "height": 2, "type": "UpdateClient" }, "actionOutcome": "UpdateOK" + }, + { + "action": { + "chainId": "chain-A", + "height": 1, + "type": "CreateClient" + }, + "actionOutcome": "CreateOK" + }, + { + "action": { + "chainId": "chain-A", + "clientId": 0, + "height": 2, + "type": "UpdateClient" + }, + "actionOutcome": "UpdateOK" + }, + { + "action": { + "chainId": "chain-A", + "height": 1, + "type": "CreateClient" + }, + "actionOutcome": "CreateOK" + }, + { + "action": { + "chainId": "chain-A", + "clientId": 1, + "height": 2, + "type": "UpdateClient" + }, + "actionOutcome": "UpdateOK" } ] \ No newline at end of file From d57cf4119a9dbfe5260ef7c1afc212bc5c305458 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Wed, 3 Feb 2021 16:48:31 +0100 Subject: [PATCH 13/52] Make eyre a dev-dependency --- modules/Cargo.toml | 3 +-- modules/src/ics02_client/handler/update_client.rs | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/Cargo.toml b/modules/Cargo.toml index 00c4a51ae8..ff08417d61 100644 --- a/modules/Cargo.toml +++ b/modules/Cargo.toml @@ -37,8 +37,6 @@ dyn-clonable = "0.9.0" regex = "1" bech32 = "0.7.2" -eyre = "0.6.5" - [dependencies.tendermint] version = "=0.18.0" @@ -54,6 +52,7 @@ version = "=0.18.0" optional = true [dev-dependencies] +eyre = "0.6.5" tokio = { version = "1.0", features = ["macros"] } subtle-encoding = { version = "0.5" } tendermint-testgen = { version = "=0.18.0" } # Needed for generating (synthetic) light blocks. diff --git a/modules/src/ics02_client/handler/update_client.rs b/modules/src/ics02_client/handler/update_client.rs index 56f3062456..d09d6c0d13 100644 --- a/modules/src/ics02_client/handler/update_client.rs +++ b/modules/src/ics02_client/handler/update_client.rs @@ -37,6 +37,8 @@ pub fn process( .ok_or_else(|| Kind::ClientNotFound(client_id.clone()))?; let client_def = AnyClient::from_client_type(client_type); + // ClientType ClientState ConsensusState + // X X X // Read client state from the host chain store. let client_state = ctx From f4dbe00b98b105480de3eb148beb98758747b264 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Wed, 3 Feb 2021 16:53:55 +0100 Subject: [PATCH 14/52] s/ICS0/IBC on TLA files --- modules/tests/support/model_based/{ICS02.cfg => IBC.cfg} | 0 modules/tests/support/model_based/{ICS02.tla => IBC.tla} | 2 +- .../support/model_based/{ICS02Tests.cfg => IBCTests.cfg} | 0 .../support/model_based/{ICS02Tests.tla => IBCTests.tla} | 2 +- modules/tests/support/model_based/Makefile | 5 ++--- 5 files changed, 4 insertions(+), 5 deletions(-) rename modules/tests/support/model_based/{ICS02.cfg => IBC.cfg} (100%) rename modules/tests/support/model_based/{ICS02.tla => IBC.tla} (98%) rename modules/tests/support/model_based/{ICS02Tests.cfg => IBCTests.cfg} (100%) rename modules/tests/support/model_based/{ICS02Tests.tla => IBCTests.tla} (87%) diff --git a/modules/tests/support/model_based/ICS02.cfg b/modules/tests/support/model_based/IBC.cfg similarity index 100% rename from modules/tests/support/model_based/ICS02.cfg rename to modules/tests/support/model_based/IBC.cfg diff --git a/modules/tests/support/model_based/ICS02.tla b/modules/tests/support/model_based/IBC.tla similarity index 98% rename from modules/tests/support/model_based/ICS02.tla rename to modules/tests/support/model_based/IBC.tla index 756d11cb76..81b9df6401 100644 --- a/modules/tests/support/model_based/ICS02.tla +++ b/modules/tests/support/model_based/IBC.tla @@ -1,4 +1,4 @@ ---------------------------- MODULE ICS02 ---------------------------- +--------------------------- MODULE IBC ---------------------------- EXTENDS Integers, FiniteSets diff --git a/modules/tests/support/model_based/ICS02Tests.cfg b/modules/tests/support/model_based/IBCTests.cfg similarity index 100% rename from modules/tests/support/model_based/ICS02Tests.cfg rename to modules/tests/support/model_based/IBCTests.cfg diff --git a/modules/tests/support/model_based/ICS02Tests.tla b/modules/tests/support/model_based/IBCTests.tla similarity index 87% rename from modules/tests/support/model_based/ICS02Tests.tla rename to modules/tests/support/model_based/IBCTests.tla index f700a50624..f24eb10cc2 100644 --- a/modules/tests/support/model_based/ICS02Tests.tla +++ b/modules/tests/support/model_based/IBCTests.tla @@ -1,4 +1,4 @@ -------------------------- MODULE ICS02Tests --------------------------- +------------------------- MODULE IBCTests --------------------------- EXTENDS IBC diff --git a/modules/tests/support/model_based/Makefile b/modules/tests/support/model_based/Makefile index 21e2f7c8a3..e0cdfbb248 100644 --- a/modules/tests/support/model_based/Makefile +++ b/modules/tests/support/model_based/Makefile @@ -1,6 +1,5 @@ check: - apalache-mc check --inv=ModelNeverErrors ICS02.tla + apalache-mc check --inv=ModelNeverErrors IBC.tla test: - apalache-mc check ICS02Tests.tla - + apalache-mc check IBCTests.tla From 20867a5affebaad55d1151de9e479788935e6624 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Wed, 3 Feb 2021 18:52:10 +0100 Subject: [PATCH 15/52] Initial support for conn open init --- modules/tests/model_based.rs | 28 ++-- modules/tests/state.rs | 11 +- modules/tests/support/model_based/IBC.cfg | 5 +- modules/tests/support/model_based/IBC.tla | 132 +++++++++++------- .../UpdateHeightVerificationFailureTest.json | 4 +- .../support/model_based/UpdateOKTest.json | 12 +- 6 files changed, 116 insertions(+), 76 deletions(-) diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index fa57b0139a..51b1f32494 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -140,13 +140,13 @@ impl modelator::TestExecutor for ICS02TestExecutor { let result = ctx.deliver(msg); // check the expected outcome: client create always succeeds - assert_eq!( - state.action_outcome, - ActionOutcome::CreateOK, - "unexpected action outcome" - ); - // the implementaion matches the model if no error occurs - result.is_ok() + match state.action_outcome { + ActionOutcome::ICS02OK => { + // the implementaion matches the model if no error occurs + result.is_ok() + } + action => panic!("unexpected action outcome {:?}", action), + } } ActionType::UpdateClient => { // get action parameters @@ -182,15 +182,13 @@ impl modelator::TestExecutor for ICS02TestExecutor { })); let result = ctx.deliver(msg); + // check the expected outcome match state.action_outcome { - ActionOutcome::Null | ActionOutcome::CreateOK => { - panic!("unexpected action outcome") - } - ActionOutcome::UpdateOK => { + ActionOutcome::ICS02OK => { // the implementaion matches the model if no error occurs result.is_ok() } - ActionOutcome::UpdateClientNotFound => { + ActionOutcome::ICS02ClientNotFound => { let handler_error_kind = Self::extract_handler_error_kind::(result); // the implementaion matches the model if there's an @@ -200,15 +198,19 @@ impl modelator::TestExecutor for ICS02TestExecutor { ICS02ErrorKind::ClientNotFound(id) if id == client_id ) } - ActionOutcome::UpdateHeightVerificationFailure => { + ActionOutcome::ICS02HeaderVerificationFailure => { let handler_error_kind = Self::extract_handler_error_kind::(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), } } + ActionType::ConnectionOpenInit => { + todo!() + } } } } diff --git a/modules/tests/state.rs b/modules/tests/state.rs index eff0fd72a4..82e9255fd3 100644 --- a/modules/tests/state.rs +++ b/modules/tests/state.rs @@ -28,13 +28,16 @@ pub enum ActionType { Null, CreateClient, UpdateClient, + ConnectionOpenInit, } #[derive(Debug, Clone, PartialEq, Deserialize)] pub enum ActionOutcome { Null, - CreateOK, - UpdateOK, - UpdateClientNotFound, - UpdateHeightVerificationFailure, + #[serde(alias = "ICS02_OK")] + ICS02OK, + #[serde(alias = "ICS02_ClientNotFound")] + ICS02ClientNotFound, + #[serde(alias = "ICS02_HeaderVerificationFailure")] + ICS02HeaderVerificationFailure, } diff --git a/modules/tests/support/model_based/IBC.cfg b/modules/tests/support/model_based/IBC.cfg index 5445bf7d9b..75f117802a 100644 --- a/modules/tests/support/model_based/IBC.cfg +++ b/modules/tests/support/model_based/IBC.cfg @@ -1,7 +1,8 @@ CONSTANTS ChainIds = {"chain-A", "chain-B"} - MaxClientsPerChain = 4 - MaxClientHeight = 4 + MaxClientsPerChain = 2 + MaxClientHeight = 2 + MaxConnectionsPerChain = 2 INIT Init NEXT Next diff --git a/modules/tests/support/model_based/IBC.tla b/modules/tests/support/model_based/IBC.tla index 81b9df6401..a854fc5415 100644 --- a/modules/tests/support/model_based/IBC.tla +++ b/modules/tests/support/model_based/IBC.tla @@ -4,12 +4,15 @@ EXTENDS Integers, FiniteSets \* ids of existing chains CONSTANT ChainIds -\* max number of client to be created +\* max number of client to be created per chain CONSTANT MaxClientsPerChain ASSUME MaxClientsPerChain > 0 \* max height which clients can reach CONSTANT MaxClientHeight ASSUME MaxClientHeight > 0 +\* max number of connections to be created per chain +CONSTANT MaxConnectionsPerChain +ASSUME MaxConnectionsPerChain > 0 \* mapping from chain id to its data VARIABLE chains @@ -25,8 +28,9 @@ a <: b == a ActionType == [ type |-> STRING, chainId |-> STRING, + height |-> Int, clientId |-> Int, - height |-> Int + counterpartyClientId |-> Int ] AsAction(a) == a <: ActionType (****************** END OF TYPE ANNOTATIONS FOR APALACHE ********************) @@ -37,13 +41,18 @@ ClientIds == 0..(MaxClientsPerChain - 1) ClientHeights == 1..MaxClientHeight \* if a client has a null height then the client does not exist NullHeight == 0 +\* set of possible connection identifiers +ConnectionIds == 0..(MaxConnectionsPerChain- 1) +\* mapping from client identifier to its height Clients == [ ClientIds -> ClientHeights \union {NullHeight} ] +\* data kept per chain Chain == [ + clients: Clients, clientIdCounter: 0..MaxClientsPerChain, - clients: Clients + connectionIdCounter: 0..MaxConnectionsPerChain ] \* set of possible actions @@ -61,18 +70,24 @@ UpdateClientActions == [ clientId: ClientIds, height: ClientHeights ] <: {ActionType} +ConnectionOpenInitActions == [ + type: {"ConnectionOpenInit"}, + chainId: ChainIds, + clientId: ClientIds, + counterpartyClientId: ClientIds +] <: {ActionType} Actions == NullActions \union CreateClientActions \union - UpdateClientActions + UpdateClientActions \union + ConnectionOpenInitActions \* set of possible action outcomes ActionOutcomes == { "Null", - "CreateOK", - "UpdateOK", - "UpdateClientNotFound", - "UpdateHeightVerificationFailure", + "ICS02_OK", + "ICS02_ClientNotFound", + "ICS02_HeaderVerificationFailure", "ModelError" } @@ -93,8 +108,9 @@ SetClientHeight(clients, clientId, clientHeight) == [clients EXCEPT ![clientId] = clientHeight] CreateClient(chainId, clientHeight) == - LET clients == chains[chainId].clients - clientIdCounter == chains[chainId].clientIdCounter IN + LET chain == chains[chainId] IN + LET clients == chain.clients IN + LET clientIdCounter == chain.clientIdCounter IN \* check if the client exists (it shouldn't) IF ClientExists(clients, clientIdCounter) THEN \* if the client to be created already exists, @@ -102,74 +118,91 @@ CreateClient(chainId, clientHeight) == /\ actionOutcome' = "ModelError" /\ UNCHANGED <> ELSE - LET chain == [ + LET updatedChain == [chain EXCEPT \* set the new client's height to `clientHeight` - clients |-> SetClientHeight(clients, clientIdCounter, clientHeight), + !.clients = SetClientHeight(clients, clientIdCounter, clientHeight), \* update `clientIdCounter` - clientIdCounter |-> clientIdCounter + 1 + !.clientIdCounter = clientIdCounter + 1 ] IN - \* update `chains` - /\ chains' = [chains EXCEPT ![chainId] = chain] - \* set `outcome` - /\ actionOutcome' = "CreateOK" + \* update `chains` and set the outcome + /\ chains' = [chains EXCEPT ![chainId] = updatedChain] + /\ actionOutcome' = "ICS02_OK" UpdateClient(chainId, clientId, clientHeight) == - LET clients == chains[chainId].clients - clientIdCounter == chains[chainId].clientIdCounter IN + LET chain == chains[chainId] IN + LET clients == chain.clients IN \* check if the client exists IF ClientExists(clients, clientId) THEN \* if the client exists, check its height IF GetClientHeight(clients, clientId) < clientHeight THEN - \* if its height is lower than the one being updated to + \* if the client's height is lower than the one being updated to \* then, update the client - LET chain == [ + LET updatedChain == [chain EXCEPT \* set the client's height to `clientHeight` - clients |-> SetClientHeight(clients, clientId, clientHeight), - \* keep `clientIdCounter` - clientIdCounter |-> clientIdCounter + !.clients = SetClientHeight(clients, clientId, clientHeight) ] IN - \* update `chains` - /\ chains' = [chains EXCEPT ![chainId] = chain] - \* set outcome - /\ actionOutcome' = "UpdateOK" + \* update `chains` and set the outcome + /\ chains' = [chains EXCEPT ![chainId] = updatedChain] + /\ actionOutcome' = "ICS02_OK" ELSE - /\ actionOutcome' = "UpdateHeightVerificationFailure" + \* if the client's height is at least as high as the one being + \* updated to, then set an error outcome + /\ actionOutcome' = "ICS02_HeaderVerificationFailure" /\ UNCHANGED <> ELSE - \* if the client does not exist, then return an error - /\ actionOutcome' = "UpdateClientNotFound" + \* if the client does not exist, then set an error outcome + /\ actionOutcome' = "ICS02_ClientNotFound" /\ UNCHANGED <> +ConnectionOpenInit(chainId, clientId, counterpartyClientId) == + \* Kind::MissingClient + /\ UNCHANGED <> + /\ UNCHANGED <> + CreateClientAction == - \* select a chain + \* select a chain id \E chainId \in ChainIds: + \* select a height for the client to be created at + \E clientHeight \in ClientHeights: \* only create client if the model constant `MaxClientsPerChain` allows it /\ chains[chainId].clientIdCounter \in ClientIds - \* select a height for the client to be created at - /\ \E clientHeight \in ClientHeights: - /\ CreateClient(chainId, clientHeight) - /\ action' = AsAction([type |-> "CreateClient", - chainId |-> chainId, - height |-> clientHeight]) + /\ CreateClient(chainId, clientHeight) + /\ action' = AsAction([type |-> "CreateClient", + chainId |-> chainId, + height |-> clientHeight]) UpdateClientAction == - \* select a chain + \* select a chain id + \E chainId \in ChainIds: + \* select a client to be updated (which may not exist) + \E clientId \in ClientIds: + \* select a height for the client to be updated + \E clientHeight \in ClientHeights: + /\ UpdateClient(chainId, clientId, clientHeight) + /\ action' = AsAction([type |-> "UpdateClient", + chainId |-> chainId, + clientId |-> clientId, + height |-> clientHeight]) + +ConnectionOpenInitAction == + \* select a chain id \E chainId \in ChainIds: - \* select a client to be updated (which may not exist) - \E clientId \in ClientIds: - \* select a height for the client to be updated - \E clientHeight \in ClientHeights: - /\ UpdateClient(chainId, clientId, clientHeight) - /\ action' = AsAction([type |-> "UpdateClient", - chainId |-> chainId, - clientId |-> clientId, - height |-> clientHeight]) + \* select a client id + \E clientId \in ClientIds: + \* select a couterparty client id + \E counterpartyClientId \in ClientIds: + /\ ConnectionOpenInit(chainId, clientId, counterpartyClientId) + /\ action' = AsAction([type |-> "ConnectionOpenInit", + chainId |-> chainId, + clientId |-> clientId, + counterpartyClientId |-> counterpartyClientId]) Init == \* create an empty chain LET emptyChain == [ clients |-> [clientId \in ClientIds |-> NullHeight], - clientIdCounter |-> 0 + clientIdCounter |-> 0, + connectionIdCounter |-> 0 ] IN /\ chains = [chainId \in ChainIds |-> emptyChain] /\ action = AsAction([type |-> "Null"]) @@ -178,6 +211,7 @@ Init == Next == \/ CreateClientAction \/ UpdateClientAction + \/ ConnectionOpenInitAction \/ UNCHANGED <> (*************************************************************************** diff --git a/modules/tests/support/model_based/UpdateHeightVerificationFailureTest.json b/modules/tests/support/model_based/UpdateHeightVerificationFailureTest.json index 0ae9ca9289..2df6fbdcd2 100644 --- a/modules/tests/support/model_based/UpdateHeightVerificationFailureTest.json +++ b/modules/tests/support/model_based/UpdateHeightVerificationFailureTest.json @@ -11,7 +11,7 @@ "height": 1, "type": "CreateClient" }, - "actionOutcome": "CreateOK" + "actionOutcome": "ICS02_OK" }, { "action": { @@ -20,6 +20,6 @@ "height": 1, "type": "UpdateClient" }, - "actionOutcome": "UpdateHeightVerificationFailure" + "actionOutcome": "ICS02_HeaderVerificationFailure" } ] \ No newline at end of file diff --git a/modules/tests/support/model_based/UpdateOKTest.json b/modules/tests/support/model_based/UpdateOKTest.json index a3d4867b14..1ebc6caf2f 100644 --- a/modules/tests/support/model_based/UpdateOKTest.json +++ b/modules/tests/support/model_based/UpdateOKTest.json @@ -11,7 +11,7 @@ "height": 1, "type": "CreateClient" }, - "actionOutcome": "CreateOK" + "actionOutcome": "ICS02_OK" }, { "action": { @@ -20,7 +20,7 @@ "height": 2, "type": "UpdateClient" }, - "actionOutcome": "UpdateOK" + "actionOutcome": "ICS02_OK" }, { "action": { @@ -28,7 +28,7 @@ "height": 1, "type": "CreateClient" }, - "actionOutcome": "CreateOK" + "actionOutcome": "ICS02_OK" }, { "action": { @@ -37,7 +37,7 @@ "height": 2, "type": "UpdateClient" }, - "actionOutcome": "UpdateOK" + "actionOutcome": "ICS02_OK" }, { "action": { @@ -45,7 +45,7 @@ "height": 1, "type": "CreateClient" }, - "actionOutcome": "CreateOK" + "actionOutcome": "ICS02_OK" }, { "action": { @@ -54,6 +54,6 @@ "height": 2, "type": "UpdateClient" }, - "actionOutcome": "UpdateOK" + "actionOutcome": "ICS02_OK" } ] \ No newline at end of file From e5e2f7be32f5ede174c7412e62f5a26ee0df37f5 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Wed, 3 Feb 2021 18:58:16 +0100 Subject: [PATCH 16/52] Start connection and channel identifiers at 0 --- modules/src/ics02_client/handler/update_client.rs | 2 -- modules/src/mock/context.rs | 9 +++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/modules/src/ics02_client/handler/update_client.rs b/modules/src/ics02_client/handler/update_client.rs index d09d6c0d13..56f3062456 100644 --- a/modules/src/ics02_client/handler/update_client.rs +++ b/modules/src/ics02_client/handler/update_client.rs @@ -37,8 +37,6 @@ pub fn process( .ok_or_else(|| Kind::ClientNotFound(client_id.clone()))?; let client_def = AnyClient::from_client_type(client_type); - // ClientType ClientState ConsensusState - // X X X // Read client state from the host chain store. let client_state = ctx diff --git a/modules/src/mock/context.rs b/modules/src/mock/context.rs index c656363513..47319c40c2 100644 --- a/modules/src/mock/context.rs +++ b/modules/src/mock/context.rs @@ -394,9 +394,9 @@ impl ChannelKeeper for MockContext { fn next_channel_id(&mut self) -> ChannelId { let prefix = ChannelId::default().to_string(); let suffix = self.channel_ids_counter; + let channel_id = ChannelId::from_str(format!("{}-{}", prefix, suffix).as_str()).unwrap(); self.channel_ids_counter += 1; - - ChannelId::from_str(format!("{}-{}", prefix, suffix).as_str()).unwrap() + channel_id } fn store_channel( @@ -499,9 +499,10 @@ impl ConnectionKeeper for MockContext { fn next_connection_id(&mut self) -> ConnectionId { let prefix = ConnectionId::default().to_string(); let suffix = self.connection_ids_counter; + let connection_id = + ConnectionId::from_str(format!("{}-{}", prefix, suffix).as_str()).unwrap(); self.connection_ids_counter += 1; - - ConnectionId::from_str(format!("{}-{}", prefix, suffix).as_str()).unwrap() + connection_id } fn store_connection( From b9ca90aa67b12428c1a0f0c3b6769bec2aab0ed3 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Wed, 3 Feb 2021 20:24:14 +0100 Subject: [PATCH 17/52] Add conn open init to TLA spec --- modules/tests/support/model_based/IBC.tla | 93 +++++++++++++++++++++-- 1 file changed, 87 insertions(+), 6 deletions(-) diff --git a/modules/tests/support/model_based/IBC.tla b/modules/tests/support/model_based/IBC.tla index a854fc5415..4a3ef02a97 100644 --- a/modules/tests/support/model_based/IBC.tla +++ b/modules/tests/support/model_based/IBC.tla @@ -37,21 +37,45 @@ AsAction(a) == a <: ActionType \* set of possible client identifiers ClientIds == 0..(MaxClientsPerChain - 1) +\* if a client identifier is undefined then it is -1 +NullClientId == -1 \* set of possible client heights ClientHeights == 1..MaxClientHeight -\* if a client has a null height then the client does not exist -NullHeight == 0 +\* if a client identifier is undefined then it is -1 +NullHeight == -1 \* set of possible connection identifiers ConnectionIds == 0..(MaxConnectionsPerChain- 1) +\* if a connection identifier is undefined then it is -1 +NullConnectionId == -1 +\* set of possible connection states +ConnectionStates == { + "Uninit", + "Init", + "TryOpen", + "Open" +} \* mapping from client identifier to its height Clients == [ ClientIds -> ClientHeights \union {NullHeight} ] +\* data kept per connection +Connection == [ + state: ConnectionStates, + clientId: ClientIds \union {NullClientId}, + counterpartyClientId: ClientIds \union {NullClientId}, + connectionId: ConnectionIds \union {NullConnectionId}, + counterpartyConnectionId: ConnectionIds \union {NullConnectionId} +] +\* mapping from connection identifier to its data +Connections == [ + ConnectionIds -> Connection +] \* data kept per chain Chain == [ clients: Clients, clientIdCounter: 0..MaxClientsPerChain, + connections: Connections, connectionIdCounter: 0..MaxConnectionsPerChain ] @@ -88,6 +112,8 @@ ActionOutcomes == { "ICS02_OK", "ICS02_ClientNotFound", "ICS02_HeaderVerificationFailure", + "ICS03_OK", + "ICS03_MissingClient", "ModelError" } @@ -107,6 +133,18 @@ ClientExists(clients, clientId) == SetClientHeight(clients, clientId, clientHeight) == [clients EXCEPT ![clientId] = clientHeight] +\* retrieves `connectionId`'s data +GetConnection(connections, connectionId) == + connections[connectionId] + +\* check if a `connectionId` exists +ConnectionExists(connections, connectionId) == + GetConnection(connections, connectionId).state /= "Uninit" + +\* update the `connectionId`'s data +SetConnection(connections, connectionId, connectionData) == + [connections EXCEPT ![connectionId] = connectionData] + CreateClient(chainId, clientHeight) == LET chain == chains[chainId] IN LET clients == chain.clients IN @@ -118,8 +156,9 @@ CreateClient(chainId, clientHeight) == /\ actionOutcome' = "ModelError" /\ UNCHANGED <> ELSE + \* if it doesn't, create it LET updatedChain == [chain EXCEPT - \* set the new client's height to `clientHeight` + \* initialize the client's height to `clientHeight` !.clients = SetClientHeight(clients, clientIdCounter, clientHeight), \* update `clientIdCounter` !.clientIdCounter = clientIdCounter + 1 @@ -155,9 +194,41 @@ UpdateClient(chainId, clientId, clientHeight) == /\ UNCHANGED <> ConnectionOpenInit(chainId, clientId, counterpartyClientId) == - \* Kind::MissingClient - /\ UNCHANGED <> - /\ UNCHANGED <> + LET chain == chains[chainId] IN + LET clients == chain.clients IN + LET connections == chain.connections IN + LET connectionIdCounter == chain.connectionIdCounter IN + \* check if the client exists + IF ClientExists(clients, clientId) THEN + \* if the client exists, + \* then check if the connection exists (it shouldn't) + IF ConnectionExists(connections, connectionIdCounter) THEN + \* if the connection to be created already exists, + \* then there's an error in the model + /\ actionOutcome' = "ModelError" + /\ UNCHANGED <> + ELSE + \* if it doesn't, create it + LET connectionData == [ + state |-> "Init", + clientId |-> clientId, + counterpartyClientId |-> counterpartyClientId, + connectionId |-> connectionIdCounter, + counterpartyConnectionId |-> NullConnectionId + ] IN + LET updatedChain == [chain EXCEPT + \* initialize the connection's data + !.connections = SetConnection(connections, connectionIdCounter, connectionData), + \* update `clientIdCounter` + !.connectionIdCounter = connectionIdCounter + 1 + ] IN + \* update `chains` and set the outcome + /\ chains' = [chains EXCEPT ![chainId] = updatedChain] + /\ actionOutcome' = "ICS03_OK" + ELSE + \* if the client does not exist, then set an error outcome + /\ actionOutcome' = "ICS03_MissingClient" + /\ UNCHANGED <> CreateClientAction == \* select a chain id @@ -191,6 +262,8 @@ ConnectionOpenInitAction == \E clientId \in ClientIds: \* select a couterparty client id \E counterpartyClientId \in ClientIds: + \* only create connection if the model constant `MaxConnectionsPerChain` allows it + /\ chains[chainId].connectionIdCounter \in ConnectionIds /\ ConnectionOpenInit(chainId, clientId, counterpartyClientId) /\ action' = AsAction([type |-> "ConnectionOpenInit", chainId |-> chainId, @@ -199,9 +272,17 @@ ConnectionOpenInitAction == Init == \* create an empty chain + LET emptyConnection == [ + state |-> "Uninit", + clientId |-> NullClientId, + counterpartyClientId |-> NullClientId, + connectionId |-> NullConnectionId, + counterpartyConnectionId |-> NullConnectionId + ] IN LET emptyChain == [ clients |-> [clientId \in ClientIds |-> NullHeight], clientIdCounter |-> 0, + connections |-> [connectionId \in ConnectionIds |-> emptyConnection], connectionIdCounter |-> 0 ] IN /\ chains = [chainId \in ChainIds |-> emptyChain] From f3cd4341922d3d554952ddcc600581229ff36146 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Wed, 3 Feb 2021 21:09:12 +0100 Subject: [PATCH 18/52] Represent clients with a record in TLA spec --- modules/tests/support/model_based/IBC.tla | 71 ++++++++++++++--------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/modules/tests/support/model_based/IBC.tla b/modules/tests/support/model_based/IBC.tla index 4a3ef02a97..e70b3484ee 100644 --- a/modules/tests/support/model_based/IBC.tla +++ b/modules/tests/support/model_based/IBC.tla @@ -55,9 +55,13 @@ ConnectionStates == { "Open" } +\* data kept per cliennt +Client == [ + height: ClientHeights \union {NullHeight} +] \* mapping from client identifier to its height Clients == [ - ClientIds -> ClientHeights \union {NullHeight} + ClientIds -> Client ] \* data kept per connection Connection == [ @@ -78,6 +82,10 @@ Chain == [ connections: Connections, connectionIdCounter: 0..MaxConnectionsPerChain ] +\* mapping from chain identifier to its data +Chains == [ + ChainIds -> Chain +] \* set of possible actions NullActions == [ @@ -121,29 +129,29 @@ ActionOutcomes == { Specification ***************************************************************************) -\* retrieves `clientId`'s height -GetClientHeight(clients, clientId) == +\* retrieves `clientId`'s data +GetClient(clients, clientId) == clients[clientId] -\* check if a `clientId` exists +\* check if `clientId` exists ClientExists(clients, clientId) == - GetClientHeight(clients, clientId) /= NullHeight + GetClient(clients, clientId).height /= NullHeight -\* update the heigth of `clientId` to `clientHeight` -SetClientHeight(clients, clientId, clientHeight) == - [clients EXCEPT ![clientId] = clientHeight] +\* update `clientId`'s data +SetClient(clients, clientId, client) == + [clients EXCEPT ![clientId] = client] \* retrieves `connectionId`'s data GetConnection(connections, connectionId) == connections[connectionId] -\* check if a `connectionId` exists +\* check if `connectionId` exists ConnectionExists(connections, connectionId) == GetConnection(connections, connectionId).state /= "Uninit" -\* update the `connectionId`'s data -SetConnection(connections, connectionId, connectionData) == - [connections EXCEPT ![connectionId] = connectionData] +\* update `connectionId`'s data +SetConnection(connections, connectionId, connection) == + [connections EXCEPT ![connectionId] = connection] CreateClient(chainId, clientHeight) == LET chain == chains[chainId] IN @@ -157,10 +165,12 @@ CreateClient(chainId, clientHeight) == /\ UNCHANGED <> ELSE \* if it doesn't, create it + LET client == [ + height |-> clientHeight + ] IN + \* update the chain LET updatedChain == [chain EXCEPT - \* initialize the client's height to `clientHeight` - !.clients = SetClientHeight(clients, clientIdCounter, clientHeight), - \* update `clientIdCounter` + !.clients = SetClient(clients, clientIdCounter, client), !.clientIdCounter = clientIdCounter + 1 ] IN \* update `chains` and set the outcome @@ -173,12 +183,16 @@ UpdateClient(chainId, clientId, clientHeight) == \* check if the client exists IF ClientExists(clients, clientId) THEN \* if the client exists, check its height - IF GetClientHeight(clients, clientId) < clientHeight THEN + LET client == GetClient(clients, clientId) IN + IF client.height < clientHeight THEN \* if the client's height is lower than the one being updated to \* then, update the client + LET updatedClient == [client EXCEPT + !.height = clientHeight + ] IN + \* update the chain LET updatedChain == [chain EXCEPT - \* set the client's height to `clientHeight` - !.clients = SetClientHeight(clients, clientId, clientHeight) + !.clients = SetClient(clients, clientId, updatedClient) ] IN \* update `chains` and set the outcome /\ chains' = [chains EXCEPT ![chainId] = updatedChain] @@ -209,17 +223,16 @@ ConnectionOpenInit(chainId, clientId, counterpartyClientId) == /\ UNCHANGED <> ELSE \* if it doesn't, create it - LET connectionData == [ + LET connection == [ state |-> "Init", clientId |-> clientId, counterpartyClientId |-> counterpartyClientId, connectionId |-> connectionIdCounter, counterpartyConnectionId |-> NullConnectionId ] IN + \* update the chain LET updatedChain == [chain EXCEPT - \* initialize the connection's data - !.connections = SetConnection(connections, connectionIdCounter, connectionData), - \* update `clientIdCounter` + !.connections = SetConnection(connections, connectionIdCounter, connection), !.connectionIdCounter = connectionIdCounter + 1 ] IN \* update `chains` and set the outcome @@ -271,18 +284,22 @@ ConnectionOpenInitAction == counterpartyClientId |-> counterpartyClientId]) Init == - \* create an empty chain - LET emptyConnection == [ + \* create an null client and a null connection + LET nullClient == [ + height |-> NullHeight + ] IN + LET nullConnection == [ state |-> "Uninit", clientId |-> NullClientId, counterpartyClientId |-> NullClientId, connectionId |-> NullConnectionId, counterpartyConnectionId |-> NullConnectionId ] IN + \* create an empty chain LET emptyChain == [ - clients |-> [clientId \in ClientIds |-> NullHeight], + clients |-> [clientId \in ClientIds |-> nullClient], clientIdCounter |-> 0, - connections |-> [connectionId \in ConnectionIds |-> emptyConnection], + connections |-> [connectionId \in ConnectionIds |-> nullConnection], connectionIdCounter |-> 0 ] IN /\ chains = [chainId \in ChainIds |-> emptyChain] @@ -300,7 +317,7 @@ Next == ***************************************************************************) TypeOK == - /\ chains \in [ChainIds -> Chain] + /\ chains \in Chains /\ action \in Actions /\ actionOutcome \in ActionOutcomes From 4167d29fef7a2b299d79df21e34c27d722c83065 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Wed, 3 Feb 2021 23:00:30 +0100 Subject: [PATCH 19/52] Finish connection open init --- modules/tests/model_based.rs | 162 +++++++++++++----- modules/tests/state.rs | 19 +- modules/tests/support/model_based/IBC.tla | 35 ++-- .../tests/support/model_based/IBCTests.cfg | 15 +- .../tests/support/model_based/IBCTests.tla | 32 ++-- ...> ICS02HeaderVerificationFailureTest.json} | 8 +- ...dateOKTest.json => ICS02UpdateOKTest.json} | 24 +-- .../ICS03ConnectionOpenInitOKTest.json | 25 +++ .../model_based/ICS03MissingClientTest.json | 25 +++ 9 files changed, 243 insertions(+), 102 deletions(-) rename modules/tests/support/model_based/{UpdateHeightVerificationFailureTest.json => ICS02HeaderVerificationFailureTest.json} (65%) rename modules/tests/support/model_based/{UpdateOKTest.json => ICS02UpdateOKTest.json} (62%) create mode 100644 modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json create mode 100644 modules/tests/support/model_based/ICS03MissingClientTest.json diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index 51b1f32494..157fcac211 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -8,7 +8,13 @@ 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::ics03_connection::connection::Counterparty; +use ibc::ics03_connection::error::Kind as ICS03ErrorKind; +use ibc::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; +use ibc::ics03_connection::msgs::ConnectionMsg; +use ibc::ics03_connection::version::Version; use ibc::ics18_relayer::error::{Error as ICS18Error, Kind as ICS18ErrorKind}; +use ibc::ics23_commitment::commitment::CommitmentPrefix; use ibc::ics24_host::identifier::ChainId; use ibc::ics24_host::identifier::ClientId; use ibc::ics26_routing::error::{Error as ICS26Error, Kind as ICS26ErrorKind}; @@ -24,9 +30,6 @@ use std::error::Error; use std::fmt::{Debug, Display}; use tendermint::account::Id as AccountId; -// all chains use the same version -const VERSION: u64 = 0; - #[derive(Debug)] struct ICS02TestExecutor { // mapping from chain identifier to its context @@ -47,22 +50,14 @@ impl ICS02TestExecutor { let max_history_size = 1; let initial_height = 0; MockContext::new( - ChainId::new(chain_id, VERSION), + ChainId::new(chain_id, Self::epoch()), HostType::Mock, max_history_size, - Height::new(VERSION, initial_height), + Height::new(Self::epoch(), initial_height), ) }) } - fn dummy_signer() -> AccountId { - AccountId::new([0; 20]) - } - - fn mock_header(height: u64) -> MockHeader { - MockHeader(Height::new(VERSION, height)) - } - fn extract_handler_error_kind(result: Result<(), ICS18Error>) -> K where K: Clone + Debug + Display + Into + 'static, @@ -89,6 +84,53 @@ impl ICS02TestExecutor { .kind() .clone() } + + // TODO: this is sometimes called version/revision number but seems + // unrelated with the `Version` type below; is that so? + fn epoch() -> u64 { + 0 + } + + fn version() -> Version { + Version::default() + } + + fn client_id(client_id: u64) -> ClientId { + ClientId::new(ClientType::Mock, client_id) + .expect("it should be possible to create the client identifier") + } + + fn mock_header(height: u64) -> MockHeader { + MockHeader(Height::new(Self::epoch(), 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]) + } + + fn counterparty(counterparty_client_id: u64) -> Counterparty { + Counterparty::new( + Self::client_id(counterparty_client_id), + None, + CommitmentPrefix(Vec::new()), + ) + } + + fn delay_period() -> u64 { + 0 + } } impl modelator::TestExecutor for ICS02TestExecutor { @@ -109,7 +151,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { fn check_next_state(&mut self, state: State) -> bool { match state.action.action_type { ActionType::Null => panic!("unexpected action type"), - ActionType::CreateClient => { + ActionType::ICS02CreateClient => { // get action parameters let chain_id = state .action @@ -123,32 +165,24 @@ impl modelator::TestExecutor for ICS02TestExecutor { // get chain's context let ctx = self.get_chain_context_or_init(chain_id); - // create client and consensus state from parameters - let client_state = AnyClientState::Mock(MockClientState(Self::mock_header(height))); - let consensus_state = - AnyConsensusState::Mock(MockConsensusState(Self::mock_header(height))); - - // create dummy signer - let signer = Self::dummy_signer(); - // create ICS26 message and deliver it let msg = ICS26Envelope::ICS2Msg(ClientMsg::CreateClient(MsgCreateAnyClient { - client_state, - consensus_state, - signer, + client_state: Self::client_state(height), + consensus_state: Self::consensus_state(height), + signer: Self::signer(), })); let result = ctx.deliver(msg); // check the expected outcome: client create always succeeds match state.action_outcome { - ActionOutcome::ICS02OK => { + ActionOutcome::ICS02CreateOK => { // the implementaion matches the model if no error occurs result.is_ok() } action => panic!("unexpected action outcome {:?}", action), } } - ActionType::UpdateClient => { + ActionType::ICS02UpdateClient => { // get action parameters let chain_id = state .action @@ -166,25 +200,17 @@ impl modelator::TestExecutor for ICS02TestExecutor { // get chain's context let ctx = self.get_chain_context_or_init(chain_id); - // create client id and header from action parameters - let client_id = ClientId::new(ClientType::Mock, client_id) - .expect("it should be possible to create the client identifier"); - let header = AnyHeader::Mock(Self::mock_header(height)); - - // create dummy signer - let signer = Self::dummy_signer(); - // create ICS26 message and deliver it let msg = ICS26Envelope::ICS2Msg(ClientMsg::UpdateClient(MsgUpdateAnyClient { - client_id: client_id.clone(), - header, - signer, + client_id: Self::client_id(client_id), + header: Self::header(height), + signer: Self::signer(), })); let result = ctx.deliver(msg); // check the expected outcome match state.action_outcome { - ActionOutcome::ICS02OK => { + ActionOutcome::ICS02UpdateOK => { // the implementaion matches the model if no error occurs result.is_ok() } @@ -195,7 +221,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { // error matching the expected outcome matches!( handler_error_kind, - ICS02ErrorKind::ClientNotFound(id) if id == client_id + ICS02ErrorKind::ClientNotFound(id) if id == Self::client_id(client_id) ) } ActionOutcome::ICS02HeaderVerificationFailure => { @@ -208,8 +234,53 @@ impl modelator::TestExecutor for ICS02TestExecutor { action => panic!("unexpected action outcome {:?}", action), } } - ActionType::ConnectionOpenInit => { - todo!() + ActionType::ICS03ConnectionOpenInit => { + // get action parameters + let chain_id = state + .action + .chain_id + .expect("connection open init action should have a chain identifier"); + let client_id = state + .action + .client_id + .expect("connection open init action should have a client identifier"); + let counterparty_client_id = state.action.counterparty_client_id.expect( + "connection open init action should have a counterparty client identifier", + ); + + // get chain's context + let ctx = self.get_chain_context_or_init(chain_id); + + // create ICS26 message and deliver it + let msg = ICS26Envelope::ICS3Msg(ConnectionMsg::ConnectionOpenInit( + MsgConnectionOpenInit { + client_id: Self::client_id(client_id), + counterparty: Self::counterparty(counterparty_client_id), + version: Self::version(), + delay_period: Self::delay_period(), + signer: Self::signer(), + }, + )); + let result = ctx.deliver(msg); + + // check the expected outcome + match state.action_outcome { + ActionOutcome::ICS03ConnectionOpenInitOK => { + // the implementaion matches the model if no error occurs + result.is_ok() + } + ActionOutcome::ICS03MissingClient => { + let handler_error_kind = + Self::extract_handler_error_kind::(result); + // the implementaion matches the model if there's an + // error matching the expected outcome + matches!( + handler_error_kind, + ICS03ErrorKind::MissingClient(id) if id == Self::client_id(client_id) + ) + } + action => panic!("unexpected action outcome {:?}", action), + } } } } @@ -219,7 +290,12 @@ const TESTS_DIR: &str = "tests/support/model_based"; #[test] fn main() { - let tests = vec!["UpdateOKTest", "UpdateHeightVerificationFailureTest"]; + let tests = vec![ + "ICS02UpdateOKTest", + "ICS02HeaderVerificationFailureTest", + "ICS03ConnectionOpenInitOKTest", + "ICS03MissingClientTest", + ]; for test in tests { let path = format!("{}/{}.json", TESTS_DIR, test); diff --git a/modules/tests/state.rs b/modules/tests/state.rs index 82e9255fd3..fdbd0820dc 100644 --- a/modules/tests/state.rs +++ b/modules/tests/state.rs @@ -17,27 +17,30 @@ pub struct Action { #[serde(alias = "chainId")] pub chain_id: Option, + pub height: Option, + #[serde(alias = "clientId")] pub client_id: Option, - pub height: Option, + #[serde(alias = "counterpartyClientId")] + pub counterparty_client_id: Option, } #[derive(Debug, Clone, PartialEq, Deserialize)] pub enum ActionType { Null, - CreateClient, - UpdateClient, - ConnectionOpenInit, + ICS02CreateClient, + ICS02UpdateClient, + ICS03ConnectionOpenInit, } #[derive(Debug, Clone, PartialEq, Deserialize)] pub enum ActionOutcome { Null, - #[serde(alias = "ICS02_OK")] - ICS02OK, - #[serde(alias = "ICS02_ClientNotFound")] + ICS02CreateOK, + ICS02UpdateOK, ICS02ClientNotFound, - #[serde(alias = "ICS02_HeaderVerificationFailure")] ICS02HeaderVerificationFailure, + ICS03ConnectionOpenInitOK, + ICS03MissingClient, } diff --git a/modules/tests/support/model_based/IBC.tla b/modules/tests/support/model_based/IBC.tla index e70b3484ee..feff8046c9 100644 --- a/modules/tests/support/model_based/IBC.tla +++ b/modules/tests/support/model_based/IBC.tla @@ -92,18 +92,18 @@ NullActions == [ type: {"Null"} ] <: {ActionType} CreateClientActions == [ - type: {"CreateClient"}, + type: {"ICS02CreateClient"}, chainId: ChainIds, height: ClientHeights ] <: {ActionType} UpdateClientActions == [ - type: {"UpdateClient"}, + type: {"ICS02UpdateClient"}, chainId: ChainIds, clientId: ClientIds, height: ClientHeights ] <: {ActionType} ConnectionOpenInitActions == [ - type: {"ConnectionOpenInit"}, + type: {"ICS03ConnectionOpenInit"}, chainId: ChainIds, clientId: ClientIds, counterpartyClientId: ClientIds @@ -117,11 +117,12 @@ Actions == \* set of possible action outcomes ActionOutcomes == { "Null", - "ICS02_OK", - "ICS02_ClientNotFound", - "ICS02_HeaderVerificationFailure", - "ICS03_OK", - "ICS03_MissingClient", + "ICS02CreateOK", + "ICS02UpdateOK", + "ICS02ClientNotFound", + "ICS02HeaderVerificationFailure", + "ICS03ConnectionOpenInitOK", + "ICS03MissingClient", "ModelError" } @@ -175,7 +176,7 @@ CreateClient(chainId, clientHeight) == ] IN \* update `chains` and set the outcome /\ chains' = [chains EXCEPT ![chainId] = updatedChain] - /\ actionOutcome' = "ICS02_OK" + /\ actionOutcome' = "ICS02CreateOK" UpdateClient(chainId, clientId, clientHeight) == LET chain == chains[chainId] IN @@ -196,15 +197,15 @@ UpdateClient(chainId, clientId, clientHeight) == ] IN \* update `chains` and set the outcome /\ chains' = [chains EXCEPT ![chainId] = updatedChain] - /\ actionOutcome' = "ICS02_OK" + /\ actionOutcome' = "ICS02UpdateOK" ELSE \* if the client's height is at least as high as the one being \* updated to, then set an error outcome - /\ actionOutcome' = "ICS02_HeaderVerificationFailure" + /\ actionOutcome' = "ICS02HeaderVerificationFailure" /\ UNCHANGED <> ELSE \* if the client does not exist, then set an error outcome - /\ actionOutcome' = "ICS02_ClientNotFound" + /\ actionOutcome' = "ICS02ClientNotFound" /\ UNCHANGED <> ConnectionOpenInit(chainId, clientId, counterpartyClientId) == @@ -237,10 +238,10 @@ ConnectionOpenInit(chainId, clientId, counterpartyClientId) == ] IN \* update `chains` and set the outcome /\ chains' = [chains EXCEPT ![chainId] = updatedChain] - /\ actionOutcome' = "ICS03_OK" + /\ actionOutcome' = "ICS03ConnectionOpenInitOK" ELSE \* if the client does not exist, then set an error outcome - /\ actionOutcome' = "ICS03_MissingClient" + /\ actionOutcome' = "ICS03MissingClient" /\ UNCHANGED <> CreateClientAction == @@ -251,7 +252,7 @@ CreateClientAction == \* only create client if the model constant `MaxClientsPerChain` allows it /\ chains[chainId].clientIdCounter \in ClientIds /\ CreateClient(chainId, clientHeight) - /\ action' = AsAction([type |-> "CreateClient", + /\ action' = AsAction([type |-> "ICS02CreateClient", chainId |-> chainId, height |-> clientHeight]) @@ -263,7 +264,7 @@ UpdateClientAction == \* select a height for the client to be updated \E clientHeight \in ClientHeights: /\ UpdateClient(chainId, clientId, clientHeight) - /\ action' = AsAction([type |-> "UpdateClient", + /\ action' = AsAction([type |-> "ICS02UpdateClient", chainId |-> chainId, clientId |-> clientId, height |-> clientHeight]) @@ -278,7 +279,7 @@ ConnectionOpenInitAction == \* only create connection if the model constant `MaxConnectionsPerChain` allows it /\ chains[chainId].connectionIdCounter \in ConnectionIds /\ ConnectionOpenInit(chainId, clientId, counterpartyClientId) - /\ action' = AsAction([type |-> "ConnectionOpenInit", + /\ action' = AsAction([type |-> "ICS03ConnectionOpenInit", chainId |-> chainId, clientId |-> clientId, counterpartyClientId |-> counterpartyClientId]) diff --git a/modules/tests/support/model_based/IBCTests.cfg b/modules/tests/support/model_based/IBCTests.cfg index 0950dd0e71..79005a26bc 100644 --- a/modules/tests/support/model_based/IBCTests.cfg +++ b/modules/tests/support/model_based/IBCTests.cfg @@ -1,13 +1,16 @@ CONSTANTS ChainIds = {"chain-A", "chain-B"} - MaxClientsPerChain = 4 - MaxClientHeight = 4 + MaxClientsPerChain = 2 + MaxClientHeight = 2 + MaxConnectionsPerChain = 2 INIT Init NEXT Next INVARIANTS - \* CreateOKTest - \* UpdateOKTest - \* UpdateClientNotFoundTest - UpdateHeightVerificationFailureTest \ No newline at end of file + \* ICS02CreateOKTest + \* ICS02UpdateOKTest + \* ICS02ClientNotFoundTest + \* ICS02HeaderVerificationFailureTest + ICS03ConnectionOpenInitOKTest + \* ICS03MissingClientTest \ No newline at end of file diff --git a/modules/tests/support/model_based/IBCTests.tla b/modules/tests/support/model_based/IBCTests.tla index f24eb10cc2..660d9cd60d 100644 --- a/modules/tests/support/model_based/IBCTests.tla +++ b/modules/tests/support/model_based/IBCTests.tla @@ -2,21 +2,29 @@ EXTENDS IBC -CreateOK == - /\ actionOutcome = "CreateOK" +ICS02CreateOK == + /\ actionOutcome = "ICS02CreateOK" -UpdateOK == - /\ actionOutcome = "UpdateOK" +ICS02UpdateOK == + /\ actionOutcome = "ICS02UpdateOK" -UpdateClientNotFound == - /\ actionOutcome = "UpdateClientNotFound" +ICS02ClientNotFound == + /\ actionOutcome = "ICS02ClientNotFound" -UpdateHeightVerificationFailure == - /\ actionOutcome = "UpdateHeightVerificationFailure" +ICS02HeaderVerificationFailure == + /\ actionOutcome = "ICS02HeaderVerificationFailure" -CreateOKTest == ~CreateOK -UpdateOKTest == ~UpdateOK -UpdateClientNotFoundTest == ~UpdateClientNotFound -UpdateHeightVerificationFailureTest == ~UpdateHeightVerificationFailure +ICS03ConnectionOpenInitOK == + /\ actionOutcome = "ICS03ConnectionOpenInitOK" + +ICS03MissingClient == + /\ actionOutcome = "ICS03MissingClient" + +ICS02CreateOKTest == ~ICS02CreateOK +ICS02UpdateOKTest == ~ICS02UpdateOK +ICS02ClientNotFoundTest == ~ICS02ClientNotFound +ICS02HeaderVerificationFailureTest == ~ICS02HeaderVerificationFailure +ICS03ConnectionOpenInitOKTest == ~ICS03ConnectionOpenInitOK +ICS03MissingClientTest == ~ICS03MissingClient ============================================================================= \ No newline at end of file diff --git a/modules/tests/support/model_based/UpdateHeightVerificationFailureTest.json b/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json similarity index 65% rename from modules/tests/support/model_based/UpdateHeightVerificationFailureTest.json rename to modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json index 2df6fbdcd2..cde46f8d20 100644 --- a/modules/tests/support/model_based/UpdateHeightVerificationFailureTest.json +++ b/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json @@ -9,17 +9,17 @@ "action": { "chainId": "chain-B", "height": 1, - "type": "CreateClient" + "type": "ICS02CreateClient" }, - "actionOutcome": "ICS02_OK" + "actionOutcome": "ICS02CreateOK" }, { "action": { "chainId": "chain-B", "clientId": 0, "height": 1, - "type": "UpdateClient" + "type": "ICS02UpdateClient" }, - "actionOutcome": "ICS02_HeaderVerificationFailure" + "actionOutcome": "ICS02HeaderVerificationFailure" } ] \ No newline at end of file diff --git a/modules/tests/support/model_based/UpdateOKTest.json b/modules/tests/support/model_based/ICS02UpdateOKTest.json similarity index 62% rename from modules/tests/support/model_based/UpdateOKTest.json rename to modules/tests/support/model_based/ICS02UpdateOKTest.json index 1ebc6caf2f..8a78fa3bfa 100644 --- a/modules/tests/support/model_based/UpdateOKTest.json +++ b/modules/tests/support/model_based/ICS02UpdateOKTest.json @@ -9,51 +9,51 @@ "action": { "chainId": "chain-B", "height": 1, - "type": "CreateClient" + "type": "ICS02CreateClient" }, - "actionOutcome": "ICS02_OK" + "actionOutcome": "ICS02CreateOK" }, { "action": { "chainId": "chain-B", "clientId": 0, "height": 2, - "type": "UpdateClient" + "type": "ICS02UpdateClient" }, - "actionOutcome": "ICS02_OK" + "actionOutcome": "ICS02UpdateOK" }, { "action": { "chainId": "chain-A", "height": 1, - "type": "CreateClient" + "type": "ICS02CreateClient" }, - "actionOutcome": "ICS02_OK" + "actionOutcome": "ICS02CreateOK" }, { "action": { "chainId": "chain-A", "clientId": 0, "height": 2, - "type": "UpdateClient" + "type": "ICS02UpdateClient" }, - "actionOutcome": "ICS02_OK" + "actionOutcome": "ICS02UpdateOK" }, { "action": { "chainId": "chain-A", "height": 1, - "type": "CreateClient" + "type": "ICS02CreateClient" }, - "actionOutcome": "ICS02_OK" + "actionOutcome": "ICS02CreateOK" }, { "action": { "chainId": "chain-A", "clientId": 1, "height": 2, - "type": "UpdateClient" + "type": "ICS02UpdateClient" }, - "actionOutcome": "ICS02_OK" + "actionOutcome": "ICS02UpdateOK" } ] \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json b/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json new file mode 100644 index 0000000000..5ef27e6744 --- /dev/null +++ b/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json @@ -0,0 +1,25 @@ +[ + { + "action": { + "type": "Null" + }, + "actionOutcome": "Null" + }, + { + "action": { + "chainId": "chain-A", + "height": 1, + "type": "ICS02CreateClient" + }, + "actionOutcome": "ICS02CreateOK" + }, + { + "action": { + "chainId": "chain-A", + "clientId": 0, + "counterpartyClientId": 0, + "type": "ICS03ConnectionOpenInit" + }, + "actionOutcome": "ICS03ConnectionOpenInitOK" + } +] \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS03MissingClientTest.json b/modules/tests/support/model_based/ICS03MissingClientTest.json new file mode 100644 index 0000000000..4f180b696b --- /dev/null +++ b/modules/tests/support/model_based/ICS03MissingClientTest.json @@ -0,0 +1,25 @@ +[ + { + "action": { + "type": "Null" + }, + "actionOutcome": "Null" + }, + { + "action": { + "chainId": "chain-A", + "height": 1, + "type": "ICS02CreateClient" + }, + "actionOutcome": "ICS02CreateOK" + }, + { + "action": { + "chainId": "chain-B", + "clientId": 0, + "counterpartyClientId": 0, + "type": "ICS03ConnectionOpenInit" + }, + "actionOutcome": "ICS03MissingClient" + } +] \ No newline at end of file From 7755fec99b2335ad46bceda6f1feb94289beebd8 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Wed, 3 Feb 2021 23:12:40 +0100 Subject: [PATCH 20/52] s/state/step --- modules/tests/model_based.rs | 38 ++++++++++++++--------------- modules/tests/modelator.rs | 35 +++++++++++++------------- modules/tests/{state.rs => step.rs} | 2 +- 3 files changed, 37 insertions(+), 38 deletions(-) rename modules/tests/{state.rs => step.rs} (98%) diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index 157fcac211..3d2d7852ed 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -1,5 +1,5 @@ mod modelator; -mod state; +mod step; use ibc::ics02_client::client_def::AnyHeader; use ibc::ics02_client::client_def::{AnyClientState, AnyConsensusState}; @@ -24,10 +24,10 @@ use ibc::mock::context::MockContext; use ibc::mock::header::MockHeader; use ibc::mock::host::HostType; use ibc::Height; -use state::{ActionOutcome, ActionType, State}; use std::collections::HashMap; use std::error::Error; use std::fmt::{Debug, Display}; +use step::{ActionOutcome, ActionType, Step}; use tendermint::account::Id as AccountId; #[derive(Debug)] @@ -133,31 +133,31 @@ impl ICS02TestExecutor { } } -impl modelator::TestExecutor for ICS02TestExecutor { - fn check_initial_state(&mut self, state: State) -> bool { +impl modelator::TestExecutor for ICS02TestExecutor { + fn initial_step(&mut self, step: Step) -> bool { assert_eq!( - state.action.action_type, + step.action.action_type, ActionType::Null, "unexpected action type" ); assert_eq!( - state.action_outcome, + step.action_outcome, ActionOutcome::Null, "unexpected action outcome" ); true } - fn check_next_state(&mut self, state: State) -> bool { - match state.action.action_type { + fn next_step(&mut self, step: Step) -> bool { + match step.action.action_type { ActionType::Null => panic!("unexpected action type"), ActionType::ICS02CreateClient => { // get action parameters - let chain_id = state + let chain_id = step .action .chain_id .expect("create client action should have a chain identifier"); - let height = state + let height = step .action .height .expect("create client action should have a height"); @@ -174,7 +174,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { let result = ctx.deliver(msg); // check the expected outcome: client create always succeeds - match state.action_outcome { + match step.action_outcome { ActionOutcome::ICS02CreateOK => { // the implementaion matches the model if no error occurs result.is_ok() @@ -184,15 +184,15 @@ impl modelator::TestExecutor for ICS02TestExecutor { } ActionType::ICS02UpdateClient => { // get action parameters - let chain_id = state + let chain_id = step .action .chain_id .expect("update client action should have a chain identifier"); - let client_id = state + let client_id = step .action .client_id .expect("update client action should have a client identifier"); - let height = state + let height = step .action .height .expect("update client action should have a height"); @@ -209,7 +209,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { let result = ctx.deliver(msg); // check the expected outcome - match state.action_outcome { + match step.action_outcome { ActionOutcome::ICS02UpdateOK => { // the implementaion matches the model if no error occurs result.is_ok() @@ -236,15 +236,15 @@ impl modelator::TestExecutor for ICS02TestExecutor { } ActionType::ICS03ConnectionOpenInit => { // get action parameters - let chain_id = state + let chain_id = step .action .chain_id .expect("connection open init action should have a chain identifier"); - let client_id = state + let client_id = step .action .client_id .expect("connection open init action should have a client identifier"); - let counterparty_client_id = state.action.counterparty_client_id.expect( + let counterparty_client_id = step.action.counterparty_client_id.expect( "connection open init action should have a counterparty client identifier", ); @@ -264,7 +264,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { let result = ctx.deliver(msg); // check the expected outcome - match state.action_outcome { + match step.action_outcome { ActionOutcome::ICS03ConnectionOpenInitOK => { // the implementaion matches the model if no error occurs result.is_ok() diff --git a/modules/tests/modelator.rs b/modules/tests/modelator.rs index d395119e20..91d60d6800 100644 --- a/modules/tests/modelator.rs +++ b/modules/tests/modelator.rs @@ -6,15 +6,14 @@ use std::io::BufReader; use std::path::Path; pub trait TestExecutor { - fn check_initial_state(&mut self, state: S) -> bool; - - fn check_next_state(&mut self, state: S) -> bool; + fn initial_step(&mut self, step: S) -> bool; + fn next_step(&mut self, step: S) -> bool; } -pub fn test_driver(mut executor: E, path: P) -> Result<()> +pub fn test_driver(mut executor: Executor, path: P) -> Result<()> where - E: TestExecutor + Debug, - S: DeserializeOwned + Debug + Clone, + Executor: TestExecutor + Debug, + Step: DeserializeOwned + Debug + Clone, P: AsRef, { // open test file @@ -23,27 +22,27 @@ where let reader = BufReader::new(file); // parse test file - let states: Vec = serde_json::de::from_reader(reader) + let steps: Vec = serde_json::de::from_reader(reader) .wrap_err_with(|| format!("test file {:?} could not be deserialized", path.as_ref()))?; - let mut states = states.into_iter(); + let mut steps = steps.into_iter(); - // check the initial state - if let Some(state) = states.next() { - if !executor.check_initial_state(state.clone()) { - return Err(eyre!("check failed on initial state:\n{:#?}", state)); + // check the initial step + if let Some(step) = steps.next() { + if !executor.initial_step(step.clone()) { + return Err(eyre!("check failed on initial step:\n{:#?}", step)); } } else { - println!("WARNING: test file {:?} had 0 states", path.as_ref()); + println!("WARNING: test file {:?} had 0 steps", path.as_ref()); return Ok(()); } - // check all the remaining states - for state in states { - if !executor.check_next_state(state.clone()) { + // check the remaining steps + for step in steps { + if !executor.next_step(step.clone()) { return Err(eyre!( - "check failed on state:\n{:#?}\n\nexecutor:\n{:#?}", - state, + "check failed on step:\n{:#?}\n\nexecutor:\n{:#?}", + step, executor )); } diff --git a/modules/tests/state.rs b/modules/tests/step.rs similarity index 98% rename from modules/tests/state.rs rename to modules/tests/step.rs index fdbd0820dc..a84e67bb33 100644 --- a/modules/tests/state.rs +++ b/modules/tests/step.rs @@ -2,7 +2,7 @@ use serde::Deserialize; use std::fmt::Debug; #[derive(Debug, Clone, Deserialize)] -pub struct State { +pub struct Step { pub action: Action, #[serde(alias = "actionOutcome")] From 70e70f63f1e60f2708822bec474601deb2ef7c89 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Thu, 4 Feb 2021 09:12:25 +0100 Subject: [PATCH 21/52] Minimize diff --- modules/src/mock/context.rs | 9 ++++----- modules/tests/model_based.rs | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/src/mock/context.rs b/modules/src/mock/context.rs index 47319c40c2..c656363513 100644 --- a/modules/src/mock/context.rs +++ b/modules/src/mock/context.rs @@ -394,9 +394,9 @@ impl ChannelKeeper for MockContext { fn next_channel_id(&mut self) -> ChannelId { let prefix = ChannelId::default().to_string(); let suffix = self.channel_ids_counter; - let channel_id = ChannelId::from_str(format!("{}-{}", prefix, suffix).as_str()).unwrap(); self.channel_ids_counter += 1; - channel_id + + ChannelId::from_str(format!("{}-{}", prefix, suffix).as_str()).unwrap() } fn store_channel( @@ -499,10 +499,9 @@ impl ConnectionKeeper for MockContext { fn next_connection_id(&mut self) -> ConnectionId { let prefix = ConnectionId::default().to_string(); let suffix = self.connection_ids_counter; - let connection_id = - ConnectionId::from_str(format!("{}-{}", prefix, suffix).as_str()).unwrap(); self.connection_ids_counter += 1; - connection_id + + ConnectionId::from_str(format!("{}-{}", prefix, suffix).as_str()).unwrap() } fn store_connection( diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index 3d2d7852ed..f4213fffaa 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -299,10 +299,10 @@ fn main() { for test in tests { let path = format!("{}/{}.json", TESTS_DIR, test); - let test_executor = ICS02TestExecutor::new(); + let executor = ICS02TestExecutor::new(); // we should be able to just return the `Result` once the following issue // is fixed: https://github.com/rust-lang/rust/issues/43301 - if let Err(e) = modelator::test_driver(test_executor, path) { + if let Err(e) = modelator::test_driver(executor, path) { panic!("{:?}", e); } } From 41d34d312adeaa60ff7600d6ac6ccead134e75d7 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Thu, 4 Feb 2021 11:44:27 +0100 Subject: [PATCH 22/52] Modularize TLA spec --- modules/tests/support/model_based/IBC.tla | 156 +++++------------- .../support/model_based/IBCDefinitions.tla | 12 ++ modules/tests/support/model_based/ICS02.tla | 67 ++++++++ modules/tests/support/model_based/ICS03.tla | 52 ++++++ 4 files changed, 169 insertions(+), 118 deletions(-) create mode 100644 modules/tests/support/model_based/IBCDefinitions.tla create mode 100644 modules/tests/support/model_based/ICS02.tla create mode 100644 modules/tests/support/model_based/ICS03.tla diff --git a/modules/tests/support/model_based/IBC.tla b/modules/tests/support/model_based/IBC.tla index feff8046c9..deb2b2011b 100644 --- a/modules/tests/support/model_based/IBC.tla +++ b/modules/tests/support/model_based/IBC.tla @@ -1,6 +1,6 @@ --------------------------- MODULE IBC ---------------------------- -EXTENDS Integers, FiniteSets +EXTENDS Integers, FiniteSets, ICS02, ICS03 \* ids of existing chains CONSTANT ChainIds @@ -37,16 +37,10 @@ AsAction(a) == a <: ActionType \* set of possible client identifiers ClientIds == 0..(MaxClientsPerChain - 1) -\* if a client identifier is undefined then it is -1 -NullClientId == -1 \* set of possible client heights ClientHeights == 1..MaxClientHeight -\* if a client identifier is undefined then it is -1 -NullHeight == -1 \* set of possible connection identifiers ConnectionIds == 0..(MaxConnectionsPerChain- 1) -\* if a connection identifier is undefined then it is -1 -NullConnectionId == -1 \* set of possible connection states ConnectionStates == { "Uninit", @@ -130,119 +124,56 @@ ActionOutcomes == { Specification ***************************************************************************) -\* retrieves `clientId`'s data -GetClient(clients, clientId) == - clients[clientId] - -\* check if `clientId` exists -ClientExists(clients, clientId) == - GetClient(clients, clientId).height /= NullHeight - -\* update `clientId`'s data -SetClient(clients, clientId, client) == - [clients EXCEPT ![clientId] = client] - -\* retrieves `connectionId`'s data -GetConnection(connections, connectionId) == - connections[connectionId] - -\* check if `connectionId` exists -ConnectionExists(connections, connectionId) == - GetConnection(connections, connectionId).state /= "Uninit" - -\* update `connectionId`'s data -SetConnection(connections, connectionId, connection) == - [connections EXCEPT ![connectionId] = connection] - CreateClient(chainId, clientHeight) == LET chain == chains[chainId] IN LET clients == chain.clients IN LET clientIdCounter == chain.clientIdCounter IN - \* check if the client exists (it shouldn't) - IF ClientExists(clients, clientIdCounter) THEN - \* if the client to be created already exists, - \* then there's an error in the model - /\ actionOutcome' = "ModelError" - /\ UNCHANGED <> - ELSE - \* if it doesn't, create it - LET client == [ - height |-> clientHeight - ] IN - \* update the chain - LET updatedChain == [chain EXCEPT - !.clients = SetClient(clients, clientIdCounter, client), - !.clientIdCounter = clientIdCounter + 1 - ] IN - \* update `chains` and set the outcome - /\ chains' = [chains EXCEPT ![chainId] = updatedChain] - /\ actionOutcome' = "ICS02CreateOK" + LET result == ICS02_CreateClient(clients, clientIdCounter, clientHeight) IN + \* update the chain + LET updatedChain == [chain EXCEPT + !.clients = result.clients, + !.clientIdCounter = result.clientIdCounter + ] IN + \* update `chains`, set the action and its outcome + /\ chains' = [chains EXCEPT ![chainId] = updatedChain] + /\ action' = AsAction([type |-> "ICS02CreateClient", + chainId |-> chainId, + height |-> clientHeight]) + /\ actionOutcome' = result.outcome UpdateClient(chainId, clientId, clientHeight) == LET chain == chains[chainId] IN LET clients == chain.clients IN - \* check if the client exists - IF ClientExists(clients, clientId) THEN - \* if the client exists, check its height - LET client == GetClient(clients, clientId) IN - IF client.height < clientHeight THEN - \* if the client's height is lower than the one being updated to - \* then, update the client - LET updatedClient == [client EXCEPT - !.height = clientHeight - ] IN - \* update the chain - LET updatedChain == [chain EXCEPT - !.clients = SetClient(clients, clientId, updatedClient) - ] IN - \* update `chains` and set the outcome - /\ chains' = [chains EXCEPT ![chainId] = updatedChain] - /\ actionOutcome' = "ICS02UpdateOK" - ELSE - \* if the client's height is at least as high as the one being - \* updated to, then set an error outcome - /\ actionOutcome' = "ICS02HeaderVerificationFailure" - /\ UNCHANGED <> - ELSE - \* if the client does not exist, then set an error outcome - /\ actionOutcome' = "ICS02ClientNotFound" - /\ UNCHANGED <> + LET result == ICS02_UpdateClient(clients, clientId, clientHeight) IN + \* update the chain + LET updatedChain == [chain EXCEPT + !.clients = result.clients + ] IN + \* update `chains`, set the action and its outcome + /\ chains' = [chains EXCEPT ![chainId] = updatedChain] + /\ action' = AsAction([type |-> "ICS02UpdateClient", + chainId |-> chainId, + clientId |-> clientId, + height |-> clientHeight]) + /\ actionOutcome' = result.outcome ConnectionOpenInit(chainId, clientId, counterpartyClientId) == LET chain == chains[chainId] IN LET clients == chain.clients IN LET connections == chain.connections IN LET connectionIdCounter == chain.connectionIdCounter IN - \* check if the client exists - IF ClientExists(clients, clientId) THEN - \* if the client exists, - \* then check if the connection exists (it shouldn't) - IF ConnectionExists(connections, connectionIdCounter) THEN - \* if the connection to be created already exists, - \* then there's an error in the model - /\ actionOutcome' = "ModelError" - /\ UNCHANGED <> - ELSE - \* if it doesn't, create it - LET connection == [ - state |-> "Init", - clientId |-> clientId, - counterpartyClientId |-> counterpartyClientId, - connectionId |-> connectionIdCounter, - counterpartyConnectionId |-> NullConnectionId - ] IN - \* update the chain - LET updatedChain == [chain EXCEPT - !.connections = SetConnection(connections, connectionIdCounter, connection), - !.connectionIdCounter = connectionIdCounter + 1 - ] IN - \* update `chains` and set the outcome - /\ chains' = [chains EXCEPT ![chainId] = updatedChain] - /\ actionOutcome' = "ICS03ConnectionOpenInitOK" - ELSE - \* if the client does not exist, then set an error outcome - /\ actionOutcome' = "ICS03MissingClient" - /\ UNCHANGED <> + LET result == ICS03_ConnectionOpenInit(clients, connections, connectionIdCounter, clientId, counterpartyClientId) IN + \* update the chain + LET updatedChain == [chain EXCEPT + !.connections = result.connections, + !.connectionIdCounter = result.connectionIdCounter + ] IN + /\ chains' = [chains EXCEPT ![chainId] = updatedChain] + /\ action' = AsAction([type |-> "ICS03ConnectionOpenInit", + chainId |-> chainId, + clientId |-> clientId, + counterpartyClientId |-> counterpartyClientId]) + /\ actionOutcome' = result.outcome CreateClientAction == \* select a chain id @@ -252,9 +183,6 @@ CreateClientAction == \* only create client if the model constant `MaxClientsPerChain` allows it /\ chains[chainId].clientIdCounter \in ClientIds /\ CreateClient(chainId, clientHeight) - /\ action' = AsAction([type |-> "ICS02CreateClient", - chainId |-> chainId, - height |-> clientHeight]) UpdateClientAction == \* select a chain id @@ -263,11 +191,7 @@ UpdateClientAction == \E clientId \in ClientIds: \* select a height for the client to be updated \E clientHeight \in ClientHeights: - /\ UpdateClient(chainId, clientId, clientHeight) - /\ action' = AsAction([type |-> "ICS02UpdateClient", - chainId |-> chainId, - clientId |-> clientId, - height |-> clientHeight]) + UpdateClient(chainId, clientId, clientHeight) ConnectionOpenInitAction == \* select a chain id @@ -279,10 +203,6 @@ ConnectionOpenInitAction == \* only create connection if the model constant `MaxConnectionsPerChain` allows it /\ chains[chainId].connectionIdCounter \in ConnectionIds /\ ConnectionOpenInit(chainId, clientId, counterpartyClientId) - /\ action' = AsAction([type |-> "ICS03ConnectionOpenInit", - chainId |-> chainId, - clientId |-> clientId, - counterpartyClientId |-> counterpartyClientId]) Init == \* create an null client and a null connection diff --git a/modules/tests/support/model_based/IBCDefinitions.tla b/modules/tests/support/model_based/IBCDefinitions.tla new file mode 100644 index 0000000000..e8ac537c08 --- /dev/null +++ b/modules/tests/support/model_based/IBCDefinitions.tla @@ -0,0 +1,12 @@ +--------------------------- MODULE IBCDefinitions ---------------------------- + +EXTENDS Integers, FiniteSets + +\* if a client identifier is undefined then it is -1 +NullClientId == -1 +\* if a client identifier is undefined then it is -1 +NullHeight == -1 +\* if a connection identifier is undefined then it is -1 +NullConnectionId == -1 + +============================================================================= diff --git a/modules/tests/support/model_based/ICS02.tla b/modules/tests/support/model_based/ICS02.tla new file mode 100644 index 0000000000..7fe4d7dc0c --- /dev/null +++ b/modules/tests/support/model_based/ICS02.tla @@ -0,0 +1,67 @@ +------------------------- MODULE ICS02 -------------------------- + +EXTENDS Integers, FiniteSets, IBCDefinitions + +\* retrieves `clientId`'s data +ICS02_GetClient(clients, clientId) == + clients[clientId] + +\* check if `clientId` exists +ICS02_ClientExists(clients, clientId) == + ICS02_GetClient(clients, clientId).height /= NullHeight + +\* update `clientId`'s data +ICS02_SetClient(clients, clientId, client) == + [clients EXCEPT ![clientId] = client] + +ICS02_CreateClient(clients, clientIdCounter, clientHeight) == + \* check if the client exists (it shouldn't) + IF ICS02_ClientExists(clients, clientIdCounter) THEN + \* if the client to be created already exists, + \* then there's an error in the model + [ + clients |-> clients, + clientIdCounter |-> clientIdCounter, + outcome |-> "ModelError" + ] + ELSE + \* if it doesn't, create it + LET client == [ + height |-> clientHeight + ] IN + [ + clients |-> ICS02_SetClient(clients, clientIdCounter, client), + clientIdCounter |-> clientIdCounter + 1, + outcome |-> "ICS02CreateOK" + ] + +ICS02_UpdateClient(clients, clientId, clientHeight) == + \* check if the client exists + IF ICS02_ClientExists(clients, clientId) THEN + \* if the client exists, check its height + LET client == ICS02_GetClient(clients, clientId) IN + IF client.height < clientHeight THEN + \* if the client's height is lower than the one being updated to + \* then, update the client + LET updatedClient == [client EXCEPT + !.height = clientHeight + ] IN + [ + clients |-> ICS02_SetClient(clients, clientId, updatedClient), + outcome |-> "ICS02UpdateOK" + ] + ELSE + \* if the client's height is at least as high as the one being + \* updated to, then set an error outcome + [ + clients |-> clients, + outcome |-> "ICS02HeaderVerificationFailure" + ] + ELSE + \* if the client does not exist, then set an error outcome + [ + clients |-> clients, + outcome |-> "ICS02ClientNotFound" + ] + +============================================================================= \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS03.tla b/modules/tests/support/model_based/ICS03.tla new file mode 100644 index 0000000000..73c370a042 --- /dev/null +++ b/modules/tests/support/model_based/ICS03.tla @@ -0,0 +1,52 @@ +------------------------- MODULE ICS03 -------------------------- + +EXTENDS Integers, FiniteSets, IBCDefinitions, ICS02 + +\* retrieves `connectionId`'s data +ICS03_GetConnection(connections, connectionId) == + connections[connectionId] + +\* check if `connectionId` exists +ICS03_ConnectionExists(connections, connectionId) == + ICS03_GetConnection(connections, connectionId).state /= "Uninit" + +\* update `connectionId`'s data +ICS03_SetConnection(connections, connectionId, connection) == + [connections EXCEPT ![connectionId] = connection] + +ICS03_ConnectionOpenInit(clients, connections, connectionIdCounter, clientId, counterpartyClientId) == + \* check if the client exists + IF ICS02_ClientExists(clients, clientId) THEN + \* if the client exists, + \* then check if the connection exists (it shouldn't) + IF ICS03_ConnectionExists(connections, connectionIdCounter) THEN + \* if the connection to be created already exists, + \* then there's an error in the model + [ + connections |-> connections, + connectionIdCounter |-> connectionIdCounter, + outcome |-> "ModelError" + ] + ELSE + \* if it doesn't, create it + LET connection == [ + state |-> "Init", + clientId |-> clientId, + counterpartyClientId |-> counterpartyClientId, + connectionId |-> connectionIdCounter, + counterpartyConnectionId |-> NullConnectionId + ] IN + [ + connections |-> ICS03_SetConnection(connections, connectionIdCounter, connection), + connectionIdCounter |-> connectionIdCounter + 1, + outcome |-> "ICS03ConnectionOpenInitOK" + ] + ELSE + \* if the client does not exist, then set an error outcome + [ + connections |-> connections, + connectionIdCounter |-> connectionIdCounter, + outcome |-> "ICS03MissingClient" + ] + +============================================================================= \ No newline at end of file From 1f9881cbf5359f43cbb91445559e8473157cb1f8 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Thu, 4 Feb 2021 12:02:39 +0100 Subject: [PATCH 23/52] TLA format convention --- modules/tests/support/model_based/IBC.tla | 65 +++++++++++-------- .../support/model_based/IBCDefinitions.tla | 4 +- .../tests/support/model_based/IBCTests.tla | 4 +- modules/tests/support/model_based/ICS02.tla | 6 +- modules/tests/support/model_based/ICS03.tla | 19 ++++-- 5 files changed, 61 insertions(+), 37 deletions(-) diff --git a/modules/tests/support/model_based/IBC.tla b/modules/tests/support/model_based/IBC.tla index deb2b2011b..6ae053d9bd 100644 --- a/modules/tests/support/model_based/IBC.tla +++ b/modules/tests/support/model_based/IBC.tla @@ -1,4 +1,4 @@ ---------------------------- MODULE IBC ---------------------------- +--------------------------------- MODULE IBC ---------------------------------- EXTENDS Integers, FiniteSets, ICS02, ICS03 @@ -21,7 +21,7 @@ VARIABLE action \* string with the outcome of the last operation VARIABLE actionOutcome -(********************* TYPE ANNOTATIONS FOR APALACHE ***********************) +(********************** TYPE ANNOTATIONS FOR APALACHE ************************) \* operator for type annotations a <: b == a @@ -33,7 +33,7 @@ ActionType == [ counterpartyClientId |-> Int ] AsAction(a) == a <: ActionType -(****************** END OF TYPE ANNOTATIONS FOR APALACHE ********************) +(******************* END OF TYPE ANNOTATIONS FOR APALACHE ********************) \* set of possible client identifiers ClientIds == 0..(MaxClientsPerChain - 1) @@ -120,9 +120,7 @@ ActionOutcomes == { "ModelError" } -(*************************************************************************** - Specification - ***************************************************************************) +(***************************** Specification *********************************) CreateClient(chainId, clientHeight) == LET chain == chains[chainId] IN @@ -134,11 +132,13 @@ CreateClient(chainId, clientHeight) == !.clients = result.clients, !.clientIdCounter = result.clientIdCounter ] IN - \* update `chains`, set the action and its outcome + \* update `chains`, set the `action` and its `actionOutcome` /\ chains' = [chains EXCEPT ![chainId] = updatedChain] - /\ action' = AsAction([type |-> "ICS02CreateClient", - chainId |-> chainId, - height |-> clientHeight]) + /\ action' = AsAction([ + type |-> "ICS02CreateClient", + chainId |-> chainId, + height |-> clientHeight + ]) /\ actionOutcome' = result.outcome UpdateClient(chainId, clientId, clientHeight) == @@ -149,12 +149,14 @@ UpdateClient(chainId, clientId, clientHeight) == LET updatedChain == [chain EXCEPT !.clients = result.clients ] IN - \* update `chains`, set the action and its outcome + \* update `chains`, set the `action` and its `actionOutcome` /\ chains' = [chains EXCEPT ![chainId] = updatedChain] - /\ action' = AsAction([type |-> "ICS02UpdateClient", - chainId |-> chainId, - clientId |-> clientId, - height |-> clientHeight]) + /\ action' = AsAction([ + type |-> "ICS02UpdateClient", + chainId |-> chainId, + clientId |-> clientId, + height |-> clientHeight + ]) /\ actionOutcome' = result.outcome ConnectionOpenInit(chainId, clientId, counterpartyClientId) == @@ -162,17 +164,26 @@ ConnectionOpenInit(chainId, clientId, counterpartyClientId) == LET clients == chain.clients IN LET connections == chain.connections IN LET connectionIdCounter == chain.connectionIdCounter IN - LET result == ICS03_ConnectionOpenInit(clients, connections, connectionIdCounter, clientId, counterpartyClientId) IN + LET result == ICS03_ConnectionOpenInit( + clients, + connections, + connectionIdCounter, + clientId, + counterpartyClientId + ) IN \* update the chain LET updatedChain == [chain EXCEPT !.connections = result.connections, !.connectionIdCounter = result.connectionIdCounter ] IN + \* update `chains`, set the `action` and its `actionOutcome` /\ chains' = [chains EXCEPT ![chainId] = updatedChain] - /\ action' = AsAction([type |-> "ICS03ConnectionOpenInit", - chainId |-> chainId, - clientId |-> clientId, - counterpartyClientId |-> counterpartyClientId]) + /\ action' = AsAction([ + type |-> "ICS03ConnectionOpenInit", + chainId |-> chainId, + clientId |-> clientId, + counterpartyClientId |-> counterpartyClientId + ]) /\ actionOutcome' = result.outcome CreateClientAction == @@ -180,7 +191,8 @@ CreateClientAction == \E chainId \in ChainIds: \* select a height for the client to be created at \E clientHeight \in ClientHeights: - \* only create client if the model constant `MaxClientsPerChain` allows it + \* only create client if the model constant `MaxClientsPerChain` allows + \* it /\ chains[chainId].clientIdCounter \in ClientIds /\ CreateClient(chainId, clientHeight) @@ -200,12 +212,13 @@ ConnectionOpenInitAction == \E clientId \in ClientIds: \* select a couterparty client id \E counterpartyClientId \in ClientIds: - \* only create connection if the model constant `MaxConnectionsPerChain` allows it + \* only create connection if the model constant `MaxConnectionsPerChain` + \* allows it /\ chains[chainId].connectionIdCounter \in ConnectionIds /\ ConnectionOpenInit(chainId, clientId, counterpartyClientId) Init == - \* create an null client and a null connection + \* create a null client and a null connection LET nullClient == [ height |-> NullHeight ] IN @@ -233,9 +246,7 @@ Next == \/ ConnectionOpenInitAction \/ UNCHANGED <> -(*************************************************************************** - Invariants - ***************************************************************************) +(******************************** Invariants *********************************) TypeOK == /\ chains \in Chains @@ -246,4 +257,4 @@ TypeOK == ModelNeverErrors == actionOutcome /= "ModelError" -============================================================================= +=============================================================================== diff --git a/modules/tests/support/model_based/IBCDefinitions.tla b/modules/tests/support/model_based/IBCDefinitions.tla index e8ac537c08..045395cfb8 100644 --- a/modules/tests/support/model_based/IBCDefinitions.tla +++ b/modules/tests/support/model_based/IBCDefinitions.tla @@ -1,4 +1,4 @@ ---------------------------- MODULE IBCDefinitions ---------------------------- +--------------------------- MODULE IBCDefinitions ----------------------------- EXTENDS Integers, FiniteSets @@ -9,4 +9,4 @@ NullHeight == -1 \* if a connection identifier is undefined then it is -1 NullConnectionId == -1 -============================================================================= +=============================================================================== diff --git a/modules/tests/support/model_based/IBCTests.tla b/modules/tests/support/model_based/IBCTests.tla index 660d9cd60d..5165bf50b9 100644 --- a/modules/tests/support/model_based/IBCTests.tla +++ b/modules/tests/support/model_based/IBCTests.tla @@ -1,4 +1,4 @@ -------------------------- MODULE IBCTests --------------------------- +------------------------------ MODULE IBCTests -------------------------------- EXTENDS IBC @@ -27,4 +27,4 @@ ICS02HeaderVerificationFailureTest == ~ICS02HeaderVerificationFailure ICS03ConnectionOpenInitOKTest == ~ICS03ConnectionOpenInitOK ICS03MissingClientTest == ~ICS03MissingClient -============================================================================= \ No newline at end of file +=============================================================================== \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS02.tla b/modules/tests/support/model_based/ICS02.tla index 7fe4d7dc0c..ab930a382b 100644 --- a/modules/tests/support/model_based/ICS02.tla +++ b/modules/tests/support/model_based/ICS02.tla @@ -1,4 +1,4 @@ -------------------------- MODULE ICS02 -------------------------- +------------------------------- MODULE ICS02 ---------------------------------- EXTENDS Integers, FiniteSets, IBCDefinitions @@ -29,6 +29,7 @@ ICS02_CreateClient(clients, clientIdCounter, clientHeight) == LET client == [ height |-> clientHeight ] IN + \* return result with updated state [ clients |-> ICS02_SetClient(clients, clientIdCounter, client), clientIdCounter |-> clientIdCounter + 1, @@ -46,6 +47,7 @@ ICS02_UpdateClient(clients, clientId, clientHeight) == LET updatedClient == [client EXCEPT !.height = clientHeight ] IN + \* return result with updated state [ clients |-> ICS02_SetClient(clients, clientId, updatedClient), outcome |-> "ICS02UpdateOK" @@ -64,4 +66,4 @@ ICS02_UpdateClient(clients, clientId, clientHeight) == outcome |-> "ICS02ClientNotFound" ] -============================================================================= \ No newline at end of file +=============================================================================== \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS03.tla b/modules/tests/support/model_based/ICS03.tla index 73c370a042..5eaabf52a7 100644 --- a/modules/tests/support/model_based/ICS03.tla +++ b/modules/tests/support/model_based/ICS03.tla @@ -1,4 +1,4 @@ -------------------------- MODULE ICS03 -------------------------- +------------------------------ MODULE ICS03 ----------------------------------- EXTENDS Integers, FiniteSets, IBCDefinitions, ICS02 @@ -14,7 +14,13 @@ ICS03_ConnectionExists(connections, connectionId) == ICS03_SetConnection(connections, connectionId, connection) == [connections EXCEPT ![connectionId] = connection] -ICS03_ConnectionOpenInit(clients, connections, connectionIdCounter, clientId, counterpartyClientId) == +ICS03_ConnectionOpenInit( + clients, + connections, + connectionIdCounter, + clientId, + counterpartyClientId +) == \* check if the client exists IF ICS02_ClientExists(clients, clientId) THEN \* if the client exists, @@ -36,8 +42,13 @@ ICS03_ConnectionOpenInit(clients, connections, connectionIdCounter, clientId, co connectionId |-> connectionIdCounter, counterpartyConnectionId |-> NullConnectionId ] IN + \* return result with updated state [ - connections |-> ICS03_SetConnection(connections, connectionIdCounter, connection), + connections |-> ICS03_SetConnection( + connections, + connectionIdCounter, + connection + ), connectionIdCounter |-> connectionIdCounter + 1, outcome |-> "ICS03ConnectionOpenInitOK" ] @@ -49,4 +60,4 @@ ICS03_ConnectionOpenInit(clients, connections, connectionIdCounter, clientId, co outcome |-> "ICS03MissingClient" ] -============================================================================= \ No newline at end of file +=============================================================================== \ No newline at end of file From e2605bb80a657a49ba78ac01f1a20b3b92c000d5 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Thu, 4 Feb 2021 12:21:29 +0100 Subject: [PATCH 24/52] s/Null/None --- modules/tests/model_based.rs | 6 +-- modules/tests/step.rs | 4 +- modules/tests/support/model_based/IBC.tla | 53 +++++++++---------- .../support/model_based/IBCDefinitions.tla | 12 ++--- modules/tests/support/model_based/ICS02.tla | 2 +- .../ICS02HeaderVerificationFailureTest.json | 4 +- .../model_based/ICS02UpdateOKTest.json | 4 +- modules/tests/support/model_based/ICS03.tla | 2 +- .../ICS03ConnectionOpenInitOKTest.json | 4 +- .../model_based/ICS03MissingClientTest.json | 4 +- 10 files changed, 46 insertions(+), 49 deletions(-) diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index f4213fffaa..d282a8b740 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -137,12 +137,12 @@ impl modelator::TestExecutor for ICS02TestExecutor { fn initial_step(&mut self, step: Step) -> bool { assert_eq!( step.action.action_type, - ActionType::Null, + ActionType::None, "unexpected action type" ); assert_eq!( step.action_outcome, - ActionOutcome::Null, + ActionOutcome::None, "unexpected action outcome" ); true @@ -150,7 +150,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { fn next_step(&mut self, step: Step) -> bool { match step.action.action_type { - ActionType::Null => panic!("unexpected action type"), + ActionType::None => panic!("unexpected action type"), ActionType::ICS02CreateClient => { // get action parameters let chain_id = step diff --git a/modules/tests/step.rs b/modules/tests/step.rs index a84e67bb33..70aa33108f 100644 --- a/modules/tests/step.rs +++ b/modules/tests/step.rs @@ -28,7 +28,7 @@ pub struct Action { #[derive(Debug, Clone, PartialEq, Deserialize)] pub enum ActionType { - Null, + None, ICS02CreateClient, ICS02UpdateClient, ICS03ConnectionOpenInit, @@ -36,7 +36,7 @@ pub enum ActionType { #[derive(Debug, Clone, PartialEq, Deserialize)] pub enum ActionOutcome { - Null, + None, ICS02CreateOK, ICS02UpdateOK, ICS02ClientNotFound, diff --git a/modules/tests/support/model_based/IBC.tla b/modules/tests/support/model_based/IBC.tla index 6ae053d9bd..03afe415e2 100644 --- a/modules/tests/support/model_based/IBC.tla +++ b/modules/tests/support/model_based/IBC.tla @@ -51,7 +51,7 @@ ConnectionStates == { \* data kept per cliennt Client == [ - height: ClientHeights \union {NullHeight} + height: ClientHeights \union {HeightNone} ] \* mapping from client identifier to its height Clients == [ @@ -60,10 +60,10 @@ Clients == [ \* data kept per connection Connection == [ state: ConnectionStates, - clientId: ClientIds \union {NullClientId}, - counterpartyClientId: ClientIds \union {NullClientId}, - connectionId: ConnectionIds \union {NullConnectionId}, - counterpartyConnectionId: ConnectionIds \union {NullConnectionId} + clientId: ClientIds \union {ClientIdNone}, + counterpartyClientId: ClientIds \union {ClientIdNone}, + connectionId: ConnectionIds \union {ConnectionIdNone}, + counterpartyConnectionId: ConnectionIds \union {ConnectionIdNone} ] \* mapping from connection identifier to its data Connections == [ @@ -82,8 +82,8 @@ Chains == [ ] \* set of possible actions -NullActions == [ - type: {"Null"} +NoneActions == [ + type: {"None"} ] <: {ActionType} CreateClientActions == [ type: {"ICS02CreateClient"}, @@ -103,14 +103,14 @@ ConnectionOpenInitActions == [ counterpartyClientId: ClientIds ] <: {ActionType} Actions == - NullActions \union + NoneActions \union CreateClientActions \union UpdateClientActions \union ConnectionOpenInitActions \* set of possible action outcomes ActionOutcomes == { - "Null", + "None", "ICS02CreateOK", "ICS02UpdateOK", "ICS02ClientNotFound", @@ -137,8 +137,7 @@ CreateClient(chainId, clientHeight) == /\ action' = AsAction([ type |-> "ICS02CreateClient", chainId |-> chainId, - height |-> clientHeight - ]) + height |-> clientHeight]) /\ actionOutcome' = result.outcome UpdateClient(chainId, clientId, clientHeight) == @@ -155,8 +154,7 @@ UpdateClient(chainId, clientId, clientHeight) == type |-> "ICS02UpdateClient", chainId |-> chainId, clientId |-> clientId, - height |-> clientHeight - ]) + height |-> clientHeight]) /\ actionOutcome' = result.outcome ConnectionOpenInit(chainId, clientId, counterpartyClientId) == @@ -182,8 +180,7 @@ ConnectionOpenInit(chainId, clientId, counterpartyClientId) == type |-> "ICS03ConnectionOpenInit", chainId |-> chainId, clientId |-> clientId, - counterpartyClientId |-> counterpartyClientId - ]) + counterpartyClientId |-> counterpartyClientId]) /\ actionOutcome' = result.outcome CreateClientAction == @@ -210,7 +207,7 @@ ConnectionOpenInitAction == \E chainId \in ChainIds: \* select a client id \E clientId \in ClientIds: - \* select a couterparty client id + \* select a counterparty client id \E counterpartyClientId \in ClientIds: \* only create connection if the model constant `MaxConnectionsPerChain` \* allows it @@ -218,27 +215,27 @@ ConnectionOpenInitAction == /\ ConnectionOpenInit(chainId, clientId, counterpartyClientId) Init == - \* create a null client and a null connection - LET nullClient == [ - height |-> NullHeight + \* create a client and a connection with none values + LET clientNone == [ + height |-> HeightNone ] IN - LET nullConnection == [ + LET connectionNone == [ state |-> "Uninit", - clientId |-> NullClientId, - counterpartyClientId |-> NullClientId, - connectionId |-> NullConnectionId, - counterpartyConnectionId |-> NullConnectionId + clientId |-> ClientIdNone, + counterpartyClientId |-> ClientIdNone, + connectionId |-> ConnectionIdNone, + counterpartyConnectionId |-> ConnectionIdNone ] IN \* create an empty chain LET emptyChain == [ - clients |-> [clientId \in ClientIds |-> nullClient], + clients |-> [clientId \in ClientIds |-> clientNone], clientIdCounter |-> 0, - connections |-> [connectionId \in ConnectionIds |-> nullConnection], + connections |-> [connectionId \in ConnectionIds |-> connectionNone], connectionIdCounter |-> 0 ] IN /\ chains = [chainId \in ChainIds |-> emptyChain] - /\ action = AsAction([type |-> "Null"]) - /\ actionOutcome = "Null" + /\ action = AsAction([type |-> "None"]) + /\ actionOutcome = "None" Next == \/ CreateClientAction diff --git a/modules/tests/support/model_based/IBCDefinitions.tla b/modules/tests/support/model_based/IBCDefinitions.tla index 045395cfb8..0d5e8e2385 100644 --- a/modules/tests/support/model_based/IBCDefinitions.tla +++ b/modules/tests/support/model_based/IBCDefinitions.tla @@ -2,11 +2,11 @@ EXTENDS Integers, FiniteSets -\* if a client identifier is undefined then it is -1 -NullClientId == -1 -\* if a client identifier is undefined then it is -1 -NullHeight == -1 -\* if a connection identifier is undefined then it is -1 -NullConnectionId == -1 +\* if a client identifier is not set then it is -1 +ClientIdNone == -1 +\* if a client identifier is not set then it is -1 +HeightNone == -1 +\* if a connection identifier is not set then it is -1 +ConnectionIdNone == -1 =============================================================================== diff --git a/modules/tests/support/model_based/ICS02.tla b/modules/tests/support/model_based/ICS02.tla index ab930a382b..82d788d89e 100644 --- a/modules/tests/support/model_based/ICS02.tla +++ b/modules/tests/support/model_based/ICS02.tla @@ -8,7 +8,7 @@ ICS02_GetClient(clients, clientId) == \* check if `clientId` exists ICS02_ClientExists(clients, clientId) == - ICS02_GetClient(clients, clientId).height /= NullHeight + ICS02_GetClient(clients, clientId).height /= HeightNone \* update `clientId`'s data ICS02_SetClient(clients, clientId, client) == diff --git a/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json b/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json index cde46f8d20..ab10c480d5 100644 --- a/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json +++ b/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json @@ -1,9 +1,9 @@ [ { "action": { - "type": "Null" + "type": "None" }, - "actionOutcome": "Null" + "actionOutcome": "None" }, { "action": { diff --git a/modules/tests/support/model_based/ICS02UpdateOKTest.json b/modules/tests/support/model_based/ICS02UpdateOKTest.json index 8a78fa3bfa..fe417e1d5c 100644 --- a/modules/tests/support/model_based/ICS02UpdateOKTest.json +++ b/modules/tests/support/model_based/ICS02UpdateOKTest.json @@ -1,9 +1,9 @@ [ { "action": { - "type": "Null" + "type": "None" }, - "actionOutcome": "Null" + "actionOutcome": "None" }, { "action": { diff --git a/modules/tests/support/model_based/ICS03.tla b/modules/tests/support/model_based/ICS03.tla index 5eaabf52a7..5228fa7242 100644 --- a/modules/tests/support/model_based/ICS03.tla +++ b/modules/tests/support/model_based/ICS03.tla @@ -40,7 +40,7 @@ ICS03_ConnectionOpenInit( clientId |-> clientId, counterpartyClientId |-> counterpartyClientId, connectionId |-> connectionIdCounter, - counterpartyConnectionId |-> NullConnectionId + counterpartyConnectionId |-> ConnectionIdNone ] IN \* return result with updated state [ diff --git a/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json b/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json index 5ef27e6744..e23a2021d7 100644 --- a/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json +++ b/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json @@ -1,9 +1,9 @@ [ { "action": { - "type": "Null" + "type": "None" }, - "actionOutcome": "Null" + "actionOutcome": "None" }, { "action": { diff --git a/modules/tests/support/model_based/ICS03MissingClientTest.json b/modules/tests/support/model_based/ICS03MissingClientTest.json index 4f180b696b..3bdd7799a5 100644 --- a/modules/tests/support/model_based/ICS03MissingClientTest.json +++ b/modules/tests/support/model_based/ICS03MissingClientTest.json @@ -1,9 +1,9 @@ [ { "action": { - "type": "Null" + "type": "None" }, - "actionOutcome": "Null" + "actionOutcome": "None" }, { "action": { From 5b98a7ef7aa52217fb9b1fe6a26dd5ab14ab62ec Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Thu, 4 Feb 2021 14:30:26 +0100 Subject: [PATCH 25/52] Sketch conn open try; Model chain's height --- modules/tests/support/model_based/IBC.tla | 74 +++++++++++++++++-- .../tests/support/model_based/IBCTests.cfg | 4 +- .../ICS02HeaderVerificationFailureTest.json | 30 +++++++- .../model_based/ICS02UpdateOKTest.json | 70 ++++++++++++++++-- modules/tests/support/model_based/ICS03.tla | 10 +++ .../ICS03ConnectionOpenInitOKTest.json | 34 +++++++-- .../model_based/ICS03MissingClientTest.json | 46 +++++++++++- 7 files changed, 240 insertions(+), 28 deletions(-) diff --git a/modules/tests/support/model_based/IBC.tla b/modules/tests/support/model_based/IBC.tla index 03afe415e2..37ec3277cc 100644 --- a/modules/tests/support/model_based/IBC.tla +++ b/modules/tests/support/model_based/IBC.tla @@ -6,13 +6,13 @@ EXTENDS Integers, FiniteSets, ICS02, ICS03 CONSTANT ChainIds \* max number of client to be created per chain CONSTANT MaxClientsPerChain -ASSUME MaxClientsPerChain > 0 +ASSUME MaxClientsPerChain >= 0 \* max height which clients can reach CONSTANT MaxClientHeight -ASSUME MaxClientHeight > 0 +ASSUME MaxClientHeight >= 0 \* max number of connections to be created per chain CONSTANT MaxConnectionsPerChain -ASSUME MaxConnectionsPerChain > 0 +ASSUME MaxConnectionsPerChain >= 0 \* mapping from chain id to its data VARIABLE chains @@ -20,6 +20,7 @@ VARIABLE chains VARIABLE action \* string with the outcome of the last operation VARIABLE actionOutcome +vars == <> (********************** TYPE ANNOTATIONS FOR APALACHE ************************) \* operator for type annotations @@ -35,6 +36,8 @@ ActionType == [ AsAction(a) == a <: ActionType (******************* END OF TYPE ANNOTATIONS FOR APALACHE ********************) +\* set of possible chain heights +ChainHeights == Int \* set of possible client identifiers ClientIds == 0..(MaxClientsPerChain - 1) \* set of possible client heights @@ -71,6 +74,7 @@ Connections == [ ] \* data kept per chain Chain == [ + height: ChainHeights, clients: Clients, clientIdCounter: 0..MaxClientsPerChain, connections: Connections, @@ -129,6 +133,7 @@ CreateClient(chainId, clientHeight) == LET result == ICS02_CreateClient(clients, clientIdCounter, clientHeight) IN \* update the chain LET updatedChain == [chain EXCEPT + !.height = @ + 1, !.clients = result.clients, !.clientIdCounter = result.clientIdCounter ] IN @@ -146,6 +151,7 @@ UpdateClient(chainId, clientId, clientHeight) == LET result == ICS02_UpdateClient(clients, clientId, clientHeight) IN \* update the chain LET updatedChain == [chain EXCEPT + !.height = @ + 1, !.clients = result.clients ] IN \* update `chains`, set the `action` and its `actionOutcome` @@ -171,6 +177,7 @@ ConnectionOpenInit(chainId, clientId, counterpartyClientId) == ) IN \* update the chain LET updatedChain == [chain EXCEPT + !.height = @ + 1, !.connections = result.connections, !.connectionIdCounter = result.connectionIdCounter ] IN @@ -183,6 +190,21 @@ ConnectionOpenInit(chainId, clientId, counterpartyClientId) == counterpartyClientId |-> counterpartyClientId]) /\ actionOutcome' = result.outcome +ConnectionOpenTry(chainId, clientId, counterpartyClientId, connectionId) == + LET chain == chains[chainId] IN + LET clients == chain.clients IN + LET connections == chain.connections IN + LET connectionIdCounter == chain.connectionIdCounter IN + LET result == ICS03_ConnectionOpenTry( + clients, + connections, + connectionIdCounter, + clientId, + counterpartyClientId, + connectionId + ) IN + UNCHANGED vars + CreateClientAction == \* select a chain id \E chainId \in ChainIds: @@ -190,8 +212,10 @@ CreateClientAction == \E clientHeight \in ClientHeights: \* only create client if the model constant `MaxClientsPerChain` allows \* it - /\ chains[chainId].clientIdCounter \in ClientIds - /\ CreateClient(chainId, clientHeight) + IF chains[chainId].clientIdCounter \in ClientIds THEN + CreateClient(chainId, clientHeight) + ELSE + UNCHANGED vars UpdateClientAction == \* select a chain id @@ -211,8 +235,40 @@ ConnectionOpenInitAction == \E counterpartyClientId \in ClientIds: \* only create connection if the model constant `MaxConnectionsPerChain` \* allows it - /\ chains[chainId].connectionIdCounter \in ConnectionIds - /\ ConnectionOpenInit(chainId, clientId, counterpartyClientId) + IF chains[chainId].connectionIdCounter \in ConnectionIds THEN + ConnectionOpenInit(chainId, clientId, counterpartyClientId) + ELSE + UNCHANGED vars + +ConnectionOpenTryAction == + \* select a chain id + \E chainId \in ChainIds: + \* select a client id + \E clientId \in ClientIds: + \* select a counterparty client id + \E counterpartyClientId \in ClientIds: + \* select a connection id (which can be none) + \E connectionId \in ConnectionIds \union {ConnectionIdNone}: + IF connectionId = ConnectionIdNone THEN + \* in this case we're trying to create a new connection; only create + \* connection if the model constant `MaxConnectionsPerChain` allows + \* it + IF chains[chainId].connectionIdCounter \in ConnectionIds THEN + ConnectionOpenTry( + chainId, + clientId, + counterpartyClientId, + connectionId + ) + ELSE + UNCHANGED vars + ELSE + ConnectionOpenTry( + chainId, + clientId, + counterpartyClientId, + connectionId + ) Init == \* create a client and a connection with none values @@ -228,6 +284,7 @@ Init == ] IN \* create an empty chain LET emptyChain == [ + height |-> 0, clients |-> [clientId \in ClientIds |-> clientNone], clientIdCounter |-> 0, connections |-> [connectionId \in ConnectionIds |-> connectionNone], @@ -241,7 +298,8 @@ Next == \/ CreateClientAction \/ UpdateClientAction \/ ConnectionOpenInitAction - \/ UNCHANGED <> + \/ ConnectionOpenTryAction + \/ UNCHANGED vars (******************************** Invariants *********************************) diff --git a/modules/tests/support/model_based/IBCTests.cfg b/modules/tests/support/model_based/IBCTests.cfg index 79005a26bc..f87ef29091 100644 --- a/modules/tests/support/model_based/IBCTests.cfg +++ b/modules/tests/support/model_based/IBCTests.cfg @@ -11,6 +11,6 @@ INVARIANTS \* ICS02CreateOKTest \* ICS02UpdateOKTest \* ICS02ClientNotFoundTest - \* ICS02HeaderVerificationFailureTest - ICS03ConnectionOpenInitOKTest + ICS02HeaderVerificationFailureTest + \* ICS03ConnectionOpenInitOKTest \* ICS03MissingClientTest \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json b/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json index ab10c480d5..83857d684d 100644 --- a/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json +++ b/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json @@ -3,7 +3,15 @@ "action": { "type": "None" }, - "actionOutcome": "None" + "actionOutcome": "None", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 0 + } + } }, { "action": { @@ -11,7 +19,15 @@ "height": 1, "type": "ICS02CreateClient" }, - "actionOutcome": "ICS02CreateOK" + "actionOutcome": "ICS02CreateOK", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 1 + } + } }, { "action": { @@ -20,6 +36,14 @@ "height": 1, "type": "ICS02UpdateClient" }, - "actionOutcome": "ICS02HeaderVerificationFailure" + "actionOutcome": "ICS02HeaderVerificationFailure", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 2 + } + } } ] \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS02UpdateOKTest.json b/modules/tests/support/model_based/ICS02UpdateOKTest.json index fe417e1d5c..0cf02fc9c6 100644 --- a/modules/tests/support/model_based/ICS02UpdateOKTest.json +++ b/modules/tests/support/model_based/ICS02UpdateOKTest.json @@ -3,7 +3,15 @@ "action": { "type": "None" }, - "actionOutcome": "None" + "actionOutcome": "None", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 0 + } + } }, { "action": { @@ -11,7 +19,15 @@ "height": 1, "type": "ICS02CreateClient" }, - "actionOutcome": "ICS02CreateOK" + "actionOutcome": "ICS02CreateOK", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 0 + } + } }, { "action": { @@ -20,7 +36,15 @@ "height": 2, "type": "ICS02UpdateClient" }, - "actionOutcome": "ICS02UpdateOK" + "actionOutcome": "ICS02UpdateOK", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 0 + } + } }, { "action": { @@ -28,7 +52,15 @@ "height": 1, "type": "ICS02CreateClient" }, - "actionOutcome": "ICS02CreateOK" + "actionOutcome": "ICS02CreateOK", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 0 + } + } }, { "action": { @@ -37,7 +69,15 @@ "height": 2, "type": "ICS02UpdateClient" }, - "actionOutcome": "ICS02UpdateOK" + "actionOutcome": "ICS02UpdateOK", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 0 + } + } }, { "action": { @@ -45,7 +85,15 @@ "height": 1, "type": "ICS02CreateClient" }, - "actionOutcome": "ICS02CreateOK" + "actionOutcome": "ICS02CreateOK", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 0 + } + } }, { "action": { @@ -54,6 +102,14 @@ "height": 2, "type": "ICS02UpdateClient" }, - "actionOutcome": "ICS02UpdateOK" + "actionOutcome": "ICS02UpdateOK", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 0 + } + } } ] \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS03.tla b/modules/tests/support/model_based/ICS03.tla index 5228fa7242..f8e5e6632c 100644 --- a/modules/tests/support/model_based/ICS03.tla +++ b/modules/tests/support/model_based/ICS03.tla @@ -60,4 +60,14 @@ ICS03_ConnectionOpenInit( outcome |-> "ICS03MissingClient" ] +ICS03_ConnectionOpenTry( + clients, + connections, + connectionIdCounter, + clientId, + counterpartyClientId, + connectionId +) == + TRUE + =============================================================================== \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json b/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json index e23a2021d7..9a71e469e2 100644 --- a/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json +++ b/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json @@ -3,23 +3,47 @@ "action": { "type": "None" }, - "actionOutcome": "None" + "actionOutcome": "None", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 0 + } + } }, { "action": { - "chainId": "chain-A", + "chainId": "chain-B", "height": 1, "type": "ICS02CreateClient" }, - "actionOutcome": "ICS02CreateOK" + "actionOutcome": "ICS02CreateOK", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 1 + } + } }, { "action": { - "chainId": "chain-A", + "chainId": "chain-B", "clientId": 0, "counterpartyClientId": 0, "type": "ICS03ConnectionOpenInit" }, - "actionOutcome": "ICS03ConnectionOpenInitOK" + "actionOutcome": "ICS03ConnectionOpenInitOK", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 2 + } + } } ] \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS03MissingClientTest.json b/modules/tests/support/model_based/ICS03MissingClientTest.json index 3bdd7799a5..c6948d035f 100644 --- a/modules/tests/support/model_based/ICS03MissingClientTest.json +++ b/modules/tests/support/model_based/ICS03MissingClientTest.json @@ -3,7 +3,15 @@ "action": { "type": "None" }, - "actionOutcome": "None" + "actionOutcome": "None", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 0 + } + } }, { "action": { @@ -11,7 +19,31 @@ "height": 1, "type": "ICS02CreateClient" }, - "actionOutcome": "ICS02CreateOK" + "actionOutcome": "ICS02CreateOK", + "chains": { + "chain-A": { + "height": 1 + }, + "chain-B": { + "height": 0 + } + } + }, + { + "action": { + "chainId": "chain-A", + "height": 1, + "type": "ICS02CreateClient" + }, + "actionOutcome": "ICS02CreateOK", + "chains": { + "chain-A": { + "height": 2 + }, + "chain-B": { + "height": 0 + } + } }, { "action": { @@ -20,6 +52,14 @@ "counterpartyClientId": 0, "type": "ICS03ConnectionOpenInit" }, - "actionOutcome": "ICS03MissingClient" + "actionOutcome": "ICS03MissingClient", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 1 + } + } } ] \ No newline at end of file From 244efa6c7d372bda3d2eaad5afcf6ea4e777a486 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Thu, 4 Feb 2021 14:33:35 +0100 Subject: [PATCH 26/52] Bound model space --- modules/tests/support/model_based/IBC.tla | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/tests/support/model_based/IBC.tla b/modules/tests/support/model_based/IBC.tla index 37ec3277cc..26d257fb2a 100644 --- a/modules/tests/support/model_based/IBC.tla +++ b/modules/tests/support/model_based/IBC.tla @@ -74,7 +74,7 @@ Connections == [ ] \* data kept per chain Chain == [ - height: ChainHeights, + \* height: ChainHeights, clients: Clients, clientIdCounter: 0..MaxClientsPerChain, connections: Connections, @@ -133,7 +133,7 @@ CreateClient(chainId, clientHeight) == LET result == ICS02_CreateClient(clients, clientIdCounter, clientHeight) IN \* update the chain LET updatedChain == [chain EXCEPT - !.height = @ + 1, + \* !.height = @ + 1, !.clients = result.clients, !.clientIdCounter = result.clientIdCounter ] IN @@ -151,7 +151,7 @@ UpdateClient(chainId, clientId, clientHeight) == LET result == ICS02_UpdateClient(clients, clientId, clientHeight) IN \* update the chain LET updatedChain == [chain EXCEPT - !.height = @ + 1, + \* !.height = @ + 1, !.clients = result.clients ] IN \* update `chains`, set the `action` and its `actionOutcome` @@ -177,7 +177,7 @@ ConnectionOpenInit(chainId, clientId, counterpartyClientId) == ) IN \* update the chain LET updatedChain == [chain EXCEPT - !.height = @ + 1, + \* !.height = @ + 1, !.connections = result.connections, !.connectionIdCounter = result.connectionIdCounter ] IN @@ -284,7 +284,7 @@ Init == ] IN \* create an empty chain LET emptyChain == [ - height |-> 0, + \* height |-> 0, clients |-> [clientId \in ClientIds |-> clientNone], clientIdCounter |-> 0, connections |-> [connectionId \in ConnectionIds |-> connectionNone], From 7d9202a8331c00ea571688f89826f755619e9a58 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Thu, 4 Feb 2021 15:35:55 +0100 Subject: [PATCH 27/52] Only update chain height upon success --- modules/tests/support/model_based/IBC.cfg | 1 + modules/tests/support/model_based/IBC.tla | 14 ++++++++----- .../tests/support/model_based/IBCTests.cfg | 1 + .../model_based/ICS02UpdateOKTest.json | 20 +++++++++---------- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/modules/tests/support/model_based/IBC.cfg b/modules/tests/support/model_based/IBC.cfg index 75f117802a..0f1dcc1985 100644 --- a/modules/tests/support/model_based/IBC.cfg +++ b/modules/tests/support/model_based/IBC.cfg @@ -1,5 +1,6 @@ CONSTANTS ChainIds = {"chain-A", "chain-B"} + MaxChainHeight = 4 MaxClientsPerChain = 2 MaxClientHeight = 2 MaxConnectionsPerChain = 2 diff --git a/modules/tests/support/model_based/IBC.tla b/modules/tests/support/model_based/IBC.tla index 26d257fb2a..49c6ae925f 100644 --- a/modules/tests/support/model_based/IBC.tla +++ b/modules/tests/support/model_based/IBC.tla @@ -74,7 +74,7 @@ Connections == [ ] \* data kept per chain Chain == [ - \* height: ChainHeights, + height: ChainHeights, clients: Clients, clientIdCounter: 0..MaxClientsPerChain, connections: Connections, @@ -126,6 +126,10 @@ ActionOutcomes == { (***************************** Specification *********************************) +\* update chain height if outcome was ok +UpdateHeight(height, outcome, okOutcome) == + IF outcome = okOutcome THEN height + 1 ELSE height + CreateClient(chainId, clientHeight) == LET chain == chains[chainId] IN LET clients == chain.clients IN @@ -133,7 +137,7 @@ CreateClient(chainId, clientHeight) == LET result == ICS02_CreateClient(clients, clientIdCounter, clientHeight) IN \* update the chain LET updatedChain == [chain EXCEPT - \* !.height = @ + 1, + !.height = UpdateHeight(@, result.outcome, "ICS02CreateOK"), !.clients = result.clients, !.clientIdCounter = result.clientIdCounter ] IN @@ -151,7 +155,7 @@ UpdateClient(chainId, clientId, clientHeight) == LET result == ICS02_UpdateClient(clients, clientId, clientHeight) IN \* update the chain LET updatedChain == [chain EXCEPT - \* !.height = @ + 1, + !.height = UpdateHeight(@, result.outcome, "ICS03CreateOK"), !.clients = result.clients ] IN \* update `chains`, set the `action` and its `actionOutcome` @@ -177,7 +181,7 @@ ConnectionOpenInit(chainId, clientId, counterpartyClientId) == ) IN \* update the chain LET updatedChain == [chain EXCEPT - \* !.height = @ + 1, + !.height = UpdateHeight(@, result.outcome, "ICS03ConnectionOpenInitOK"), !.connections = result.connections, !.connectionIdCounter = result.connectionIdCounter ] IN @@ -284,7 +288,7 @@ Init == ] IN \* create an empty chain LET emptyChain == [ - \* height |-> 0, + height |-> 0, clients |-> [clientId \in ClientIds |-> clientNone], clientIdCounter |-> 0, connections |-> [connectionId \in ConnectionIds |-> connectionNone], diff --git a/modules/tests/support/model_based/IBCTests.cfg b/modules/tests/support/model_based/IBCTests.cfg index f87ef29091..d5e07381b5 100644 --- a/modules/tests/support/model_based/IBCTests.cfg +++ b/modules/tests/support/model_based/IBCTests.cfg @@ -1,5 +1,6 @@ CONSTANTS ChainIds = {"chain-A", "chain-B"} + MaxChainHeight = 4 MaxClientsPerChain = 2 MaxClientHeight = 2 MaxConnectionsPerChain = 2 diff --git a/modules/tests/support/model_based/ICS02UpdateOKTest.json b/modules/tests/support/model_based/ICS02UpdateOKTest.json index 0cf02fc9c6..4fbcd5ec30 100644 --- a/modules/tests/support/model_based/ICS02UpdateOKTest.json +++ b/modules/tests/support/model_based/ICS02UpdateOKTest.json @@ -25,7 +25,7 @@ "height": 0 }, "chain-B": { - "height": 0 + "height": 1 } } }, @@ -42,7 +42,7 @@ "height": 0 }, "chain-B": { - "height": 0 + "height": 2 } } }, @@ -55,10 +55,10 @@ "actionOutcome": "ICS02CreateOK", "chains": { "chain-A": { - "height": 0 + "height": 1 }, "chain-B": { - "height": 0 + "height": 2 } } }, @@ -72,10 +72,10 @@ "actionOutcome": "ICS02UpdateOK", "chains": { "chain-A": { - "height": 0 + "height": 2 }, "chain-B": { - "height": 0 + "height": 2 } } }, @@ -88,10 +88,10 @@ "actionOutcome": "ICS02CreateOK", "chains": { "chain-A": { - "height": 0 + "height": 3 }, "chain-B": { - "height": 0 + "height": 2 } } }, @@ -105,10 +105,10 @@ "actionOutcome": "ICS02UpdateOK", "chains": { "chain-A": { - "height": 0 + "height": 4 }, "chain-B": { - "height": 0 + "height": 2 } } } From 28879f0efda68a550691f82a779860a2b9618825 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Thu, 4 Feb 2021 15:44:39 +0100 Subject: [PATCH 28/52] Check that chain heights match the ones in the model --- modules/tests/model_based.rs | 74 +++++++++++++------ modules/tests/modelator.rs | 32 ++++---- modules/tests/step.rs | 8 ++ .../ICS02HeaderVerificationFailureTest.json | 2 +- .../model_based/ICS03MissingClientTest.json | 4 +- 5 files changed, 79 insertions(+), 41 deletions(-) diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index d282a8b740..53e8622898 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -1,7 +1,6 @@ mod modelator; mod step; -use ibc::ics02_client::client_def::AnyHeader; use ibc::ics02_client::client_def::{AnyClientState, AnyConsensusState}; use ibc::ics02_client::client_type::ClientType; use ibc::ics02_client::error::Kind as ICS02ErrorKind; @@ -24,10 +23,11 @@ use ibc::mock::context::MockContext; use ibc::mock::header::MockHeader; use ibc::mock::host::HostType; use ibc::Height; +use ibc::{ics02_client::client_def::AnyHeader, ics18_relayer::context::ICS18Context}; use std::collections::HashMap; use std::error::Error; use std::fmt::{Debug, Display}; -use step::{ActionOutcome, ActionType, Step}; +use step::{ActionOutcome, ActionType, Chain, Step}; use tendermint::account::Id as AccountId; #[derive(Debug)] @@ -43,19 +43,33 @@ impl ICS02TestExecutor { } } - /// Find the `MockContext` of a given `chain_id`. - /// If no context is found, a new one is created. - fn get_chain_context_or_init(&mut self, chain_id: String) -> &mut MockContext { - self.contexts.entry(chain_id.clone()).or_insert_with(|| { - let max_history_size = 1; - let initial_height = 0; - MockContext::new( - ChainId::new(chain_id, Self::epoch()), - HostType::Mock, - max_history_size, - Height::new(Self::epoch(), initial_height), - ) - }) + /// 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 max_history_size = 1; + let ctx = MockContext::new( + ChainId::new(chain_id.clone(), Self::epoch()), + HostType::Mock, + max_history_size, + Height::new(Self::epoch(), 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(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(chain_id) + .expect("chain context should have been initialized") } fn extract_handler_error_kind(result: Result<(), ICS18Error>) -> K @@ -100,8 +114,12 @@ impl ICS02TestExecutor { .expect("it should be possible to create the client identifier") } + fn height(height: u64) -> Height { + Height::new(Self::epoch(), height) + } + fn mock_header(height: u64) -> MockHeader { - MockHeader(Height::new(Self::epoch(), height)) + MockHeader(Self::height(height)) } fn header(height: u64) -> AnyHeader { @@ -131,6 +149,14 @@ impl ICS02TestExecutor { fn delay_period() -> u64 { 0 } + + // Check that chain heights match the ones in the model. + fn check_chain_heights(&self, chains: HashMap) -> 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 for ICS02TestExecutor { @@ -145,11 +171,15 @@ impl modelator::TestExecutor for ICS02TestExecutor { 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 { - match step.action.action_type { + let outcome_matches = match step.action.action_type { ActionType::None => panic!("unexpected action type"), ActionType::ICS02CreateClient => { // get action parameters @@ -163,7 +193,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { .expect("create client action should have a height"); // get chain's context - let ctx = self.get_chain_context_or_init(chain_id); + let ctx = self.chain_context_mut(&chain_id); // create ICS26 message and deliver it let msg = ICS26Envelope::ICS2Msg(ClientMsg::CreateClient(MsgCreateAnyClient { @@ -198,7 +228,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { .expect("update client action should have a height"); // get chain's context - let ctx = self.get_chain_context_or_init(chain_id); + let ctx = self.chain_context_mut(&chain_id); // create ICS26 message and deliver it let msg = ICS26Envelope::ICS2Msg(ClientMsg::UpdateClient(MsgUpdateAnyClient { @@ -249,7 +279,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { ); // get chain's context - let ctx = self.get_chain_context_or_init(chain_id); + let ctx = self.chain_context_mut(&chain_id); // create ICS26 message and deliver it let msg = ICS26Envelope::ICS3Msg(ConnectionMsg::ConnectionOpenInit( @@ -282,7 +312,9 @@ impl modelator::TestExecutor for ICS02TestExecutor { action => panic!("unexpected action outcome {:?}", action), } } - } + }; + // also check that chain heights match + outcome_matches && self.check_chain_heights(step.chains) } } diff --git a/modules/tests/modelator.rs b/modules/tests/modelator.rs index 91d60d6800..6f0f9b7b22 100644 --- a/modules/tests/modelator.rs +++ b/modules/tests/modelator.rs @@ -18,30 +18,28 @@ where { // open test file let file = File::open(path.as_ref()) - .wrap_err_with(|| format!("test file {:?} not found.", path.as_ref()))?; + .wrap_err_with(|| format!("test {:?} not found.", path.as_ref()))?; let reader = BufReader::new(file); // parse test file let steps: Vec = serde_json::de::from_reader(reader) - .wrap_err_with(|| format!("test file {:?} could not be deserialized", path.as_ref()))?; + .wrap_err_with(|| format!("test {:?} could not be deserialized", path.as_ref()))?; + let step_count = steps.len(); - let mut steps = steps.into_iter(); + for (i, step) in steps.into_iter().enumerate() { + // check the step + let ok = if i == 0 { + executor.initial_step(step.clone()) + } else { + executor.next_step(step.clone()) + }; - // check the initial step - if let Some(step) = steps.next() { - if !executor.initial_step(step.clone()) { - return Err(eyre!("check failed on initial step:\n{:#?}", step)); - } - } else { - println!("WARNING: test file {:?} had 0 steps", path.as_ref()); - return Ok(()); - } - - // check the remaining steps - for step in steps { - if !executor.next_step(step.clone()) { + if !ok { return Err(eyre!( - "check failed on step:\n{:#?}\n\nexecutor:\n{:#?}", + "test {:?} failed on step {}/{}:\n{:#?}\n\nexecutor:\n{:#?}", + path.as_ref(), + i + 1, + step_count, step, executor )); diff --git a/modules/tests/step.rs b/modules/tests/step.rs index 70aa33108f..eabf76d08f 100644 --- a/modules/tests/step.rs +++ b/modules/tests/step.rs @@ -1,4 +1,5 @@ use serde::Deserialize; +use std::collections::HashMap; use std::fmt::Debug; #[derive(Debug, Clone, Deserialize)] @@ -7,6 +8,8 @@ pub struct Step { #[serde(alias = "actionOutcome")] pub action_outcome: ActionOutcome, + + pub chains: HashMap, } #[derive(Debug, Clone, Deserialize)] @@ -44,3 +47,8 @@ pub enum ActionOutcome { ICS03ConnectionOpenInitOK, ICS03MissingClient, } + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct Chain { + pub height: u64, +} diff --git a/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json b/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json index 83857d684d..2fb55f7080 100644 --- a/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json +++ b/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json @@ -42,7 +42,7 @@ "height": 0 }, "chain-B": { - "height": 2 + "height": 1 } } } diff --git a/modules/tests/support/model_based/ICS03MissingClientTest.json b/modules/tests/support/model_based/ICS03MissingClientTest.json index c6948d035f..86e67b14d0 100644 --- a/modules/tests/support/model_based/ICS03MissingClientTest.json +++ b/modules/tests/support/model_based/ICS03MissingClientTest.json @@ -55,10 +55,10 @@ "actionOutcome": "ICS03MissingClient", "chains": { "chain-A": { - "height": 0 + "height": 2 }, "chain-B": { - "height": 1 + "height": 0 } } } From a870537360e16fb5046c799e1cc6e86f01166bd1 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Thu, 4 Feb 2021 16:17:41 +0100 Subject: [PATCH 29/52] Sketch conn open try --- modules/tests/model_based.rs | 8 +-- modules/tests/support/model_based/IBC.tla | 72 +++++++++++++++++---- modules/tests/support/model_based/ICS03.tla | 17 ++++- 3 files changed, 78 insertions(+), 19 deletions(-) diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index 53e8622898..3622e61599 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -72,11 +72,11 @@ impl ICS02TestExecutor { .expect("chain context should have been initialized") } - fn extract_handler_error_kind(result: Result<(), ICS18Error>) -> K + fn extract_handler_error_kind(ics18_result: Result<(), ICS18Error>) -> K where K: Clone + Debug + Display + Into + 'static, { - let ics18_error = result.expect_err("ICS18 error expected"); + let ics18_error = ics18_result.expect_err("ICS18 error expected"); assert!(matches!( ics18_error.kind(), ICS18ErrorKind::TransactionFailed @@ -94,7 +94,7 @@ impl ICS02TestExecutor { .source() .expect("expected source in ICS26 error") .downcast_ref::>() - .expect("ICS26 source should be an error") + .expect("ICS26 source should be an handler error") .kind() .clone() } @@ -150,7 +150,7 @@ impl ICS02TestExecutor { 0 } - // Check that chain heights match the ones in the model. + /// Check that chain heights match the ones in the model. fn check_chain_heights(&self, chains: HashMap) -> bool { chains.into_iter().all(|(chain_id, chain)| { let ctx = self.chain_context(&chain_id); diff --git a/modules/tests/support/model_based/IBC.tla b/modules/tests/support/model_based/IBC.tla index 49c6ae925f..dcc21256fb 100644 --- a/modules/tests/support/model_based/IBC.tla +++ b/modules/tests/support/model_based/IBC.tla @@ -29,9 +29,10 @@ a <: b == a ActionType == [ type |-> STRING, chainId |-> STRING, - height |-> Int, + clientHeight |-> Int, clientId |-> Int, - counterpartyClientId |-> Int + counterpartyClientId |-> Int, + connectionId |-> Int ] AsAction(a) == a <: ActionType (******************* END OF TYPE ANNOTATIONS FOR APALACHE ********************) @@ -92,13 +93,13 @@ NoneActions == [ CreateClientActions == [ type: {"ICS02CreateClient"}, chainId: ChainIds, - height: ClientHeights + clientHeight: ClientHeights ] <: {ActionType} UpdateClientActions == [ type: {"ICS02UpdateClient"}, chainId: ChainIds, clientId: ClientIds, - height: ClientHeights + clientHeight: ClientHeights ] <: {ActionType} ConnectionOpenInitActions == [ type: {"ICS03ConnectionOpenInit"}, @@ -106,28 +107,43 @@ ConnectionOpenInitActions == [ clientId: ClientIds, counterpartyClientId: ClientIds ] <: {ActionType} +ConnectionOpenTryActions == [ + type: {"ICS03ConnectionOpenTry"}, + chainId: ChainIds, + clientId: ClientIds, + clientHeight: ClientHeights, + counterpartyClientId: ClientIds, + connectionId: ConnectionIds \union {ConnectionIdNone} +] <: {ActionType} Actions == NoneActions \union CreateClientActions \union UpdateClientActions \union - ConnectionOpenInitActions + ConnectionOpenInitActions \union + ConnectionOpenTryActions \* set of possible action outcomes ActionOutcomes == { "None", + "ModelError", + \* ICS02_CreateClient outcomes: "ICS02CreateOK", + \* ICS02_UpdateClient outcomes: "ICS02UpdateOK", "ICS02ClientNotFound", "ICS02HeaderVerificationFailure", + \* ICS03_ConnectionOpenInit outcomes: "ICS03ConnectionOpenInitOK", "ICS03MissingClient", - "ModelError" + \* ICS03_ConnectionOpenTry outcomes: + "ICS03ConnectionOpenTryOK", + "ICS03InvalidConsensusHeight" } (***************************** Specification *********************************) \* update chain height if outcome was ok -UpdateHeight(height, outcome, okOutcome) == +UpdateChainHeight(height, outcome, okOutcome) == IF outcome = okOutcome THEN height + 1 ELSE height CreateClient(chainId, clientHeight) == @@ -137,7 +153,7 @@ CreateClient(chainId, clientHeight) == LET result == ICS02_CreateClient(clients, clientIdCounter, clientHeight) IN \* update the chain LET updatedChain == [chain EXCEPT - !.height = UpdateHeight(@, result.outcome, "ICS02CreateOK"), + !.height = UpdateChainHeight(@, result.outcome, "ICS02CreateOK"), !.clients = result.clients, !.clientIdCounter = result.clientIdCounter ] IN @@ -146,7 +162,7 @@ CreateClient(chainId, clientHeight) == /\ action' = AsAction([ type |-> "ICS02CreateClient", chainId |-> chainId, - height |-> clientHeight]) + clientHeight |-> clientHeight]) /\ actionOutcome' = result.outcome UpdateClient(chainId, clientId, clientHeight) == @@ -155,7 +171,7 @@ UpdateClient(chainId, clientId, clientHeight) == LET result == ICS02_UpdateClient(clients, clientId, clientHeight) IN \* update the chain LET updatedChain == [chain EXCEPT - !.height = UpdateHeight(@, result.outcome, "ICS03CreateOK"), + !.height = UpdateChainHeight(@, result.outcome, "ICS03CreateOK"), !.clients = result.clients ] IN \* update `chains`, set the `action` and its `actionOutcome` @@ -164,7 +180,7 @@ UpdateClient(chainId, clientId, clientHeight) == type |-> "ICS02UpdateClient", chainId |-> chainId, clientId |-> clientId, - height |-> clientHeight]) + clientHeight |-> clientHeight]) /\ actionOutcome' = result.outcome ConnectionOpenInit(chainId, clientId, counterpartyClientId) == @@ -181,7 +197,7 @@ ConnectionOpenInit(chainId, clientId, counterpartyClientId) == ) IN \* update the chain LET updatedChain == [chain EXCEPT - !.height = UpdateHeight(@, result.outcome, "ICS03ConnectionOpenInitOK"), + !.height = UpdateChainHeight(@, result.outcome, "ICS03ConnectionOpenInitOK"), !.connections = result.connections, !.connectionIdCounter = result.connectionIdCounter ] IN @@ -194,20 +210,44 @@ ConnectionOpenInit(chainId, clientId, counterpartyClientId) == counterpartyClientId |-> counterpartyClientId]) /\ actionOutcome' = result.outcome -ConnectionOpenTry(chainId, clientId, counterpartyClientId, connectionId) == +ConnectionOpenTry( + chainId, + clientId, + clientHeight, + counterpartyClientId, + connectionId +) == LET chain == chains[chainId] IN + LET height == chain.height IN LET clients == chain.clients IN LET connections == chain.connections IN LET connectionIdCounter == chain.connectionIdCounter IN LET result == ICS03_ConnectionOpenTry( + height, clients, connections, connectionIdCounter, clientId, + clientHeight, counterpartyClientId, connectionId ) IN - UNCHANGED vars + \* update the chain + LET updatedChain == [chain EXCEPT + !.height = UpdateChainHeight(@, result.outcome, "ICS03ConnectionOpenTryOK"), + !.connections = result.connections, + !.connectionIdCounter = result.connectionIdCounter + ] IN + \* update `chains`, set the `action` and its `actionOutcome` + /\ chains' = [chains EXCEPT ![chainId] = updatedChain] + /\ action' = AsAction([ + type |-> "ICS03ConnectionOpenTry", + chainId |-> chainId, + clientId |-> clientId, + clientHeight |-> clientHeight, + counterpartyClientId |-> counterpartyClientId, + connectionId |-> connectionId]) + /\ actionOutcome' = result.outcome CreateClientAction == \* select a chain id @@ -249,6 +289,8 @@ ConnectionOpenTryAction == \E chainId \in ChainIds: \* select a client id \E clientId \in ClientIds: + \* select a claimed height for the client + \E clientHeight \in ClientHeights: \* select a counterparty client id \E counterpartyClientId \in ClientIds: \* select a connection id (which can be none) @@ -261,6 +303,7 @@ ConnectionOpenTryAction == ConnectionOpenTry( chainId, clientId, + clientHeight, counterpartyClientId, connectionId ) @@ -270,6 +313,7 @@ ConnectionOpenTryAction == ConnectionOpenTry( chainId, clientId, + clientHeight, counterpartyClientId, connectionId ) diff --git a/modules/tests/support/model_based/ICS03.tla b/modules/tests/support/model_based/ICS03.tla index f8e5e6632c..c3d1d675bb 100644 --- a/modules/tests/support/model_based/ICS03.tla +++ b/modules/tests/support/model_based/ICS03.tla @@ -61,13 +61,28 @@ ICS03_ConnectionOpenInit( ] ICS03_ConnectionOpenTry( + height, clients, connections, connectionIdCounter, clientId, + clientHeight, counterpartyClientId, connectionId ) == - TRUE + \* check if client's claimed height + IF clientHeight > height THEN + \* if client's height is too advanced, then set an error outcome + [ + connections |-> connections, + connectionIdCounter |-> connectionIdCounter, + outcome |-> "ICS03InvalidConsensusHeight" + ] + ELSE + [ + connections |-> connections, + connectionIdCounter |-> connectionIdCounter, + outcome |-> "ICS03ConnectionOpenTryOK" + ] =============================================================================== \ No newline at end of file From a533def051d856b4902debd0e8c6166a18349486 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Thu, 4 Feb 2021 19:00:51 +0100 Subject: [PATCH 30/52] Sketch conn open try --- modules/src/ics24_host/identifier.rs | 14 ++ modules/src/mock/context.rs | 16 +- modules/tests/model_based.rs | 138 +++++++++++++++--- modules/tests/step.rs | 28 +++- modules/tests/support/model_based/IBC.cfg | 1 - .../tests/support/model_based/IBCTests.cfg | 7 +- .../tests/support/model_based/IBCTests.tla | 8 + .../ICS02HeaderVerificationFailureTest.json | 4 +- .../model_based/ICS02UpdateOKTest.json | 12 +- .../ICS03ConnectionOpenInitOKTest.json | 2 +- .../ICS03InvalidConsensusHeightTest.json | 35 +++++ .../model_based/ICS03MissingClientTest.json | 4 +- 12 files changed, 222 insertions(+), 47 deletions(-) create mode 100644 modules/tests/support/model_based/ICS03InvalidConsensusHeightTest.json diff --git a/modules/src/ics24_host/identifier.rs b/modules/src/ics24_host/identifier.rs index e76c9f7d60..965d04038b 100644 --- a/modules/src/ics24_host/identifier.rs +++ b/modules/src/ics24_host/identifier.rs @@ -197,6 +197,13 @@ impl PartialEq for ClientId { pub struct ConnectionId(String); impl ConnectionId { + /// Builds a new connection identifier. + pub fn new(counter: u64) -> Result { + let prefix = "connection"; + let id = format!("{}-{}", prefix, counter); + Self::from_str(id.as_str()) + } + /// Get this identifier as a borrowed `&str` pub fn as_str(&self) -> &str { &self.0 @@ -283,6 +290,13 @@ impl Default for PortId { pub struct ChannelId(String); impl ChannelId { + /// Builds a new channel identifier. + pub fn new(counter: u64) -> Result { + let prefix = "channel"; + let id = format!("{}-{}", prefix, counter); + Self::from_str(id.as_str()) + } + /// Get this identifier as a borrowed `&str` pub fn as_str(&self) -> &str { &self.0 diff --git a/modules/src/mock/context.rs b/modules/src/mock/context.rs index c656363513..6bb829924e 100644 --- a/modules/src/mock/context.rs +++ b/modules/src/mock/context.rs @@ -88,10 +88,10 @@ pub struct MockContext { port_capabilities: HashMap, /// Counter for connection identifiers (see `next_connection_id`). - connection_ids_counter: u32, + connection_ids_counter: u64, /// Counter for channel identifiers (see `next_channel_id`). - channel_ids_counter: u32, + channel_ids_counter: u64, } /// Returns a MockContext with bare minimum initialization: no clients, no connections and no channels are @@ -392,11 +392,9 @@ impl ChannelReader for MockContext { impl ChannelKeeper for MockContext { fn next_channel_id(&mut self) -> ChannelId { - let prefix = ChannelId::default().to_string(); - let suffix = self.channel_ids_counter; + let counter = self.channel_ids_counter; self.channel_ids_counter += 1; - - ChannelId::from_str(format!("{}-{}", prefix, suffix).as_str()).unwrap() + ChannelId::new(counter).unwrap() } fn store_channel( @@ -497,11 +495,9 @@ impl ConnectionReader for MockContext { impl ConnectionKeeper for MockContext { fn next_connection_id(&mut self) -> ConnectionId { - let prefix = ConnectionId::default().to_string(); - let suffix = self.connection_ids_counter; + let counter = self.connection_ids_counter; self.connection_ids_counter += 1; - - ConnectionId::from_str(format!("{}-{}", prefix, suffix).as_str()).unwrap() + ConnectionId::new(counter).unwrap() } fn store_connection( diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index 3622e61599..7740f7d50d 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -10,23 +10,25 @@ use ibc::ics02_client::msgs::ClientMsg; use ibc::ics03_connection::connection::Counterparty; use ibc::ics03_connection::error::Kind as ICS03ErrorKind; use ibc::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; +use ibc::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; use ibc::ics03_connection::msgs::ConnectionMsg; use ibc::ics03_connection::version::Version; use ibc::ics18_relayer::error::{Error as ICS18Error, Kind as ICS18ErrorKind}; use ibc::ics23_commitment::commitment::CommitmentPrefix; -use ibc::ics24_host::identifier::ChainId; -use ibc::ics24_host::identifier::ClientId; +use ibc::ics23_commitment::commitment::CommitmentProofBytes; +use ibc::ics24_host::identifier::{ChainId, ClientId, ConnectionId}; 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::proofs::{ConsensusProof, Proofs}; use ibc::Height; use ibc::{ics02_client::client_def::AnyHeader, ics18_relayer::context::ICS18Context}; -use std::collections::HashMap; use std::error::Error; use std::fmt::{Debug, Display}; +use std::{collections::HashMap, vec}; use step::{ActionOutcome, ActionType, Chain, Step}; use tendermint::account::Id as AccountId; @@ -109,11 +111,20 @@ impl ICS02TestExecutor { Version::default() } + fn versions() -> Vec { + vec![Self::version()] + } + fn client_id(client_id: u64) -> ClientId { ClientId::new(ClientType::Mock, client_id) .expect("it should be possible to create the client identifier") } + fn connection_id(connection_id: u64) -> ConnectionId { + ConnectionId::new(connection_id) + .expect("it should be possible to create the connection identifier") + } + fn height(height: u64) -> Height { Height::new(Self::epoch(), height) } @@ -139,17 +150,47 @@ impl ICS02TestExecutor { } fn counterparty(counterparty_client_id: u64) -> Counterparty { - Counterparty::new( - Self::client_id(counterparty_client_id), - None, - CommitmentPrefix(Vec::new()), - ) + let client_id = Self::client_id(counterparty_client_id); + let connection_id = None; + let prefix = Self::commitment_prefix(); + Counterparty::new(client_id, connection_id, prefix) } fn delay_period() -> u64 { 0 } + fn commitment_prefix() -> CommitmentPrefix { + CommitmentPrefix(Vec::new()) + } + + fn commitment_proof_bytes() -> CommitmentProofBytes { + vec![0].into() + } + + fn consensus_proof(height: u64) -> ConsensusProof { + let consensus_proof = Self::commitment_proof_bytes(); + let consensus_height = Self::height(height); + ConsensusProof::new(consensus_proof, consensus_height) + .expect("it should be possible to create the consensus proof") + } + + fn proofs(height: u64) -> Proofs { + let object_proof = Self::commitment_proof_bytes(); + let client_proof = None; + let consensus_proof = Some(Self::consensus_proof(height)); + let other_proof = None; + let height = Self::height(height); + Proofs::new( + object_proof, + client_proof, + consensus_proof, + other_proof, + height, + ) + .expect("it should be possible to create the proofs") + } + /// Check that chain heights match the ones in the model. fn check_chain_heights(&self, chains: HashMap) -> bool { chains.into_iter().all(|(chain_id, chain)| { @@ -179,6 +220,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { } fn next_step(&mut self, step: Step) -> bool { + println!("{:?}", step); let outcome_matches = match step.action.action_type { ActionType::None => panic!("unexpected action type"), ActionType::ICS02CreateClient => { @@ -187,18 +229,18 @@ impl modelator::TestExecutor for ICS02TestExecutor { .action .chain_id .expect("create client action should have a chain identifier"); - let height = step + let client_height = step .action - .height - .expect("create client action should have a height"); + .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(height), - consensus_state: Self::consensus_state(height), + client_state: Self::client_state(client_height), + consensus_state: Self::consensus_state(client_height), signer: Self::signer(), })); let result = ctx.deliver(msg); @@ -222,10 +264,10 @@ impl modelator::TestExecutor for ICS02TestExecutor { .action .client_id .expect("update client action should have a client identifier"); - let height = step + let client_height = step .action - .height - .expect("update client action should have a height"); + .client_height + .expect("update client action should have a client height"); // get chain's context let ctx = self.chain_context_mut(&chain_id); @@ -233,7 +275,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { // create ICS26 message and deliver it let msg = ICS26Envelope::ICS2Msg(ClientMsg::UpdateClient(MsgUpdateAnyClient { client_id: Self::client_id(client_id), - header: Self::header(height), + header: Self::header(client_height), signer: Self::signer(), })); let result = ctx.deliver(msg); @@ -251,7 +293,8 @@ impl modelator::TestExecutor for ICS02TestExecutor { // error matching the expected outcome matches!( handler_error_kind, - ICS02ErrorKind::ClientNotFound(id) if id == Self::client_id(client_id) + ICS02ErrorKind::ClientNotFound(error_client_id) + if error_client_id == Self::client_id(client_id) ) } ActionOutcome::ICS02HeaderVerificationFailure => { @@ -306,9 +349,64 @@ impl modelator::TestExecutor for ICS02TestExecutor { // error matching the expected outcome matches!( handler_error_kind, - ICS03ErrorKind::MissingClient(id) if id == Self::client_id(client_id) + ICS03ErrorKind::MissingClient(error_client_id) + if error_client_id == Self::client_id(client_id) + ) + } + action => panic!("unexpected action outcome {:?}", action), + } + } + ActionType::ICS03ConnectionOpenTry => { + // get action parameters + let chain_id = step + .action + .chain_id + .expect("connection open init action should have a chain identifier"); + let client_id = step + .action + .client_id + .expect("connection open init action should have a client identifier"); + let client_height = step + .action + .client_height + .expect("connection open try action should have a client height"); + let counterparty_client_id = step.action.counterparty_client_id.expect( + "connection open init action should have a counterparty client identifier", + ); + let connection_id = step.action.connection_id; + + // get chain's context + let ctx = self.chain_context_mut(&chain_id); + + // create ICS26 message and deliver it + let msg = ICS26Envelope::ICS3Msg(ConnectionMsg::ConnectionOpenTry(Box::new( + MsgConnectionOpenTry { + previous_connection_id: connection_id.map(Self::connection_id), + client_id: Self::client_id(client_id), + client_state: None, + counterparty: Self::counterparty(counterparty_client_id), + counterparty_versions: Self::versions(), + proofs: Self::proofs(client_height), + delay_period: Self::delay_period(), + signer: Self::signer(), + }, + ))); + let result = ctx.deliver(msg); + + // check the expected outcome + match step.action_outcome { + ActionOutcome::ICS03InvalidConsensusHeight => { + let handler_error_kind = + Self::extract_handler_error_kind::(result); + // the implementaion matches the model if there's an + // error matching the expected outcome + matches!( + handler_error_kind, + ICS03ErrorKind::InvalidConsensusHeight(error_consensus_height, _) + if error_consensus_height == Self::height(client_height) ) } + action => panic!("unexpected action outcome {:?}", action), } } @@ -327,6 +425,8 @@ fn main() { "ICS02HeaderVerificationFailureTest", "ICS03ConnectionOpenInitOKTest", "ICS03MissingClientTest", + "ICS03MissingClientTest", + "ICS03InvalidConsensusHeightTest", ]; for test in tests { diff --git a/modules/tests/step.rs b/modules/tests/step.rs index eabf76d08f..85935ab09e 100644 --- a/modules/tests/step.rs +++ b/modules/tests/step.rs @@ -1,4 +1,4 @@ -use serde::Deserialize; +use serde::{Deserialize, Deserializer}; use std::collections::HashMap; use std::fmt::Debug; @@ -20,13 +20,33 @@ pub struct Action { #[serde(alias = "chainId")] pub chain_id: Option, - pub height: Option, - #[serde(alias = "clientId")] pub client_id: Option, + #[serde(alias = "clientHeight")] + pub client_height: Option, + #[serde(alias = "counterpartyClientId")] pub counterparty_client_id: Option, + + #[serde(alias = "connectionId")] + #[serde(default, deserialize_with = "deserialize_connection_id")] + pub connection_id: Option, +} + +/// On the model, a non-existing `connection_id` is represented with -1. +/// For this reason, this function maps a `Some(-1)` to a `None`. +fn deserialize_connection_id<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let connection_id: Option = Deserialize::deserialize(deserializer)?; + let connection_id = if connection_id == Some(-1) { + None + } else { + connection_id.map(|connection_id| connection_id as u64) + }; + Ok(connection_id) } #[derive(Debug, Clone, PartialEq, Deserialize)] @@ -35,6 +55,7 @@ pub enum ActionType { ICS02CreateClient, ICS02UpdateClient, ICS03ConnectionOpenInit, + ICS03ConnectionOpenTry, } #[derive(Debug, Clone, PartialEq, Deserialize)] @@ -46,6 +67,7 @@ pub enum ActionOutcome { ICS02HeaderVerificationFailure, ICS03ConnectionOpenInitOK, ICS03MissingClient, + ICS03InvalidConsensusHeight, } #[derive(Debug, Clone, PartialEq, Deserialize)] diff --git a/modules/tests/support/model_based/IBC.cfg b/modules/tests/support/model_based/IBC.cfg index 0f1dcc1985..75f117802a 100644 --- a/modules/tests/support/model_based/IBC.cfg +++ b/modules/tests/support/model_based/IBC.cfg @@ -1,6 +1,5 @@ CONSTANTS ChainIds = {"chain-A", "chain-B"} - MaxChainHeight = 4 MaxClientsPerChain = 2 MaxClientHeight = 2 MaxConnectionsPerChain = 2 diff --git a/modules/tests/support/model_based/IBCTests.cfg b/modules/tests/support/model_based/IBCTests.cfg index d5e07381b5..93d056dc99 100644 --- a/modules/tests/support/model_based/IBCTests.cfg +++ b/modules/tests/support/model_based/IBCTests.cfg @@ -1,6 +1,5 @@ CONSTANTS ChainIds = {"chain-A", "chain-B"} - MaxChainHeight = 4 MaxClientsPerChain = 2 MaxClientHeight = 2 MaxConnectionsPerChain = 2 @@ -12,6 +11,8 @@ INVARIANTS \* ICS02CreateOKTest \* ICS02UpdateOKTest \* ICS02ClientNotFoundTest - ICS02HeaderVerificationFailureTest + \* ICS02HeaderVerificationFailureTest \* ICS03ConnectionOpenInitOKTest - \* ICS03MissingClientTest \ No newline at end of file + \* ICS03MissingClientTest + \* ICS03ConnectionOpenTryOKTest + ICS03InvalidConsensusHeightTest \ No newline at end of file diff --git a/modules/tests/support/model_based/IBCTests.tla b/modules/tests/support/model_based/IBCTests.tla index 5165bf50b9..12ba4da0f2 100644 --- a/modules/tests/support/model_based/IBCTests.tla +++ b/modules/tests/support/model_based/IBCTests.tla @@ -20,11 +20,19 @@ ICS03ConnectionOpenInitOK == ICS03MissingClient == /\ actionOutcome = "ICS03MissingClient" +ICS03ConnectionOpenTryOK == + /\ actionOutcome = "ICS03ConnectionOpenTryOK" + +ICS03InvalidConsensusHeight == + /\ actionOutcome = "ICS03InvalidConsensusHeight" + ICS02CreateOKTest == ~ICS02CreateOK ICS02UpdateOKTest == ~ICS02UpdateOK ICS02ClientNotFoundTest == ~ICS02ClientNotFound ICS02HeaderVerificationFailureTest == ~ICS02HeaderVerificationFailure ICS03ConnectionOpenInitOKTest == ~ICS03ConnectionOpenInitOK ICS03MissingClientTest == ~ICS03MissingClient +ICS03ConnectionOpenTryOKTest == ~ICS03ConnectionOpenTryOK +ICS03InvalidConsensusHeightTest == ~ICS03InvalidConsensusHeight =============================================================================== \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json b/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json index 2fb55f7080..9947b27078 100644 --- a/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json +++ b/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json @@ -16,7 +16,7 @@ { "action": { "chainId": "chain-B", - "height": 1, + "clientHeight": 1, "type": "ICS02CreateClient" }, "actionOutcome": "ICS02CreateOK", @@ -33,7 +33,7 @@ "action": { "chainId": "chain-B", "clientId": 0, - "height": 1, + "clientHeight": 1, "type": "ICS02UpdateClient" }, "actionOutcome": "ICS02HeaderVerificationFailure", diff --git a/modules/tests/support/model_based/ICS02UpdateOKTest.json b/modules/tests/support/model_based/ICS02UpdateOKTest.json index 4fbcd5ec30..33e67f0967 100644 --- a/modules/tests/support/model_based/ICS02UpdateOKTest.json +++ b/modules/tests/support/model_based/ICS02UpdateOKTest.json @@ -16,7 +16,7 @@ { "action": { "chainId": "chain-B", - "height": 1, + "clientHeight": 1, "type": "ICS02CreateClient" }, "actionOutcome": "ICS02CreateOK", @@ -33,7 +33,7 @@ "action": { "chainId": "chain-B", "clientId": 0, - "height": 2, + "clientHeight": 2, "type": "ICS02UpdateClient" }, "actionOutcome": "ICS02UpdateOK", @@ -49,7 +49,7 @@ { "action": { "chainId": "chain-A", - "height": 1, + "clientHeight": 1, "type": "ICS02CreateClient" }, "actionOutcome": "ICS02CreateOK", @@ -66,7 +66,7 @@ "action": { "chainId": "chain-A", "clientId": 0, - "height": 2, + "clientHeight": 2, "type": "ICS02UpdateClient" }, "actionOutcome": "ICS02UpdateOK", @@ -82,7 +82,7 @@ { "action": { "chainId": "chain-A", - "height": 1, + "clientHeight": 1, "type": "ICS02CreateClient" }, "actionOutcome": "ICS02CreateOK", @@ -99,7 +99,7 @@ "action": { "chainId": "chain-A", "clientId": 1, - "height": 2, + "clientHeight": 2, "type": "ICS02UpdateClient" }, "actionOutcome": "ICS02UpdateOK", diff --git a/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json b/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json index 9a71e469e2..14fe580531 100644 --- a/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json +++ b/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json @@ -16,7 +16,7 @@ { "action": { "chainId": "chain-B", - "height": 1, + "clientHeight": 1, "type": "ICS02CreateClient" }, "actionOutcome": "ICS02CreateOK", diff --git a/modules/tests/support/model_based/ICS03InvalidConsensusHeightTest.json b/modules/tests/support/model_based/ICS03InvalidConsensusHeightTest.json new file mode 100644 index 0000000000..8e4a6553fb --- /dev/null +++ b/modules/tests/support/model_based/ICS03InvalidConsensusHeightTest.json @@ -0,0 +1,35 @@ +[ + { + "action": { + "type": "None" + }, + "actionOutcome": "None", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 0 + } + } + }, + { + "action": { + "chainId": "chain-A", + "clientId": 0, + "clientHeight": 1, + "connectionId": -1, + "counterpartyClientId": 0, + "type": "ICS03ConnectionOpenTry" + }, + "actionOutcome": "ICS03InvalidConsensusHeight", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 0 + } + } + } +] \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS03MissingClientTest.json b/modules/tests/support/model_based/ICS03MissingClientTest.json index 86e67b14d0..bfec803cd7 100644 --- a/modules/tests/support/model_based/ICS03MissingClientTest.json +++ b/modules/tests/support/model_based/ICS03MissingClientTest.json @@ -16,7 +16,7 @@ { "action": { "chainId": "chain-A", - "height": 1, + "clientHeight": 1, "type": "ICS02CreateClient" }, "actionOutcome": "ICS02CreateOK", @@ -32,7 +32,7 @@ { "action": { "chainId": "chain-A", - "height": 1, + "clientHeight": 1, "type": "ICS02CreateClient" }, "actionOutcome": "ICS02CreateOK", From 1cdb02a242ade2ed70537f65b17cdd9b8a969824 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Thu, 4 Feb 2021 19:51:10 +0100 Subject: [PATCH 31/52] Model missing connections and connection mismatches in conn open try --- modules/tests/model_based.rs | 27 ++++- modules/tests/step.rs | 2 + modules/tests/support/model_based/IBC.tla | 4 +- .../tests/support/model_based/IBCTests.cfg | 4 +- .../tests/support/model_based/IBCTests.tla | 8 ++ modules/tests/support/model_based/ICS03.tla | 106 ++++++++++++++++-- .../ICS03ConnectionMismatchTest.json | 68 +++++++++++ .../ICS03ConnectionNotFoundTest.json | 51 +++++++++ 8 files changed, 257 insertions(+), 13 deletions(-) create mode 100644 modules/tests/support/model_based/ICS03ConnectionMismatchTest.json create mode 100644 modules/tests/support/model_based/ICS03ConnectionNotFoundTest.json diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index 7740f7d50d..368fae334c 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -406,7 +406,30 @@ impl modelator::TestExecutor for ICS02TestExecutor { if error_consensus_height == Self::height(client_height) ) } - + ActionOutcome::ICS03ConnectionNotFound => { + let handler_error_kind = + Self::extract_handler_error_kind::(result); + // the implementaion matches the model if there's an + // error matching the expected outcome + connection_id.is_some() + && matches!( + handler_error_kind, + ICS03ErrorKind::ConnectionNotFound(error_connection_id) + if error_connection_id == Self::connection_id(connection_id.unwrap()) + ) + } + ActionOutcome::ICS03ConnectionMismatch => { + let handler_error_kind = + Self::extract_handler_error_kind::(result); + // the implementaion matches the model if there's an + // error matching the expected outcome + connection_id.is_some() + && matches!( + handler_error_kind, + ICS03ErrorKind::ConnectionMismatch(error_connection_id) + if error_connection_id == Self::connection_id(connection_id.unwrap()) + ) + } action => panic!("unexpected action outcome {:?}", action), } } @@ -427,6 +450,8 @@ fn main() { "ICS03MissingClientTest", "ICS03MissingClientTest", "ICS03InvalidConsensusHeightTest", + "ICS03ConnectionNotFoundTest", + "ICS03ConnectionMismatchTest", ]; for test in tests { diff --git a/modules/tests/step.rs b/modules/tests/step.rs index 85935ab09e..2b54c109e0 100644 --- a/modules/tests/step.rs +++ b/modules/tests/step.rs @@ -68,6 +68,8 @@ pub enum ActionOutcome { ICS03ConnectionOpenInitOK, ICS03MissingClient, ICS03InvalidConsensusHeight, + ICS03ConnectionNotFound, + ICS03ConnectionMismatch, } #[derive(Debug, Clone, PartialEq, Deserialize)] diff --git a/modules/tests/support/model_based/IBC.tla b/modules/tests/support/model_based/IBC.tla index dcc21256fb..0f6676e8a1 100644 --- a/modules/tests/support/model_based/IBC.tla +++ b/modules/tests/support/model_based/IBC.tla @@ -137,7 +137,9 @@ ActionOutcomes == { "ICS03MissingClient", \* ICS03_ConnectionOpenTry outcomes: "ICS03ConnectionOpenTryOK", - "ICS03InvalidConsensusHeight" + "ICS03InvalidConsensusHeight", + "ICS03ConnectionNotFound", + "ICS03ConnectionMismatch" } (***************************** Specification *********************************) diff --git a/modules/tests/support/model_based/IBCTests.cfg b/modules/tests/support/model_based/IBCTests.cfg index 93d056dc99..9f501b448a 100644 --- a/modules/tests/support/model_based/IBCTests.cfg +++ b/modules/tests/support/model_based/IBCTests.cfg @@ -15,4 +15,6 @@ INVARIANTS \* ICS03ConnectionOpenInitOKTest \* ICS03MissingClientTest \* ICS03ConnectionOpenTryOKTest - ICS03InvalidConsensusHeightTest \ No newline at end of file + \* ICS03InvalidConsensusHeightTest + \* ICS03ConnectionNotFoundTest + ICS03ConnectionMismatchTest \ No newline at end of file diff --git a/modules/tests/support/model_based/IBCTests.tla b/modules/tests/support/model_based/IBCTests.tla index 12ba4da0f2..0d7e352217 100644 --- a/modules/tests/support/model_based/IBCTests.tla +++ b/modules/tests/support/model_based/IBCTests.tla @@ -26,6 +26,12 @@ ICS03ConnectionOpenTryOK == ICS03InvalidConsensusHeight == /\ actionOutcome = "ICS03InvalidConsensusHeight" +ICS03ConnectionNotFound == + /\ actionOutcome = "ICS03ConnectionNotFound" + +ICS03ConnectionMismatch == + /\ actionOutcome = "ICS03ConnectionMismatch" + ICS02CreateOKTest == ~ICS02CreateOK ICS02UpdateOKTest == ~ICS02UpdateOK ICS02ClientNotFoundTest == ~ICS02ClientNotFound @@ -34,5 +40,7 @@ ICS03ConnectionOpenInitOKTest == ~ICS03ConnectionOpenInitOK ICS03MissingClientTest == ~ICS03MissingClient ICS03ConnectionOpenTryOKTest == ~ICS03ConnectionOpenTryOK ICS03InvalidConsensusHeightTest == ~ICS03InvalidConsensusHeight +ICS03ConnectionNotFoundTest == ~ICS03ConnectionNotFound +ICS03ConnectionMismatchTest == ~ICS03ConnectionMismatch =============================================================================== \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS03.tla b/modules/tests/support/model_based/ICS03.tla index c3d1d675bb..04fd29863b 100644 --- a/modules/tests/support/model_based/ICS03.tla +++ b/modules/tests/support/model_based/ICS03.tla @@ -60,29 +60,115 @@ ICS03_ConnectionOpenInit( outcome |-> "ICS03MissingClient" ] -ICS03_ConnectionOpenTry( - height, +ICS03_ConnectionOpenTry_1( + chainHeight, + clients, + connections, + connectionIdCounter, + clientId, + clientClaimedHeight, + counterpartyClientId, + connectionId +) == + \* TODO check that all parameters are still needed + [ + connections |-> connections, + connectionIdCounter |-> connectionIdCounter, + outcome |-> "ICS03ConnectionOpenTryOK" + ] + +ICS03_ConnectionOpenTry_0( + chainHeight, clients, connections, connectionIdCounter, clientId, - clientHeight, + clientClaimedHeight, counterpartyClientId, connectionId ) == - \* check if client's claimed height - IF clientHeight > height THEN + \* check if client's claimed height is higher than the chain's height + IF clientClaimedHeight > chainHeight THEN \* if client's height is too advanced, then set an error outcome [ connections |-> connections, connectionIdCounter |-> connectionIdCounter, outcome |-> "ICS03InvalidConsensusHeight" ] + \* TODO: add `chain_max_history_size` to the model to be able to also + \* return a `ICS03StaleConsensusHeight` error outcome ELSE - [ - connections |-> connections, - connectionIdCounter |-> connectionIdCounter, - outcome |-> "ICS03ConnectionOpenTryOK" - ] + \* check if a `connectionId` was set + IF connectionId /= ConnectionIdNone THEN + \* if so, check if the connection exists + IF ICS03_ConnectionExists(connections, connectionId) THEN + \* if the connection exists, verify that is matches the + \* the parameters provided + LET connection == ICS03_GetConnection( + connections, + connectionId + ) IN + IF /\ connection.state = "Init" + /\ connection.clientId = clientId + /\ connection.counterpartyClientId = counterpartyClientId + THEN + \* initial verification passed; move to step 1 + ICS03_ConnectionOpenTry_1( + chainHeight, + clients, + connections, + connectionIdCounter, + clientId, + clientClaimedHeight, + counterpartyClientId, + connectionId + ) + ELSE + [ + connections |-> connections, + connectionIdCounter |-> connectionIdCounter, + outcome |-> "ICS03ConnectionMismatch" + ] + ELSE + \* if the connection does not exist, then set an error outcome + [ + connections |-> connections, + connectionIdCounter |-> connectionIdCounter, + outcome |-> "ICS03ConnectionNotFound" + ] + ELSE + \* initial verification passed; move to step 1 + ICS03_ConnectionOpenTry_1( + chainHeight, + clients, + connections, + connectionIdCounter, + clientId, + clientClaimedHeight, + counterpartyClientId, + connectionId + ) + +ICS03_ConnectionOpenTry( + chainHeight, + clients, + connections, + connectionIdCounter, + clientId, + clientClaimedHeight, + counterpartyClientId, + connectionId +) == + \* start step 0 + ICS03_ConnectionOpenTry_0( + chainHeight, + clients, + connections, + connectionIdCounter, + clientId, + clientClaimedHeight, + counterpartyClientId, + connectionId + ) =============================================================================== \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS03ConnectionMismatchTest.json b/modules/tests/support/model_based/ICS03ConnectionMismatchTest.json new file mode 100644 index 0000000000..41a6394b0a --- /dev/null +++ b/modules/tests/support/model_based/ICS03ConnectionMismatchTest.json @@ -0,0 +1,68 @@ +[ + { + "action": { + "type": "None" + }, + "actionOutcome": "None", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 0 + } + } + }, + { + "action": { + "chainId": "chain-B", + "clientHeight": 1, + "type": "ICS02CreateClient" + }, + "actionOutcome": "ICS02CreateOK", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 1 + } + } + }, + { + "action": { + "chainId": "chain-B", + "clientId": 0, + "counterpartyClientId": 1, + "type": "ICS03ConnectionOpenInit" + }, + "actionOutcome": "ICS03ConnectionOpenInitOK", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 2 + } + } + }, + { + "action": { + "chainId": "chain-B", + "clientId": 1, + "clientHeight": 2, + "connectionId": 0, + "counterpartyClientId": 0, + "type": "ICS03ConnectionOpenTry" + }, + "actionOutcome": "ICS03ConnectionMismatch", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 2 + } + } + } +] \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS03ConnectionNotFoundTest.json b/modules/tests/support/model_based/ICS03ConnectionNotFoundTest.json new file mode 100644 index 0000000000..7d8ea34d92 --- /dev/null +++ b/modules/tests/support/model_based/ICS03ConnectionNotFoundTest.json @@ -0,0 +1,51 @@ +[ + { + "action": { + "type": "None" + }, + "actionOutcome": "None", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 0 + } + } + }, + { + "action": { + "chainId": "chain-B", + "clientHeight": 1, + "type": "ICS02CreateClient" + }, + "actionOutcome": "ICS02CreateOK", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 1 + } + } + }, + { + "action": { + "chainId": "chain-B", + "clientId": 0, + "clientHeight": 1, + "connectionId": 0, + "counterpartyClientId": 0, + "type": "ICS03ConnectionOpenTry" + }, + "actionOutcome": "ICS03ConnectionNotFound", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 1 + } + } + } +] \ No newline at end of file From 0186a1c2da296f5cc2f01983b5a0469c00f34e38 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Thu, 4 Feb 2021 22:01:48 +0100 Subject: [PATCH 32/52] Trigger bug in conn open try --- modules/tests/TODO | 3 + modules/tests/model_based.rs | 41 ++++--- modules/tests/step.rs | 5 + modules/tests/support/model_based/IBC.cfg | 2 +- modules/tests/support/model_based/IBC.tla | 33 ++++-- .../tests/support/model_based/IBCTests.cfg | 6 +- .../tests/support/model_based/IBCTests.tla | 2 +- modules/tests/support/model_based/ICS02.tla | 4 +- .../ICS02HeaderVerificationFailureTest.json | 2 +- .../model_based/ICS02UpdateOKTest.json | 6 +- modules/tests/support/model_based/ICS03.tla | 112 ++++++++---------- .../ICS03ConnectionMismatchTest.json | 4 +- .../ICS03ConnectionNotFoundTest.json | 4 +- .../ICS03ConnectionOpenInitOKTest.json | 2 +- .../ICS03ConnectionOpenTryOKTest.json | 52 ++++++++ .../ICS03InvalidConsensusHeightTest.json | 2 +- .../model_based/ICS03MissingClientTest.json | 2 +- 17 files changed, 173 insertions(+), 109 deletions(-) create mode 100644 modules/tests/TODO create mode 100644 modules/tests/support/model_based/ICS03ConnectionOpenTryOKTest.json diff --git a/modules/tests/TODO b/modules/tests/TODO new file mode 100644 index 0000000000..b86a736905 --- /dev/null +++ b/modules/tests/TODO @@ -0,0 +1,3 @@ +- conn open try does not increment the connection id counter; looks like a bug +- the model allows a conn open try to succeed without a conn open try (likely because we don't have proofs) +- TLC doesn't finish with `MaxClientsPerChain = 2` diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index 368fae334c..9d2566345c 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -1,7 +1,7 @@ mod modelator; mod step; -use ibc::ics02_client::client_def::{AnyClientState, AnyConsensusState}; +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; @@ -13,9 +13,9 @@ use ibc::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; use ibc::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; use ibc::ics03_connection::msgs::ConnectionMsg; use ibc::ics03_connection::version::Version; +use ibc::ics18_relayer::context::ICS18Context; use ibc::ics18_relayer::error::{Error as ICS18Error, Kind as ICS18ErrorKind}; -use ibc::ics23_commitment::commitment::CommitmentPrefix; -use ibc::ics23_commitment::commitment::CommitmentProofBytes; +use ibc::ics23_commitment::commitment::{CommitmentPrefix, CommitmentProofBytes}; use ibc::ics24_host::identifier::{ChainId, ClientId, ConnectionId}; use ibc::ics26_routing::error::{Error as ICS26Error, Kind as ICS26ErrorKind}; use ibc::ics26_routing::msgs::ICS26Envelope; @@ -25,10 +25,9 @@ use ibc::mock::header::MockHeader; use ibc::mock::host::HostType; use ibc::proofs::{ConsensusProof, Proofs}; use ibc::Height; -use ibc::{ics02_client::client_def::AnyHeader, ics18_relayer::context::ICS18Context}; +use std::collections::HashMap; use std::error::Error; use std::fmt::{Debug, Display}; -use std::{collections::HashMap, vec}; use step::{ActionOutcome, ActionType, Chain, Step}; use tendermint::account::Id as AccountId; @@ -149,9 +148,9 @@ impl ICS02TestExecutor { AccountId::new([0; 20]) } - fn counterparty(counterparty_client_id: u64) -> Counterparty { - let client_id = Self::client_id(counterparty_client_id); - let connection_id = None; + fn counterparty(client_id: u64, connection_id: Option) -> Counterparty { + let client_id = Self::client_id(client_id); + let connection_id = connection_id.map(|connection_id| Self::connection_id(connection_id)); let prefix = Self::commitment_prefix(); Counterparty::new(client_id, connection_id, prefix) } @@ -161,7 +160,7 @@ impl ICS02TestExecutor { } fn commitment_prefix() -> CommitmentPrefix { - CommitmentPrefix(Vec::new()) + vec![0].into() } fn commitment_proof_bytes() -> CommitmentProofBytes { @@ -328,7 +327,7 @@ impl modelator::TestExecutor for ICS02TestExecutor { let msg = ICS26Envelope::ICS3Msg(ConnectionMsg::ConnectionOpenInit( MsgConnectionOpenInit { client_id: Self::client_id(client_id), - counterparty: Self::counterparty(counterparty_client_id), + counterparty: Self::counterparty(counterparty_client_id, None), version: Self::version(), delay_period: Self::delay_period(), signer: Self::signer(), @@ -361,19 +360,22 @@ impl modelator::TestExecutor for ICS02TestExecutor { let chain_id = step .action .chain_id - .expect("connection open init action should have a chain identifier"); + .expect("connection open try action should have a chain identifier"); let client_id = step .action .client_id - .expect("connection open init action should have a client identifier"); + .expect("connection open try action should have a client identifier"); let client_height = step .action .client_height .expect("connection open try action should have a client height"); let counterparty_client_id = step.action.counterparty_client_id.expect( - "connection open init action should have a counterparty client identifier", + "connection open try action should have a counterparty client identifier", ); let connection_id = step.action.connection_id; + let counterparty_connection_id = step.action.counterparty_connection_id.expect( + "connection open try action should have a counterparty connection identifier", + ); // get chain's context let ctx = self.chain_context_mut(&chain_id); @@ -384,17 +386,24 @@ impl modelator::TestExecutor for ICS02TestExecutor { previous_connection_id: connection_id.map(Self::connection_id), client_id: Self::client_id(client_id), client_state: None, - counterparty: Self::counterparty(counterparty_client_id), + counterparty: Self::counterparty( + counterparty_client_id, + Some(counterparty_connection_id), + ), counterparty_versions: Self::versions(), proofs: Self::proofs(client_height), delay_period: Self::delay_period(), signer: Self::signer(), }, ))); - let result = ctx.deliver(msg); + let result = dbg!(ctx.deliver(msg)); // check the expected outcome match step.action_outcome { + ActionOutcome::ICS03ConnectionOpenTryOK => { + // the implementaion matches the model if no error occurs + result.is_ok() + } ActionOutcome::ICS03InvalidConsensusHeight => { let handler_error_kind = Self::extract_handler_error_kind::(result); @@ -448,7 +457,7 @@ fn main() { "ICS02HeaderVerificationFailureTest", "ICS03ConnectionOpenInitOKTest", "ICS03MissingClientTest", - "ICS03MissingClientTest", + "ICS03ConnectionOpenTryOKTest", "ICS03InvalidConsensusHeightTest", "ICS03ConnectionNotFoundTest", "ICS03ConnectionMismatchTest", diff --git a/modules/tests/step.rs b/modules/tests/step.rs index 2b54c109e0..67f97882bc 100644 --- a/modules/tests/step.rs +++ b/modules/tests/step.rs @@ -32,6 +32,10 @@ pub struct Action { #[serde(alias = "connectionId")] #[serde(default, deserialize_with = "deserialize_connection_id")] pub connection_id: Option, + + #[serde(alias = "counterpartyConnectionId")] + #[serde(default, deserialize_with = "deserialize_connection_id")] + pub counterparty_connection_id: Option, } /// On the model, a non-existing `connection_id` is represented with -1. @@ -67,6 +71,7 @@ pub enum ActionOutcome { ICS02HeaderVerificationFailure, ICS03ConnectionOpenInitOK, ICS03MissingClient, + ICS03ConnectionOpenTryOK, ICS03InvalidConsensusHeight, ICS03ConnectionNotFound, ICS03ConnectionMismatch, diff --git a/modules/tests/support/model_based/IBC.cfg b/modules/tests/support/model_based/IBC.cfg index 75f117802a..25f40c9106 100644 --- a/modules/tests/support/model_based/IBC.cfg +++ b/modules/tests/support/model_based/IBC.cfg @@ -1,6 +1,6 @@ CONSTANTS ChainIds = {"chain-A", "chain-B"} - MaxClientsPerChain = 2 + MaxClientsPerChain = 1 MaxClientHeight = 2 MaxConnectionsPerChain = 2 diff --git a/modules/tests/support/model_based/IBC.tla b/modules/tests/support/model_based/IBC.tla index 0f6676e8a1..89ddfe6de2 100644 --- a/modules/tests/support/model_based/IBC.tla +++ b/modules/tests/support/model_based/IBC.tla @@ -31,8 +31,9 @@ ActionType == [ chainId |-> STRING, clientHeight |-> Int, clientId |-> Int, + connectionId |-> Int, counterpartyClientId |-> Int, - connectionId |-> Int + counterpartyConnectionId |-> Int ] AsAction(a) == a <: ActionType (******************* END OF TYPE ANNOTATIONS FOR APALACHE ********************) @@ -98,8 +99,8 @@ CreateClientActions == [ UpdateClientActions == [ type: {"ICS02UpdateClient"}, chainId: ChainIds, - clientId: ClientIds, - clientHeight: ClientHeights + clientHeight: ClientHeights, + clientId: ClientIds ] <: {ActionType} ConnectionOpenInitActions == [ type: {"ICS03ConnectionOpenInit"}, @@ -110,10 +111,11 @@ ConnectionOpenInitActions == [ ConnectionOpenTryActions == [ type: {"ICS03ConnectionOpenTry"}, chainId: ChainIds, - clientId: ClientIds, clientHeight: ClientHeights, + clientId: ClientIds, counterpartyClientId: ClientIds, - connectionId: ConnectionIds \union {ConnectionIdNone} + connectionId: ConnectionIds \union {ConnectionIdNone}, + counterpartyConnectionId: ConnectionIds ] <: {ActionType} Actions == NoneActions \union @@ -216,8 +218,9 @@ ConnectionOpenTry( chainId, clientId, clientHeight, + connectionId, counterpartyClientId, - connectionId + counterpartyConnectionId ) == LET chain == chains[chainId] IN LET height == chain.height IN @@ -231,8 +234,9 @@ ConnectionOpenTry( connectionIdCounter, clientId, clientHeight, + connectionId, counterpartyClientId, - connectionId + counterpartyConnectionId ) IN \* update the chain LET updatedChain == [chain EXCEPT @@ -247,8 +251,9 @@ ConnectionOpenTry( chainId |-> chainId, clientId |-> clientId, clientHeight |-> clientHeight, + connectionId |-> connectionId, counterpartyClientId |-> counterpartyClientId, - connectionId |-> connectionId]) + counterpartyConnectionId |-> counterpartyConnectionId]) /\ actionOutcome' = result.outcome CreateClientAction == @@ -293,10 +298,12 @@ ConnectionOpenTryAction == \E clientId \in ClientIds: \* select a claimed height for the client \E clientHeight \in ClientHeights: - \* select a counterparty client id - \E counterpartyClientId \in ClientIds: \* select a connection id (which can be none) \E connectionId \in ConnectionIds \union {ConnectionIdNone}: + \* select a counterparty client id + \E counterpartyClientId \in ClientIds: + \* select a counterparty connection id + \E counterpartyConnectionId \in ConnectionIds: IF connectionId = ConnectionIdNone THEN \* in this case we're trying to create a new connection; only create \* connection if the model constant `MaxConnectionsPerChain` allows @@ -306,8 +313,9 @@ ConnectionOpenTryAction == chainId, clientId, clientHeight, + connectionId, counterpartyClientId, - connectionId + counterpartyConnectionId ) ELSE UNCHANGED vars @@ -316,8 +324,9 @@ ConnectionOpenTryAction == chainId, clientId, clientHeight, + connectionId, counterpartyClientId, - connectionId + counterpartyConnectionId ) Init == diff --git a/modules/tests/support/model_based/IBCTests.cfg b/modules/tests/support/model_based/IBCTests.cfg index 9f501b448a..4090d7a7f4 100644 --- a/modules/tests/support/model_based/IBCTests.cfg +++ b/modules/tests/support/model_based/IBCTests.cfg @@ -1,6 +1,6 @@ CONSTANTS ChainIds = {"chain-A", "chain-B"} - MaxClientsPerChain = 2 + MaxClientsPerChain = 1 MaxClientHeight = 2 MaxConnectionsPerChain = 2 @@ -14,7 +14,7 @@ INVARIANTS \* ICS02HeaderVerificationFailureTest \* ICS03ConnectionOpenInitOKTest \* ICS03MissingClientTest - \* ICS03ConnectionOpenTryOKTest + ICS03ConnectionOpenTryOKTest \* ICS03InvalidConsensusHeightTest \* ICS03ConnectionNotFoundTest - ICS03ConnectionMismatchTest \ No newline at end of file + \* ICS03ConnectionMismatchTest diff --git a/modules/tests/support/model_based/IBCTests.tla b/modules/tests/support/model_based/IBCTests.tla index 0d7e352217..35127df6ae 100644 --- a/modules/tests/support/model_based/IBCTests.tla +++ b/modules/tests/support/model_based/IBCTests.tla @@ -43,4 +43,4 @@ ICS03InvalidConsensusHeightTest == ~ICS03InvalidConsensusHeight ICS03ConnectionNotFoundTest == ~ICS03ConnectionNotFound ICS03ConnectionMismatchTest == ~ICS03ConnectionMismatch -=============================================================================== \ No newline at end of file +=============================================================================== diff --git a/modules/tests/support/model_based/ICS02.tla b/modules/tests/support/model_based/ICS02.tla index 82d788d89e..84fa0c4727 100644 --- a/modules/tests/support/model_based/ICS02.tla +++ b/modules/tests/support/model_based/ICS02.tla @@ -65,5 +65,7 @@ ICS02_UpdateClient(clients, clientId, clientHeight) == clients |-> clients, outcome |-> "ICS02ClientNotFound" ] + \* TODO: distinguish between client state and consensus state to also be + \* able to return a `ConsensusStateNotFound` error outcome -=============================================================================== \ No newline at end of file +=============================================================================== diff --git a/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json b/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json index 9947b27078..dbad8fc3ba 100644 --- a/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json +++ b/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json @@ -32,8 +32,8 @@ { "action": { "chainId": "chain-B", - "clientId": 0, "clientHeight": 1, + "clientId": 0, "type": "ICS02UpdateClient" }, "actionOutcome": "ICS02HeaderVerificationFailure", diff --git a/modules/tests/support/model_based/ICS02UpdateOKTest.json b/modules/tests/support/model_based/ICS02UpdateOKTest.json index 33e67f0967..309d6aa953 100644 --- a/modules/tests/support/model_based/ICS02UpdateOKTest.json +++ b/modules/tests/support/model_based/ICS02UpdateOKTest.json @@ -32,8 +32,8 @@ { "action": { "chainId": "chain-B", - "clientId": 0, "clientHeight": 2, + "clientId": 0, "type": "ICS02UpdateClient" }, "actionOutcome": "ICS02UpdateOK", @@ -65,8 +65,8 @@ { "action": { "chainId": "chain-A", - "clientId": 0, "clientHeight": 2, + "clientId": 0, "type": "ICS02UpdateClient" }, "actionOutcome": "ICS02UpdateOK", @@ -98,8 +98,8 @@ { "action": { "chainId": "chain-A", - "clientId": 1, "clientHeight": 2, + "clientId": 1, "type": "ICS02UpdateClient" }, "actionOutcome": "ICS02UpdateOK", diff --git a/modules/tests/support/model_based/ICS03.tla b/modules/tests/support/model_based/ICS03.tla index 04fd29863b..e9b3096a7f 100644 --- a/modules/tests/support/model_based/ICS03.tla +++ b/modules/tests/support/model_based/ICS03.tla @@ -60,32 +60,18 @@ ICS03_ConnectionOpenInit( outcome |-> "ICS03MissingClient" ] -ICS03_ConnectionOpenTry_1( - chainHeight, - clients, - connections, - connectionIdCounter, - clientId, - clientClaimedHeight, - counterpartyClientId, - connectionId -) == - \* TODO check that all parameters are still needed - [ - connections |-> connections, - connectionIdCounter |-> connectionIdCounter, - outcome |-> "ICS03ConnectionOpenTryOK" - ] - -ICS03_ConnectionOpenTry_0( +\* TODO: errors generated when verifying proofs are never an outcome of this +\* model +ICS03_ConnectionOpenTry( chainHeight, clients, connections, connectionIdCounter, clientId, clientClaimedHeight, + connectionId, counterpartyClientId, - connectionId + counterpartyConnectionId ) == \* check if client's claimed height is higher than the chain's height IF clientClaimedHeight > chainHeight THEN @@ -112,17 +98,27 @@ ICS03_ConnectionOpenTry_0( /\ connection.clientId = clientId /\ connection.counterpartyClientId = counterpartyClientId THEN - \* initial verification passed; move to step 1 - ICS03_ConnectionOpenTry_1( - chainHeight, - clients, - connections, - connectionIdCounter, - clientId, - clientClaimedHeight, - counterpartyClientId, - connectionId - ) + \* verification passed; update connection + LET updatedConnection == [ + state |-> "TryOpen", + clientId |-> clientId, + connectionId |-> connectionId, + counterpartyClientId |-> counterpartyClientId, + counterpartyConnectionId |-> counterpartyConnectionId + ] IN + \* return result with updated state + [ + connections |-> ICS03_SetConnection( + connections, + connectionId, + updatedConnection + ), + \* as the connection identifier has already been + \* created, here we do not update the + \* `connectionIdCounter` + connectionIdCounter |-> connectionIdCounter, + outcome |-> "ICS03ConnectionOpenTryOK" + ] ELSE [ connections |-> connections, @@ -137,38 +133,26 @@ ICS03_ConnectionOpenTry_0( outcome |-> "ICS03ConnectionNotFound" ] ELSE - \* initial verification passed; move to step 1 - ICS03_ConnectionOpenTry_1( - chainHeight, - clients, - connections, - connectionIdCounter, - clientId, - clientClaimedHeight, - counterpartyClientId, - connectionId - ) - -ICS03_ConnectionOpenTry( - chainHeight, - clients, - connections, - connectionIdCounter, - clientId, - clientClaimedHeight, - counterpartyClientId, - connectionId -) == - \* start step 0 - ICS03_ConnectionOpenTry_0( - chainHeight, - clients, - connections, - connectionIdCounter, - clientId, - clientClaimedHeight, - counterpartyClientId, - connectionId - ) + \* verification passed; create connection + LET connection == [ + state |-> "TryOpen", + clientId |-> clientId, + \* generate a new connection identifier + connectionId |-> connectionIdCounter, + counterpartyClientId |-> counterpartyClientId, + counterpartyConnectionId |-> counterpartyConnectionId + ] IN + \* return result with updated state + [ + connections |-> ICS03_SetConnection( + connections, + connectionIdCounter, + connection + ), + \* since a new connection identifier has been created, here we + \* update the `connectionIdCounter` + connectionIdCounter |-> connectionIdCounter + 1, + outcome |-> "ICS03ConnectionOpenTryOK" + ] -=============================================================================== \ No newline at end of file +=============================================================================== diff --git a/modules/tests/support/model_based/ICS03ConnectionMismatchTest.json b/modules/tests/support/model_based/ICS03ConnectionMismatchTest.json index 41a6394b0a..6bed88ced0 100644 --- a/modules/tests/support/model_based/ICS03ConnectionMismatchTest.json +++ b/modules/tests/support/model_based/ICS03ConnectionMismatchTest.json @@ -49,8 +49,8 @@ { "action": { "chainId": "chain-B", - "clientId": 1, "clientHeight": 2, + "clientId": 1, "connectionId": 0, "counterpartyClientId": 0, "type": "ICS03ConnectionOpenTry" @@ -65,4 +65,4 @@ } } } -] \ No newline at end of file +] diff --git a/modules/tests/support/model_based/ICS03ConnectionNotFoundTest.json b/modules/tests/support/model_based/ICS03ConnectionNotFoundTest.json index 7d8ea34d92..4f1da1bc44 100644 --- a/modules/tests/support/model_based/ICS03ConnectionNotFoundTest.json +++ b/modules/tests/support/model_based/ICS03ConnectionNotFoundTest.json @@ -32,8 +32,8 @@ { "action": { "chainId": "chain-B", - "clientId": 0, "clientHeight": 1, + "clientId": 0, "connectionId": 0, "counterpartyClientId": 0, "type": "ICS03ConnectionOpenTry" @@ -48,4 +48,4 @@ } } } -] \ No newline at end of file +] diff --git a/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json b/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json index 14fe580531..6e3d67ed6b 100644 --- a/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json +++ b/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json @@ -46,4 +46,4 @@ } } } -] \ No newline at end of file +] diff --git a/modules/tests/support/model_based/ICS03ConnectionOpenTryOKTest.json b/modules/tests/support/model_based/ICS03ConnectionOpenTryOKTest.json new file mode 100644 index 0000000000..2c2655da7b --- /dev/null +++ b/modules/tests/support/model_based/ICS03ConnectionOpenTryOKTest.json @@ -0,0 +1,52 @@ +[ + { + "action": { + "type": "None" + }, + "actionOutcome": "None", + "chains": { + "chain-A": { + "height": 0 + }, + "chain-B": { + "height": 0 + } + } + }, + { + "action": { + "chainId": "chain-A", + "clientHeight": 1, + "type": "ICS02CreateClient" + }, + "actionOutcome": "ICS02CreateOK", + "chains": { + "chain-A": { + "height": 1 + }, + "chain-B": { + "height": 0 + } + } + }, + { + "action": { + "chainId": "chain-A", + "clientHeight": 1, + "clientId": 0, + "connectionId": -1, + "counterpartyClientId": 0, + "counterpartyConnectionId": 0, + "type": "ICS03ConnectionOpenTry" + }, + "actionOutcome": "ICS03ConnectionOpenTryOK", + "chains": { + "chain-A": { + "height": 2 + }, + "chain-B": { + "height": 0 + } + } + } +] \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS03InvalidConsensusHeightTest.json b/modules/tests/support/model_based/ICS03InvalidConsensusHeightTest.json index 8e4a6553fb..b87a592328 100644 --- a/modules/tests/support/model_based/ICS03InvalidConsensusHeightTest.json +++ b/modules/tests/support/model_based/ICS03InvalidConsensusHeightTest.json @@ -32,4 +32,4 @@ } } } -] \ No newline at end of file +] diff --git a/modules/tests/support/model_based/ICS03MissingClientTest.json b/modules/tests/support/model_based/ICS03MissingClientTest.json index bfec803cd7..9200e16399 100644 --- a/modules/tests/support/model_based/ICS03MissingClientTest.json +++ b/modules/tests/support/model_based/ICS03MissingClientTest.json @@ -62,4 +62,4 @@ } } } -] \ No newline at end of file +] From 6b6cd43f92a3fc521dc1eb5e5ded4f41b3552a13 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Thu, 4 Feb 2021 22:33:57 +0100 Subject: [PATCH 33/52] Go back to previous way of generating connection and channel ids --- modules/src/ics24_host/identifier.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/src/ics24_host/identifier.rs b/modules/src/ics24_host/identifier.rs index 965d04038b..293461bda1 100644 --- a/modules/src/ics24_host/identifier.rs +++ b/modules/src/ics24_host/identifier.rs @@ -199,7 +199,7 @@ pub struct ConnectionId(String); impl ConnectionId { /// Builds a new connection identifier. pub fn new(counter: u64) -> Result { - let prefix = "connection"; + let prefix = ConnectionId::default().to_string(); let id = format!("{}-{}", prefix, counter); Self::from_str(id.as_str()) } @@ -292,7 +292,7 @@ pub struct ChannelId(String); impl ChannelId { /// Builds a new channel identifier. pub fn new(counter: u64) -> Result { - let prefix = "channel"; + let prefix = ConnectionId::default().to_string(); let id = format!("{}-{}", prefix, counter); Self::from_str(id.as_str()) } From 6186bec661df3f6acac78ec6339da50620a3fdd4 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Thu, 4 Feb 2021 22:40:54 +0100 Subject: [PATCH 34/52] Disable failing MBT test --- modules/tests/model_based.rs | 2 +- .../tests/support/model_based/ICS03ConnectionMismatchTest.json | 3 ++- .../tests/support/model_based/ICS03ConnectionNotFoundTest.json | 3 ++- .../support/model_based/ICS03InvalidConsensusHeightTest.json | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index 9d2566345c..b2c6061982 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -457,7 +457,7 @@ fn main() { "ICS02HeaderVerificationFailureTest", "ICS03ConnectionOpenInitOKTest", "ICS03MissingClientTest", - "ICS03ConnectionOpenTryOKTest", + // "ICS03ConnectionOpenTryOKTest", "ICS03InvalidConsensusHeightTest", "ICS03ConnectionNotFoundTest", "ICS03ConnectionMismatchTest", diff --git a/modules/tests/support/model_based/ICS03ConnectionMismatchTest.json b/modules/tests/support/model_based/ICS03ConnectionMismatchTest.json index 6bed88ced0..6e579d841b 100644 --- a/modules/tests/support/model_based/ICS03ConnectionMismatchTest.json +++ b/modules/tests/support/model_based/ICS03ConnectionMismatchTest.json @@ -53,6 +53,7 @@ "clientId": 1, "connectionId": 0, "counterpartyClientId": 0, + "counterpartyConnectionId": 0, "type": "ICS03ConnectionOpenTry" }, "actionOutcome": "ICS03ConnectionMismatch", @@ -65,4 +66,4 @@ } } } -] +] \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS03ConnectionNotFoundTest.json b/modules/tests/support/model_based/ICS03ConnectionNotFoundTest.json index 4f1da1bc44..84666a043a 100644 --- a/modules/tests/support/model_based/ICS03ConnectionNotFoundTest.json +++ b/modules/tests/support/model_based/ICS03ConnectionNotFoundTest.json @@ -36,6 +36,7 @@ "clientId": 0, "connectionId": 0, "counterpartyClientId": 0, + "counterpartyConnectionId": 0, "type": "ICS03ConnectionOpenTry" }, "actionOutcome": "ICS03ConnectionNotFound", @@ -48,4 +49,4 @@ } } } -] +] \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS03InvalidConsensusHeightTest.json b/modules/tests/support/model_based/ICS03InvalidConsensusHeightTest.json index b87a592328..7c04490b6b 100644 --- a/modules/tests/support/model_based/ICS03InvalidConsensusHeightTest.json +++ b/modules/tests/support/model_based/ICS03InvalidConsensusHeightTest.json @@ -16,10 +16,11 @@ { "action": { "chainId": "chain-A", - "clientId": 0, "clientHeight": 1, + "clientId": 0, "connectionId": -1, "counterpartyClientId": 0, + "counterpartyConnectionId": 0, "type": "ICS03ConnectionOpenTry" }, "actionOutcome": "ICS03InvalidConsensusHeight", From a7b50d772aba468189e4dcdf9714bfa2a26c6dc5 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Fri, 5 Feb 2021 10:23:39 +0100 Subject: [PATCH 35/52] Fix panic in conn open try when no connection id is provided --- modules/src/ics03_connection/context.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/src/ics03_connection/context.rs b/modules/src/ics03_connection/context.rs index a0b9bc6140..718101a842 100644 --- a/modules/src/ics03_connection/context.rs +++ b/modules/src/ics03_connection/context.rs @@ -72,14 +72,14 @@ pub trait ConnectionKeeper { )?; } State::TryOpen => { - self.store_connection( - &result.connection_id.clone().unwrap(), - &result.connection_end, - )?; + let connection_id = result + .connection_id + .unwrap_or_else(|| self.next_connection_id()); + self.store_connection(&connection_id, &result.connection_end)?; // If this is the first time the handler processed this connection, associate the // connection end to its client identifier. self.store_connection_to_client( - &result.connection_id.clone().unwrap(), + &connection_id, &result.connection_end.client_id(), )?; } From 5b66aac5c89d3efed06c0a6a348361df6ad757c4 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Fri, 5 Feb 2021 19:22:38 +0100 Subject: [PATCH 36/52] ICS02TestExecutor -> IBCTestExecutor --- modules/tests/model_based.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index b2c6061982..dd9507af95 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -32,12 +32,12 @@ use step::{ActionOutcome, ActionType, Chain, Step}; use tendermint::account::Id as AccountId; #[derive(Debug)] -struct ICS02TestExecutor { +struct IBCTestExecutor { // mapping from chain identifier to its context contexts: HashMap, } -impl ICS02TestExecutor { +impl IBCTestExecutor { fn new() -> Self { Self { contexts: Default::default(), @@ -199,7 +199,7 @@ impl ICS02TestExecutor { } } -impl modelator::TestExecutor for ICS02TestExecutor { +impl modelator::TestExecutor for IBCTestExecutor { fn initial_step(&mut self, step: Step) -> bool { assert_eq!( step.action.action_type, @@ -465,7 +465,7 @@ fn main() { for test in tests { let path = format!("{}/{}.json", TESTS_DIR, test); - let executor = ICS02TestExecutor::new(); + let executor = IBCTestExecutor::new(); // we should be able to just return the `Result` once the following issue // is fixed: https://github.com/rust-lang/rust/issues/43301 if let Err(e) = modelator::test_driver(executor, path) { From f2194cf1521ec82887c246b552a5e6c9c196fc89 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Fri, 5 Feb 2021 19:52:44 +0100 Subject: [PATCH 37/52] Failing MBT test now passes with #615 --- modules/tests/model_based.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index dd9507af95..d5baf8cee7 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -457,7 +457,7 @@ fn main() { "ICS02HeaderVerificationFailureTest", "ICS03ConnectionOpenInitOKTest", "ICS03MissingClientTest", - // "ICS03ConnectionOpenTryOKTest", + "ICS03ConnectionOpenTryOKTest", "ICS03InvalidConsensusHeightTest", "ICS03ConnectionNotFoundTest", "ICS03ConnectionMismatchTest", From 7bdd3b6a19d49c65e44cb9c8f00aab41b64686a0 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Mon, 8 Feb 2021 11:25:31 +0100 Subject: [PATCH 38/52] Add notes on MBT --- modules/tests/README.md | 46 +++++++++++++++++++++++++++++++++++++++++ modules/tests/TODO | 1 + 2 files changed, 47 insertions(+) create mode 100644 modules/tests/README.md diff --git a/modules/tests/README.md b/modules/tests/README.md new file mode 100644 index 0000000000..3e4b1fb7cc --- /dev/null +++ b/modules/tests/README.md @@ -0,0 +1,46 @@ +### Model-based testing + +Let's say we want to test a system where: +- users can be created and deleted +- users have an id (say `u`) +- existing users have different ids + +#### i. Testing +- sequence of actions and expected outcomes + - `Test :: Vec<(Action, ActionOutcome)>` + - __test a)__: + - if I create user `u` (`action_1`), the operation should _succeed_ (`action_outcome_1`) + - if then I delete user `u` (`action_2`), the operation should _succeed_ (`action_outcome_2`) + - `Test = [(action_1, action_outcome_1), (action_2, action_outcome_2)]` + - __test b)__: + - if I create a user `u` (`action_1`), the operation should _succeed_ (`action_outcome_1`) + - if I then I create user `u` (`action_2`), the operation should _fail_ with an `ExistingUser` error (`action_outcome_2`) +- let's say that an action + its expected outcome is a `Step` + - `Step :: (Action, ActionOutcome)` + - `Test :: Vec` + + +#### ii. Model + +- what's a model? + - it's description of a system + - in our case, it's a __description of the system under test__ +- how do we write a model? + - in a language called `TLA+` +- what do we do with a model? + - e.g. prove invariants about it using a model checker + - example of an invariant: "existing users have different ids" + - model checkers for `TLA+`: `TLC` and `Apalache` + +#### How do we join i. and ii.? + +1. use the model to generate tests + - let's say we want a test where some user is deleted successfully + - define an invariant __negating the test__: + - "it's not possible that a user is deleted successfully" + - then we ask `Apalache` to prove it + - because the invariant is false, `Apalache` will find a counter example that shows it + - for example, it can find __test a)__ +2. monitor the model + - track each model action and its outcome + - define two `TLA+` variables: `action` and `actionOutcome` \ No newline at end of file diff --git a/modules/tests/TODO b/modules/tests/TODO index b86a736905..062411140c 100644 --- a/modules/tests/TODO +++ b/modules/tests/TODO @@ -1,3 +1,4 @@ - conn open try does not increment the connection id counter; looks like a bug - the model allows a conn open try to succeed without a conn open try (likely because we don't have proofs) + - one possible solution is the following; let A be the chain where a conn open init occurs and B the counterparty chain; when processing the conn open init, A should write a proof of it to B's set of proofs; a coon open try at B should then only succeed if B's set of proofs contains a proof of the corresponding conn open init - TLC doesn't finish with `MaxClientsPerChain = 2` From 9ee48bd2a7c337277de3889df06d963b0fd005bf Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Mon, 8 Feb 2021 20:20:23 +0100 Subject: [PATCH 39/52] Remove ICS02 --- modules/src/ics24_host/identifier.rs | 14 -- modules/src/mock/context.rs | 16 +- modules/tests/Makefile | 2 - modules/tests/TODO | 4 - modules/tests/model_based.rs | 217 +----------------- modules/tests/step.rs | 36 +-- modules/tests/support/model_based/IBC.tla | 200 +--------------- .../tests/support/model_based/IBCTests.cfg | 8 +- .../tests/support/model_based/IBCTests.tla | 24 -- modules/tests/support/model_based/ICS03.tla | 158 ------------- .../ICS03ConnectionMismatchTest.json | 69 ------ .../ICS03ConnectionNotFoundTest.json | 52 ----- .../ICS03ConnectionOpenInitOKTest.json | 49 ---- .../ICS03ConnectionOpenTryOKTest.json | 52 ----- .../ICS03InvalidConsensusHeightTest.json | 36 --- .../model_based/ICS03MissingClientTest.json | 65 ------ modules/tests/support/model_based/Makefile | 5 - .../ICS02HeaderVerificationFailureTest.json | 0 .../{ => tests}/ICS02UpdateOKTest.json | 0 19 files changed, 23 insertions(+), 984 deletions(-) delete mode 100644 modules/tests/Makefile delete mode 100644 modules/tests/TODO delete mode 100644 modules/tests/support/model_based/ICS03.tla delete mode 100644 modules/tests/support/model_based/ICS03ConnectionMismatchTest.json delete mode 100644 modules/tests/support/model_based/ICS03ConnectionNotFoundTest.json delete mode 100644 modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json delete mode 100644 modules/tests/support/model_based/ICS03ConnectionOpenTryOKTest.json delete mode 100644 modules/tests/support/model_based/ICS03InvalidConsensusHeightTest.json delete mode 100644 modules/tests/support/model_based/ICS03MissingClientTest.json delete mode 100644 modules/tests/support/model_based/Makefile rename modules/tests/support/model_based/{ => tests}/ICS02HeaderVerificationFailureTest.json (100%) rename modules/tests/support/model_based/{ => tests}/ICS02UpdateOKTest.json (100%) diff --git a/modules/src/ics24_host/identifier.rs b/modules/src/ics24_host/identifier.rs index 293461bda1..e76c9f7d60 100644 --- a/modules/src/ics24_host/identifier.rs +++ b/modules/src/ics24_host/identifier.rs @@ -197,13 +197,6 @@ impl PartialEq for ClientId { pub struct ConnectionId(String); impl ConnectionId { - /// Builds a new connection identifier. - pub fn new(counter: u64) -> Result { - let prefix = ConnectionId::default().to_string(); - let id = format!("{}-{}", prefix, counter); - Self::from_str(id.as_str()) - } - /// Get this identifier as a borrowed `&str` pub fn as_str(&self) -> &str { &self.0 @@ -290,13 +283,6 @@ impl Default for PortId { pub struct ChannelId(String); impl ChannelId { - /// Builds a new channel identifier. - pub fn new(counter: u64) -> Result { - let prefix = ConnectionId::default().to_string(); - let id = format!("{}-{}", prefix, counter); - Self::from_str(id.as_str()) - } - /// Get this identifier as a borrowed `&str` pub fn as_str(&self) -> &str { &self.0 diff --git a/modules/src/mock/context.rs b/modules/src/mock/context.rs index 6bb829924e..c656363513 100644 --- a/modules/src/mock/context.rs +++ b/modules/src/mock/context.rs @@ -88,10 +88,10 @@ pub struct MockContext { port_capabilities: HashMap, /// Counter for connection identifiers (see `next_connection_id`). - connection_ids_counter: u64, + connection_ids_counter: u32, /// Counter for channel identifiers (see `next_channel_id`). - channel_ids_counter: u64, + channel_ids_counter: u32, } /// Returns a MockContext with bare minimum initialization: no clients, no connections and no channels are @@ -392,9 +392,11 @@ impl ChannelReader for MockContext { impl ChannelKeeper for MockContext { fn next_channel_id(&mut self) -> ChannelId { - let counter = self.channel_ids_counter; + let prefix = ChannelId::default().to_string(); + let suffix = self.channel_ids_counter; self.channel_ids_counter += 1; - ChannelId::new(counter).unwrap() + + ChannelId::from_str(format!("{}-{}", prefix, suffix).as_str()).unwrap() } fn store_channel( @@ -495,9 +497,11 @@ impl ConnectionReader for MockContext { impl ConnectionKeeper for MockContext { fn next_connection_id(&mut self) -> ConnectionId { - let counter = self.connection_ids_counter; + let prefix = ConnectionId::default().to_string(); + let suffix = self.connection_ids_counter; self.connection_ids_counter += 1; - ConnectionId::new(counter).unwrap() + + ConnectionId::from_str(format!("{}-{}", prefix, suffix).as_str()).unwrap() } fn store_connection( diff --git a/modules/tests/Makefile b/modules/tests/Makefile deleted file mode 100644 index 7d64c99e0d..0000000000 --- a/modules/tests/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -test: - cargo t --features mocks -- --nocapture main diff --git a/modules/tests/TODO b/modules/tests/TODO deleted file mode 100644 index 062411140c..0000000000 --- a/modules/tests/TODO +++ /dev/null @@ -1,4 +0,0 @@ -- conn open try does not increment the connection id counter; looks like a bug -- the model allows a conn open try to succeed without a conn open try (likely because we don't have proofs) - - one possible solution is the following; let A be the chain where a conn open init occurs and B the counterparty chain; when processing the conn open init, A should write a proof of it to B's set of proofs; a coon open try at B should then only succeed if B's set of proofs contains a proof of the corresponding conn open init -- TLC doesn't finish with `MaxClientsPerChain = 2` diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index d5baf8cee7..e7ef6e8365 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -7,23 +7,15 @@ 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::ics03_connection::connection::Counterparty; -use ibc::ics03_connection::error::Kind as ICS03ErrorKind; -use ibc::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; -use ibc::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; -use ibc::ics03_connection::msgs::ConnectionMsg; -use ibc::ics03_connection::version::Version; use ibc::ics18_relayer::context::ICS18Context; use ibc::ics18_relayer::error::{Error as ICS18Error, Kind as ICS18ErrorKind}; -use ibc::ics23_commitment::commitment::{CommitmentPrefix, CommitmentProofBytes}; -use ibc::ics24_host::identifier::{ChainId, ClientId, ConnectionId}; +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::proofs::{ConsensusProof, Proofs}; use ibc::Height; use std::collections::HashMap; use std::error::Error; @@ -101,29 +93,16 @@ impl IBCTestExecutor { } // TODO: this is sometimes called version/revision number but seems - // unrelated with the `Version` type below; is that so? + // unrelated with the `Version` type; is that so? fn epoch() -> u64 { 0 } - fn version() -> Version { - Version::default() - } - - fn versions() -> Vec { - vec![Self::version()] - } - fn client_id(client_id: u64) -> ClientId { ClientId::new(ClientType::Mock, client_id) .expect("it should be possible to create the client identifier") } - fn connection_id(connection_id: u64) -> ConnectionId { - ConnectionId::new(connection_id) - .expect("it should be possible to create the connection identifier") - } - fn height(height: u64) -> Height { Height::new(Self::epoch(), height) } @@ -148,48 +127,6 @@ impl IBCTestExecutor { AccountId::new([0; 20]) } - fn counterparty(client_id: u64, connection_id: Option) -> Counterparty { - let client_id = Self::client_id(client_id); - let connection_id = connection_id.map(|connection_id| Self::connection_id(connection_id)); - let prefix = Self::commitment_prefix(); - Counterparty::new(client_id, connection_id, prefix) - } - - fn delay_period() -> u64 { - 0 - } - - fn commitment_prefix() -> CommitmentPrefix { - vec![0].into() - } - - fn commitment_proof_bytes() -> CommitmentProofBytes { - vec![0].into() - } - - fn consensus_proof(height: u64) -> ConsensusProof { - let consensus_proof = Self::commitment_proof_bytes(); - let consensus_height = Self::height(height); - ConsensusProof::new(consensus_proof, consensus_height) - .expect("it should be possible to create the consensus proof") - } - - fn proofs(height: u64) -> Proofs { - let object_proof = Self::commitment_proof_bytes(); - let client_proof = None; - let consensus_proof = Some(Self::consensus_proof(height)); - let other_proof = None; - let height = Self::height(height); - Proofs::new( - object_proof, - client_proof, - consensus_proof, - other_proof, - height, - ) - .expect("it should be possible to create the proofs") - } - /// Check that chain heights match the ones in the model. fn check_chain_heights(&self, chains: HashMap) -> bool { chains.into_iter().all(|(chain_id, chain)| { @@ -219,7 +156,6 @@ impl modelator::TestExecutor for IBCTestExecutor { } fn next_step(&mut self, step: Step) -> bool { - println!("{:?}", step); let outcome_matches = match step.action.action_type { ActionType::None => panic!("unexpected action type"), ActionType::ICS02CreateClient => { @@ -306,162 +242,17 @@ impl modelator::TestExecutor for IBCTestExecutor { action => panic!("unexpected action outcome {:?}", action), } } - ActionType::ICS03ConnectionOpenInit => { - // get action parameters - let chain_id = step - .action - .chain_id - .expect("connection open init action should have a chain identifier"); - let client_id = step - .action - .client_id - .expect("connection open init action should have a client identifier"); - let counterparty_client_id = step.action.counterparty_client_id.expect( - "connection open init action should have a counterparty client identifier", - ); - - // get chain's context - let ctx = self.chain_context_mut(&chain_id); - - // create ICS26 message and deliver it - let msg = ICS26Envelope::ICS3Msg(ConnectionMsg::ConnectionOpenInit( - MsgConnectionOpenInit { - client_id: Self::client_id(client_id), - counterparty: Self::counterparty(counterparty_client_id, None), - version: Self::version(), - delay_period: Self::delay_period(), - signer: Self::signer(), - }, - )); - let result = ctx.deliver(msg); - - // check the expected outcome - match step.action_outcome { - ActionOutcome::ICS03ConnectionOpenInitOK => { - // the implementaion matches the model if no error occurs - result.is_ok() - } - ActionOutcome::ICS03MissingClient => { - let handler_error_kind = - Self::extract_handler_error_kind::(result); - // the implementaion matches the model if there's an - // error matching the expected outcome - matches!( - handler_error_kind, - ICS03ErrorKind::MissingClient(error_client_id) - if error_client_id == Self::client_id(client_id) - ) - } - action => panic!("unexpected action outcome {:?}", action), - } - } - ActionType::ICS03ConnectionOpenTry => { - // get action parameters - let chain_id = step - .action - .chain_id - .expect("connection open try action should have a chain identifier"); - let client_id = step - .action - .client_id - .expect("connection open try action should have a client identifier"); - let client_height = step - .action - .client_height - .expect("connection open try action should have a client height"); - let counterparty_client_id = step.action.counterparty_client_id.expect( - "connection open try action should have a counterparty client identifier", - ); - let connection_id = step.action.connection_id; - let counterparty_connection_id = step.action.counterparty_connection_id.expect( - "connection open try action should have a counterparty connection identifier", - ); - - // get chain's context - let ctx = self.chain_context_mut(&chain_id); - - // create ICS26 message and deliver it - let msg = ICS26Envelope::ICS3Msg(ConnectionMsg::ConnectionOpenTry(Box::new( - MsgConnectionOpenTry { - previous_connection_id: connection_id.map(Self::connection_id), - client_id: Self::client_id(client_id), - client_state: None, - counterparty: Self::counterparty( - counterparty_client_id, - Some(counterparty_connection_id), - ), - counterparty_versions: Self::versions(), - proofs: Self::proofs(client_height), - delay_period: Self::delay_period(), - signer: Self::signer(), - }, - ))); - let result = dbg!(ctx.deliver(msg)); - - // check the expected outcome - match step.action_outcome { - ActionOutcome::ICS03ConnectionOpenTryOK => { - // the implementaion matches the model if no error occurs - result.is_ok() - } - ActionOutcome::ICS03InvalidConsensusHeight => { - let handler_error_kind = - Self::extract_handler_error_kind::(result); - // the implementaion matches the model if there's an - // error matching the expected outcome - matches!( - handler_error_kind, - ICS03ErrorKind::InvalidConsensusHeight(error_consensus_height, _) - if error_consensus_height == Self::height(client_height) - ) - } - ActionOutcome::ICS03ConnectionNotFound => { - let handler_error_kind = - Self::extract_handler_error_kind::(result); - // the implementaion matches the model if there's an - // error matching the expected outcome - connection_id.is_some() - && matches!( - handler_error_kind, - ICS03ErrorKind::ConnectionNotFound(error_connection_id) - if error_connection_id == Self::connection_id(connection_id.unwrap()) - ) - } - ActionOutcome::ICS03ConnectionMismatch => { - let handler_error_kind = - Self::extract_handler_error_kind::(result); - // the implementaion matches the model if there's an - // error matching the expected outcome - connection_id.is_some() - && matches!( - handler_error_kind, - ICS03ErrorKind::ConnectionMismatch(error_connection_id) - if error_connection_id == Self::connection_id(connection_id.unwrap()) - ) - } - action => panic!("unexpected action outcome {:?}", action), - } - } }; // also check that chain heights match outcome_matches && self.check_chain_heights(step.chains) } } -const TESTS_DIR: &str = "tests/support/model_based"; +const TESTS_DIR: &str = "tests/support/model_based/tests"; #[test] fn main() { - let tests = vec![ - "ICS02UpdateOKTest", - "ICS02HeaderVerificationFailureTest", - "ICS03ConnectionOpenInitOKTest", - "ICS03MissingClientTest", - "ICS03ConnectionOpenTryOKTest", - "ICS03InvalidConsensusHeightTest", - "ICS03ConnectionNotFoundTest", - "ICS03ConnectionMismatchTest", - ]; + let tests = vec!["ICS02UpdateOKTest", "ICS02HeaderVerificationFailureTest"]; for test in tests { let path = format!("{}/{}.json", TESTS_DIR, test); diff --git a/modules/tests/step.rs b/modules/tests/step.rs index 67f97882bc..98c831e050 100644 --- a/modules/tests/step.rs +++ b/modules/tests/step.rs @@ -1,4 +1,4 @@ -use serde::{Deserialize, Deserializer}; +use serde::Deserialize; use std::collections::HashMap; use std::fmt::Debug; @@ -25,32 +25,6 @@ pub struct Action { #[serde(alias = "clientHeight")] pub client_height: Option, - - #[serde(alias = "counterpartyClientId")] - pub counterparty_client_id: Option, - - #[serde(alias = "connectionId")] - #[serde(default, deserialize_with = "deserialize_connection_id")] - pub connection_id: Option, - - #[serde(alias = "counterpartyConnectionId")] - #[serde(default, deserialize_with = "deserialize_connection_id")] - pub counterparty_connection_id: Option, -} - -/// On the model, a non-existing `connection_id` is represented with -1. -/// For this reason, this function maps a `Some(-1)` to a `None`. -fn deserialize_connection_id<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let connection_id: Option = Deserialize::deserialize(deserializer)?; - let connection_id = if connection_id == Some(-1) { - None - } else { - connection_id.map(|connection_id| connection_id as u64) - }; - Ok(connection_id) } #[derive(Debug, Clone, PartialEq, Deserialize)] @@ -58,8 +32,6 @@ pub enum ActionType { None, ICS02CreateClient, ICS02UpdateClient, - ICS03ConnectionOpenInit, - ICS03ConnectionOpenTry, } #[derive(Debug, Clone, PartialEq, Deserialize)] @@ -69,12 +41,6 @@ pub enum ActionOutcome { ICS02UpdateOK, ICS02ClientNotFound, ICS02HeaderVerificationFailure, - ICS03ConnectionOpenInitOK, - ICS03MissingClient, - ICS03ConnectionOpenTryOK, - ICS03InvalidConsensusHeight, - ICS03ConnectionNotFound, - ICS03ConnectionMismatch, } #[derive(Debug, Clone, PartialEq, Deserialize)] diff --git a/modules/tests/support/model_based/IBC.tla b/modules/tests/support/model_based/IBC.tla index 89ddfe6de2..7586626558 100644 --- a/modules/tests/support/model_based/IBC.tla +++ b/modules/tests/support/model_based/IBC.tla @@ -1,6 +1,6 @@ --------------------------------- MODULE IBC ---------------------------------- -EXTENDS Integers, FiniteSets, ICS02, ICS03 +EXTENDS Integers, FiniteSets, ICS02 \* ids of existing chains CONSTANT ChainIds @@ -10,9 +10,6 @@ ASSUME MaxClientsPerChain >= 0 \* max height which clients can reach CONSTANT MaxClientHeight ASSUME MaxClientHeight >= 0 -\* max number of connections to be created per chain -CONSTANT MaxConnectionsPerChain -ASSUME MaxConnectionsPerChain >= 0 \* mapping from chain id to its data VARIABLE chains @@ -30,10 +27,7 @@ ActionType == [ type |-> STRING, chainId |-> STRING, clientHeight |-> Int, - clientId |-> Int, - connectionId |-> Int, - counterpartyClientId |-> Int, - counterpartyConnectionId |-> Int + clientId |-> Int ] AsAction(a) == a <: ActionType (******************* END OF TYPE ANNOTATIONS FOR APALACHE ********************) @@ -44,15 +38,6 @@ ChainHeights == Int ClientIds == 0..(MaxClientsPerChain - 1) \* set of possible client heights ClientHeights == 1..MaxClientHeight -\* set of possible connection identifiers -ConnectionIds == 0..(MaxConnectionsPerChain- 1) -\* set of possible connection states -ConnectionStates == { - "Uninit", - "Init", - "TryOpen", - "Open" -} \* data kept per cliennt Client == [ @@ -62,25 +47,11 @@ Client == [ Clients == [ ClientIds -> Client ] -\* data kept per connection -Connection == [ - state: ConnectionStates, - clientId: ClientIds \union {ClientIdNone}, - counterpartyClientId: ClientIds \union {ClientIdNone}, - connectionId: ConnectionIds \union {ConnectionIdNone}, - counterpartyConnectionId: ConnectionIds \union {ConnectionIdNone} -] -\* mapping from connection identifier to its data -Connections == [ - ConnectionIds -> Connection -] \* data kept per chain Chain == [ height: ChainHeights, clients: Clients, - clientIdCounter: 0..MaxClientsPerChain, - connections: Connections, - connectionIdCounter: 0..MaxConnectionsPerChain + clientIdCounter: 0..MaxClientsPerChain ] \* mapping from chain identifier to its data Chains == [ @@ -102,27 +73,10 @@ UpdateClientActions == [ clientHeight: ClientHeights, clientId: ClientIds ] <: {ActionType} -ConnectionOpenInitActions == [ - type: {"ICS03ConnectionOpenInit"}, - chainId: ChainIds, - clientId: ClientIds, - counterpartyClientId: ClientIds -] <: {ActionType} -ConnectionOpenTryActions == [ - type: {"ICS03ConnectionOpenTry"}, - chainId: ChainIds, - clientHeight: ClientHeights, - clientId: ClientIds, - counterpartyClientId: ClientIds, - connectionId: ConnectionIds \union {ConnectionIdNone}, - counterpartyConnectionId: ConnectionIds -] <: {ActionType} Actions == NoneActions \union CreateClientActions \union - UpdateClientActions \union - ConnectionOpenInitActions \union - ConnectionOpenTryActions + UpdateClientActions \* set of possible action outcomes ActionOutcomes == { @@ -133,15 +87,7 @@ ActionOutcomes == { \* ICS02_UpdateClient outcomes: "ICS02UpdateOK", "ICS02ClientNotFound", - "ICS02HeaderVerificationFailure", - \* ICS03_ConnectionOpenInit outcomes: - "ICS03ConnectionOpenInitOK", - "ICS03MissingClient", - \* ICS03_ConnectionOpenTry outcomes: - "ICS03ConnectionOpenTryOK", - "ICS03InvalidConsensusHeight", - "ICS03ConnectionNotFound", - "ICS03ConnectionMismatch" + "ICS02HeaderVerificationFailure" } (***************************** Specification *********************************) @@ -187,75 +133,6 @@ UpdateClient(chainId, clientId, clientHeight) == clientHeight |-> clientHeight]) /\ actionOutcome' = result.outcome -ConnectionOpenInit(chainId, clientId, counterpartyClientId) == - LET chain == chains[chainId] IN - LET clients == chain.clients IN - LET connections == chain.connections IN - LET connectionIdCounter == chain.connectionIdCounter IN - LET result == ICS03_ConnectionOpenInit( - clients, - connections, - connectionIdCounter, - clientId, - counterpartyClientId - ) IN - \* update the chain - LET updatedChain == [chain EXCEPT - !.height = UpdateChainHeight(@, result.outcome, "ICS03ConnectionOpenInitOK"), - !.connections = result.connections, - !.connectionIdCounter = result.connectionIdCounter - ] IN - \* update `chains`, set the `action` and its `actionOutcome` - /\ chains' = [chains EXCEPT ![chainId] = updatedChain] - /\ action' = AsAction([ - type |-> "ICS03ConnectionOpenInit", - chainId |-> chainId, - clientId |-> clientId, - counterpartyClientId |-> counterpartyClientId]) - /\ actionOutcome' = result.outcome - -ConnectionOpenTry( - chainId, - clientId, - clientHeight, - connectionId, - counterpartyClientId, - counterpartyConnectionId -) == - LET chain == chains[chainId] IN - LET height == chain.height IN - LET clients == chain.clients IN - LET connections == chain.connections IN - LET connectionIdCounter == chain.connectionIdCounter IN - LET result == ICS03_ConnectionOpenTry( - height, - clients, - connections, - connectionIdCounter, - clientId, - clientHeight, - connectionId, - counterpartyClientId, - counterpartyConnectionId - ) IN - \* update the chain - LET updatedChain == [chain EXCEPT - !.height = UpdateChainHeight(@, result.outcome, "ICS03ConnectionOpenTryOK"), - !.connections = result.connections, - !.connectionIdCounter = result.connectionIdCounter - ] IN - \* update `chains`, set the `action` and its `actionOutcome` - /\ chains' = [chains EXCEPT ![chainId] = updatedChain] - /\ action' = AsAction([ - type |-> "ICS03ConnectionOpenTry", - chainId |-> chainId, - clientId |-> clientId, - clientHeight |-> clientHeight, - connectionId |-> connectionId, - counterpartyClientId |-> counterpartyClientId, - counterpartyConnectionId |-> counterpartyConnectionId]) - /\ actionOutcome' = result.outcome - CreateClientAction == \* select a chain id \E chainId \in ChainIds: @@ -277,77 +154,16 @@ UpdateClientAction == \E clientHeight \in ClientHeights: UpdateClient(chainId, clientId, clientHeight) -ConnectionOpenInitAction == - \* select a chain id - \E chainId \in ChainIds: - \* select a client id - \E clientId \in ClientIds: - \* select a counterparty client id - \E counterpartyClientId \in ClientIds: - \* only create connection if the model constant `MaxConnectionsPerChain` - \* allows it - IF chains[chainId].connectionIdCounter \in ConnectionIds THEN - ConnectionOpenInit(chainId, clientId, counterpartyClientId) - ELSE - UNCHANGED vars - -ConnectionOpenTryAction == - \* select a chain id - \E chainId \in ChainIds: - \* select a client id - \E clientId \in ClientIds: - \* select a claimed height for the client - \E clientHeight \in ClientHeights: - \* select a connection id (which can be none) - \E connectionId \in ConnectionIds \union {ConnectionIdNone}: - \* select a counterparty client id - \E counterpartyClientId \in ClientIds: - \* select a counterparty connection id - \E counterpartyConnectionId \in ConnectionIds: - IF connectionId = ConnectionIdNone THEN - \* in this case we're trying to create a new connection; only create - \* connection if the model constant `MaxConnectionsPerChain` allows - \* it - IF chains[chainId].connectionIdCounter \in ConnectionIds THEN - ConnectionOpenTry( - chainId, - clientId, - clientHeight, - connectionId, - counterpartyClientId, - counterpartyConnectionId - ) - ELSE - UNCHANGED vars - ELSE - ConnectionOpenTry( - chainId, - clientId, - clientHeight, - connectionId, - counterpartyClientId, - counterpartyConnectionId - ) - Init == - \* create a client and a connection with none values + \* create a client with none values LET clientNone == [ height |-> HeightNone ] IN - LET connectionNone == [ - state |-> "Uninit", - clientId |-> ClientIdNone, - counterpartyClientId |-> ClientIdNone, - connectionId |-> ConnectionIdNone, - counterpartyConnectionId |-> ConnectionIdNone - ] IN \* create an empty chain LET emptyChain == [ height |-> 0, clients |-> [clientId \in ClientIds |-> clientNone], - clientIdCounter |-> 0, - connections |-> [connectionId \in ConnectionIds |-> connectionNone], - connectionIdCounter |-> 0 + clientIdCounter |-> 0 ] IN /\ chains = [chainId \in ChainIds |-> emptyChain] /\ action = AsAction([type |-> "None"]) @@ -356,8 +172,6 @@ Init == Next == \/ CreateClientAction \/ UpdateClientAction - \/ ConnectionOpenInitAction - \/ ConnectionOpenTryAction \/ UNCHANGED vars (******************************** Invariants *********************************) diff --git a/modules/tests/support/model_based/IBCTests.cfg b/modules/tests/support/model_based/IBCTests.cfg index 4090d7a7f4..55958ebe13 100644 --- a/modules/tests/support/model_based/IBCTests.cfg +++ b/modules/tests/support/model_based/IBCTests.cfg @@ -11,10 +11,4 @@ INVARIANTS \* ICS02CreateOKTest \* ICS02UpdateOKTest \* ICS02ClientNotFoundTest - \* ICS02HeaderVerificationFailureTest - \* ICS03ConnectionOpenInitOKTest - \* ICS03MissingClientTest - ICS03ConnectionOpenTryOKTest - \* ICS03InvalidConsensusHeightTest - \* ICS03ConnectionNotFoundTest - \* ICS03ConnectionMismatchTest + \* ICS02HeaderVerificationFailureTest \ No newline at end of file diff --git a/modules/tests/support/model_based/IBCTests.tla b/modules/tests/support/model_based/IBCTests.tla index 35127df6ae..e8d4926557 100644 --- a/modules/tests/support/model_based/IBCTests.tla +++ b/modules/tests/support/model_based/IBCTests.tla @@ -14,33 +14,9 @@ ICS02ClientNotFound == ICS02HeaderVerificationFailure == /\ actionOutcome = "ICS02HeaderVerificationFailure" -ICS03ConnectionOpenInitOK == - /\ actionOutcome = "ICS03ConnectionOpenInitOK" - -ICS03MissingClient == - /\ actionOutcome = "ICS03MissingClient" - -ICS03ConnectionOpenTryOK == - /\ actionOutcome = "ICS03ConnectionOpenTryOK" - -ICS03InvalidConsensusHeight == - /\ actionOutcome = "ICS03InvalidConsensusHeight" - -ICS03ConnectionNotFound == - /\ actionOutcome = "ICS03ConnectionNotFound" - -ICS03ConnectionMismatch == - /\ actionOutcome = "ICS03ConnectionMismatch" - ICS02CreateOKTest == ~ICS02CreateOK ICS02UpdateOKTest == ~ICS02UpdateOK ICS02ClientNotFoundTest == ~ICS02ClientNotFound ICS02HeaderVerificationFailureTest == ~ICS02HeaderVerificationFailure -ICS03ConnectionOpenInitOKTest == ~ICS03ConnectionOpenInitOK -ICS03MissingClientTest == ~ICS03MissingClient -ICS03ConnectionOpenTryOKTest == ~ICS03ConnectionOpenTryOK -ICS03InvalidConsensusHeightTest == ~ICS03InvalidConsensusHeight -ICS03ConnectionNotFoundTest == ~ICS03ConnectionNotFound -ICS03ConnectionMismatchTest == ~ICS03ConnectionMismatch =============================================================================== diff --git a/modules/tests/support/model_based/ICS03.tla b/modules/tests/support/model_based/ICS03.tla deleted file mode 100644 index e9b3096a7f..0000000000 --- a/modules/tests/support/model_based/ICS03.tla +++ /dev/null @@ -1,158 +0,0 @@ ------------------------------- MODULE ICS03 ----------------------------------- - -EXTENDS Integers, FiniteSets, IBCDefinitions, ICS02 - -\* retrieves `connectionId`'s data -ICS03_GetConnection(connections, connectionId) == - connections[connectionId] - -\* check if `connectionId` exists -ICS03_ConnectionExists(connections, connectionId) == - ICS03_GetConnection(connections, connectionId).state /= "Uninit" - -\* update `connectionId`'s data -ICS03_SetConnection(connections, connectionId, connection) == - [connections EXCEPT ![connectionId] = connection] - -ICS03_ConnectionOpenInit( - clients, - connections, - connectionIdCounter, - clientId, - counterpartyClientId -) == - \* check if the client exists - IF ICS02_ClientExists(clients, clientId) THEN - \* if the client exists, - \* then check if the connection exists (it shouldn't) - IF ICS03_ConnectionExists(connections, connectionIdCounter) THEN - \* if the connection to be created already exists, - \* then there's an error in the model - [ - connections |-> connections, - connectionIdCounter |-> connectionIdCounter, - outcome |-> "ModelError" - ] - ELSE - \* if it doesn't, create it - LET connection == [ - state |-> "Init", - clientId |-> clientId, - counterpartyClientId |-> counterpartyClientId, - connectionId |-> connectionIdCounter, - counterpartyConnectionId |-> ConnectionIdNone - ] IN - \* return result with updated state - [ - connections |-> ICS03_SetConnection( - connections, - connectionIdCounter, - connection - ), - connectionIdCounter |-> connectionIdCounter + 1, - outcome |-> "ICS03ConnectionOpenInitOK" - ] - ELSE - \* if the client does not exist, then set an error outcome - [ - connections |-> connections, - connectionIdCounter |-> connectionIdCounter, - outcome |-> "ICS03MissingClient" - ] - -\* TODO: errors generated when verifying proofs are never an outcome of this -\* model -ICS03_ConnectionOpenTry( - chainHeight, - clients, - connections, - connectionIdCounter, - clientId, - clientClaimedHeight, - connectionId, - counterpartyClientId, - counterpartyConnectionId -) == - \* check if client's claimed height is higher than the chain's height - IF clientClaimedHeight > chainHeight THEN - \* if client's height is too advanced, then set an error outcome - [ - connections |-> connections, - connectionIdCounter |-> connectionIdCounter, - outcome |-> "ICS03InvalidConsensusHeight" - ] - \* TODO: add `chain_max_history_size` to the model to be able to also - \* return a `ICS03StaleConsensusHeight` error outcome - ELSE - \* check if a `connectionId` was set - IF connectionId /= ConnectionIdNone THEN - \* if so, check if the connection exists - IF ICS03_ConnectionExists(connections, connectionId) THEN - \* if the connection exists, verify that is matches the - \* the parameters provided - LET connection == ICS03_GetConnection( - connections, - connectionId - ) IN - IF /\ connection.state = "Init" - /\ connection.clientId = clientId - /\ connection.counterpartyClientId = counterpartyClientId - THEN - \* verification passed; update connection - LET updatedConnection == [ - state |-> "TryOpen", - clientId |-> clientId, - connectionId |-> connectionId, - counterpartyClientId |-> counterpartyClientId, - counterpartyConnectionId |-> counterpartyConnectionId - ] IN - \* return result with updated state - [ - connections |-> ICS03_SetConnection( - connections, - connectionId, - updatedConnection - ), - \* as the connection identifier has already been - \* created, here we do not update the - \* `connectionIdCounter` - connectionIdCounter |-> connectionIdCounter, - outcome |-> "ICS03ConnectionOpenTryOK" - ] - ELSE - [ - connections |-> connections, - connectionIdCounter |-> connectionIdCounter, - outcome |-> "ICS03ConnectionMismatch" - ] - ELSE - \* if the connection does not exist, then set an error outcome - [ - connections |-> connections, - connectionIdCounter |-> connectionIdCounter, - outcome |-> "ICS03ConnectionNotFound" - ] - ELSE - \* verification passed; create connection - LET connection == [ - state |-> "TryOpen", - clientId |-> clientId, - \* generate a new connection identifier - connectionId |-> connectionIdCounter, - counterpartyClientId |-> counterpartyClientId, - counterpartyConnectionId |-> counterpartyConnectionId - ] IN - \* return result with updated state - [ - connections |-> ICS03_SetConnection( - connections, - connectionIdCounter, - connection - ), - \* since a new connection identifier has been created, here we - \* update the `connectionIdCounter` - connectionIdCounter |-> connectionIdCounter + 1, - outcome |-> "ICS03ConnectionOpenTryOK" - ] - -=============================================================================== diff --git a/modules/tests/support/model_based/ICS03ConnectionMismatchTest.json b/modules/tests/support/model_based/ICS03ConnectionMismatchTest.json deleted file mode 100644 index 6e579d841b..0000000000 --- a/modules/tests/support/model_based/ICS03ConnectionMismatchTest.json +++ /dev/null @@ -1,69 +0,0 @@ -[ - { - "action": { - "type": "None" - }, - "actionOutcome": "None", - "chains": { - "chain-A": { - "height": 0 - }, - "chain-B": { - "height": 0 - } - } - }, - { - "action": { - "chainId": "chain-B", - "clientHeight": 1, - "type": "ICS02CreateClient" - }, - "actionOutcome": "ICS02CreateOK", - "chains": { - "chain-A": { - "height": 0 - }, - "chain-B": { - "height": 1 - } - } - }, - { - "action": { - "chainId": "chain-B", - "clientId": 0, - "counterpartyClientId": 1, - "type": "ICS03ConnectionOpenInit" - }, - "actionOutcome": "ICS03ConnectionOpenInitOK", - "chains": { - "chain-A": { - "height": 0 - }, - "chain-B": { - "height": 2 - } - } - }, - { - "action": { - "chainId": "chain-B", - "clientHeight": 2, - "clientId": 1, - "connectionId": 0, - "counterpartyClientId": 0, - "counterpartyConnectionId": 0, - "type": "ICS03ConnectionOpenTry" - }, - "actionOutcome": "ICS03ConnectionMismatch", - "chains": { - "chain-A": { - "height": 0 - }, - "chain-B": { - "height": 2 - } - } - } -] \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS03ConnectionNotFoundTest.json b/modules/tests/support/model_based/ICS03ConnectionNotFoundTest.json deleted file mode 100644 index 84666a043a..0000000000 --- a/modules/tests/support/model_based/ICS03ConnectionNotFoundTest.json +++ /dev/null @@ -1,52 +0,0 @@ -[ - { - "action": { - "type": "None" - }, - "actionOutcome": "None", - "chains": { - "chain-A": { - "height": 0 - }, - "chain-B": { - "height": 0 - } - } - }, - { - "action": { - "chainId": "chain-B", - "clientHeight": 1, - "type": "ICS02CreateClient" - }, - "actionOutcome": "ICS02CreateOK", - "chains": { - "chain-A": { - "height": 0 - }, - "chain-B": { - "height": 1 - } - } - }, - { - "action": { - "chainId": "chain-B", - "clientHeight": 1, - "clientId": 0, - "connectionId": 0, - "counterpartyClientId": 0, - "counterpartyConnectionId": 0, - "type": "ICS03ConnectionOpenTry" - }, - "actionOutcome": "ICS03ConnectionNotFound", - "chains": { - "chain-A": { - "height": 0 - }, - "chain-B": { - "height": 1 - } - } - } -] \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json b/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json deleted file mode 100644 index 6e3d67ed6b..0000000000 --- a/modules/tests/support/model_based/ICS03ConnectionOpenInitOKTest.json +++ /dev/null @@ -1,49 +0,0 @@ -[ - { - "action": { - "type": "None" - }, - "actionOutcome": "None", - "chains": { - "chain-A": { - "height": 0 - }, - "chain-B": { - "height": 0 - } - } - }, - { - "action": { - "chainId": "chain-B", - "clientHeight": 1, - "type": "ICS02CreateClient" - }, - "actionOutcome": "ICS02CreateOK", - "chains": { - "chain-A": { - "height": 0 - }, - "chain-B": { - "height": 1 - } - } - }, - { - "action": { - "chainId": "chain-B", - "clientId": 0, - "counterpartyClientId": 0, - "type": "ICS03ConnectionOpenInit" - }, - "actionOutcome": "ICS03ConnectionOpenInitOK", - "chains": { - "chain-A": { - "height": 0 - }, - "chain-B": { - "height": 2 - } - } - } -] diff --git a/modules/tests/support/model_based/ICS03ConnectionOpenTryOKTest.json b/modules/tests/support/model_based/ICS03ConnectionOpenTryOKTest.json deleted file mode 100644 index 2c2655da7b..0000000000 --- a/modules/tests/support/model_based/ICS03ConnectionOpenTryOKTest.json +++ /dev/null @@ -1,52 +0,0 @@ -[ - { - "action": { - "type": "None" - }, - "actionOutcome": "None", - "chains": { - "chain-A": { - "height": 0 - }, - "chain-B": { - "height": 0 - } - } - }, - { - "action": { - "chainId": "chain-A", - "clientHeight": 1, - "type": "ICS02CreateClient" - }, - "actionOutcome": "ICS02CreateOK", - "chains": { - "chain-A": { - "height": 1 - }, - "chain-B": { - "height": 0 - } - } - }, - { - "action": { - "chainId": "chain-A", - "clientHeight": 1, - "clientId": 0, - "connectionId": -1, - "counterpartyClientId": 0, - "counterpartyConnectionId": 0, - "type": "ICS03ConnectionOpenTry" - }, - "actionOutcome": "ICS03ConnectionOpenTryOK", - "chains": { - "chain-A": { - "height": 2 - }, - "chain-B": { - "height": 0 - } - } - } -] \ No newline at end of file diff --git a/modules/tests/support/model_based/ICS03InvalidConsensusHeightTest.json b/modules/tests/support/model_based/ICS03InvalidConsensusHeightTest.json deleted file mode 100644 index 7c04490b6b..0000000000 --- a/modules/tests/support/model_based/ICS03InvalidConsensusHeightTest.json +++ /dev/null @@ -1,36 +0,0 @@ -[ - { - "action": { - "type": "None" - }, - "actionOutcome": "None", - "chains": { - "chain-A": { - "height": 0 - }, - "chain-B": { - "height": 0 - } - } - }, - { - "action": { - "chainId": "chain-A", - "clientHeight": 1, - "clientId": 0, - "connectionId": -1, - "counterpartyClientId": 0, - "counterpartyConnectionId": 0, - "type": "ICS03ConnectionOpenTry" - }, - "actionOutcome": "ICS03InvalidConsensusHeight", - "chains": { - "chain-A": { - "height": 0 - }, - "chain-B": { - "height": 0 - } - } - } -] diff --git a/modules/tests/support/model_based/ICS03MissingClientTest.json b/modules/tests/support/model_based/ICS03MissingClientTest.json deleted file mode 100644 index 9200e16399..0000000000 --- a/modules/tests/support/model_based/ICS03MissingClientTest.json +++ /dev/null @@ -1,65 +0,0 @@ -[ - { - "action": { - "type": "None" - }, - "actionOutcome": "None", - "chains": { - "chain-A": { - "height": 0 - }, - "chain-B": { - "height": 0 - } - } - }, - { - "action": { - "chainId": "chain-A", - "clientHeight": 1, - "type": "ICS02CreateClient" - }, - "actionOutcome": "ICS02CreateOK", - "chains": { - "chain-A": { - "height": 1 - }, - "chain-B": { - "height": 0 - } - } - }, - { - "action": { - "chainId": "chain-A", - "clientHeight": 1, - "type": "ICS02CreateClient" - }, - "actionOutcome": "ICS02CreateOK", - "chains": { - "chain-A": { - "height": 2 - }, - "chain-B": { - "height": 0 - } - } - }, - { - "action": { - "chainId": "chain-B", - "clientId": 0, - "counterpartyClientId": 0, - "type": "ICS03ConnectionOpenInit" - }, - "actionOutcome": "ICS03MissingClient", - "chains": { - "chain-A": { - "height": 2 - }, - "chain-B": { - "height": 0 - } - } - } -] diff --git a/modules/tests/support/model_based/Makefile b/modules/tests/support/model_based/Makefile deleted file mode 100644 index e0cdfbb248..0000000000 --- a/modules/tests/support/model_based/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -check: - apalache-mc check --inv=ModelNeverErrors IBC.tla - -test: - apalache-mc check IBCTests.tla diff --git a/modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json b/modules/tests/support/model_based/tests/ICS02HeaderVerificationFailureTest.json similarity index 100% rename from modules/tests/support/model_based/ICS02HeaderVerificationFailureTest.json rename to modules/tests/support/model_based/tests/ICS02HeaderVerificationFailureTest.json diff --git a/modules/tests/support/model_based/ICS02UpdateOKTest.json b/modules/tests/support/model_based/tests/ICS02UpdateOKTest.json similarity index 100% rename from modules/tests/support/model_based/ICS02UpdateOKTest.json rename to modules/tests/support/model_based/tests/ICS02UpdateOKTest.json From 035651e68338fc82c6d76762b11622bf9538d6fc Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Mon, 8 Feb 2021 23:42:48 +0100 Subject: [PATCH 40/52] Add README --- modules/tests/model_based.rs | 2 +- .../tests/support/model_based/IBCTests.cfg | 8 +--- .../tests/support/model_based/IBCTests.tla | 16 ++++---- modules/tests/support/model_based/README.md | 37 +++++++++++++++++++ 4 files changed, 47 insertions(+), 16 deletions(-) create mode 100644 modules/tests/support/model_based/README.md diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index e7ef6e8365..906aa64a2f 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -251,7 +251,7 @@ impl modelator::TestExecutor for IBCTestExecutor { const TESTS_DIR: &str = "tests/support/model_based/tests"; #[test] -fn main() { +fn model_based() { let tests = vec!["ICS02UpdateOKTest", "ICS02HeaderVerificationFailureTest"]; for test in tests { diff --git a/modules/tests/support/model_based/IBCTests.cfg b/modules/tests/support/model_based/IBCTests.cfg index 55958ebe13..0fde2766a1 100644 --- a/modules/tests/support/model_based/IBCTests.cfg +++ b/modules/tests/support/model_based/IBCTests.cfg @@ -5,10 +5,4 @@ CONSTANTS MaxConnectionsPerChain = 2 INIT Init -NEXT Next - -INVARIANTS - \* ICS02CreateOKTest - \* ICS02UpdateOKTest - \* ICS02ClientNotFoundTest - \* ICS02HeaderVerificationFailureTest \ No newline at end of file +NEXT Next \ No newline at end of file diff --git a/modules/tests/support/model_based/IBCTests.tla b/modules/tests/support/model_based/IBCTests.tla index e8d4926557..245461c3c9 100644 --- a/modules/tests/support/model_based/IBCTests.tla +++ b/modules/tests/support/model_based/IBCTests.tla @@ -2,21 +2,21 @@ EXTENDS IBC -ICS02CreateOK == +ICS02CreateOKTest == /\ actionOutcome = "ICS02CreateOK" -ICS02UpdateOK == +ICS02UpdateOKTest == /\ actionOutcome = "ICS02UpdateOK" -ICS02ClientNotFound == +ICS02ClientNotFoundTest == /\ actionOutcome = "ICS02ClientNotFound" -ICS02HeaderVerificationFailure == +ICS02HeaderVerificationFailureTest == /\ actionOutcome = "ICS02HeaderVerificationFailure" -ICS02CreateOKTest == ~ICS02CreateOK -ICS02UpdateOKTest == ~ICS02UpdateOK -ICS02ClientNotFoundTest == ~ICS02ClientNotFound -ICS02HeaderVerificationFailureTest == ~ICS02HeaderVerificationFailure +ICS02CreateOKTestNeg == ~ICS02CreateOKTest +ICS02UpdateOKTestNeg == ~ICS02UpdateOKTest +ICS02ClientNotFoundTestNeg == ~ICS02ClientNotFoundTest +ICS02HeaderVerificationFailureTestNeg == ~ICS02HeaderVerificationFailureTest =============================================================================== diff --git a/modules/tests/support/model_based/README.md b/modules/tests/support/model_based/README.md new file mode 100644 index 0000000000..980aadde05 --- /dev/null +++ b/modules/tests/support/model_based/README.md @@ -0,0 +1,37 @@ +## Model-based tests for IBC modules + +This directory contains the model-based tests for the IBC modules. They are "model-based" because they are generated from a `TLA+` model of the IBC modules (see [IBC.tla](IBC.tla)). + +Tests are `TLA+` assertions that describe the desired shape of the test. One of the assertions in [IBCTests.tla](IBCTests.tla) is the following: + +```tla +ICS02UpdateOKTest == + /\ actionOutcome = "ICS02UpdateOK" +``` + +This very simple assertion describes a test where the [model](IBC.tla) variable `actionOutcome` reaches the value `"ICS02UpdateOK"`, which occurs when a light client is successfully updated to a new height (see [ICS02.tla](ICS02.tla)). + +To generate a test from the `ICS02UpdateOKTest` assertion, we first define an invariant negating it: +```tla +ICS02UpdateOKTestNeg == ~ICS02UpdateOKTest +``` + +Then, we ask [`Apalache`](https://apalache.informal.systems), a model checker for `TLA+`, to prove it: + +```bash +apalache-mc check --inv=ICS02UpdateOKTestNeg IBCTests.tla +``` + +Because the invariant is wrong, `Apalache` will find a counterexample showing that it is indeed possible that a light-client is sucessfully updated to a new height. This counterexample is our test. + +### Current limitations + +The counterexamples currently produced by `Apalache` are not easy to parse and have traditionally required tools like [`jsonatr`](https://github.com/informalsystems/jsonatr). Fortunately, [that will change soon](https://github.com/informalsystems/apalache/issues/530), and `Apalache` will be able to produce counterexamples (like those in [tests/](tests/), which are currently generated manually) that can be easily mapped to Rust (see [step.rs](../../step.rs)). + +### Running the model-based tests + +The model-based tests can be run with the following command: + +```bash +cargo test -p ibc --features mocks -- model_based +``` From b6854ce33fcb99fe85300209906d0a9095ec906c Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Mon, 8 Feb 2021 23:52:33 +0100 Subject: [PATCH 41/52] Improve README --- modules/tests/support/model_based/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/tests/support/model_based/README.md b/modules/tests/support/model_based/README.md index 980aadde05..d2d0497221 100644 --- a/modules/tests/support/model_based/README.md +++ b/modules/tests/support/model_based/README.md @@ -26,7 +26,8 @@ Because the invariant is wrong, `Apalache` will find a counterexample showing th ### Current limitations -The counterexamples currently produced by `Apalache` are not easy to parse and have traditionally required tools like [`jsonatr`](https://github.com/informalsystems/jsonatr). Fortunately, [that will change soon](https://github.com/informalsystems/apalache/issues/530), and `Apalache` will be able to produce counterexamples (like those in [tests/](tests/), which are currently generated manually) that can be easily mapped to Rust (see [step.rs](../../step.rs)). +The counterexamples currently produced by `Apalache` are not easy to parse and have traditionally required tools like [`jsonatr`](https://github.com/informalsystems/jsonatr). Fortunately, [that will change soon](https://github.com/informalsystems/apalache/issues/530), and `Apalache` will be able to produce counterexamples like those in [tests/](tests/). +These are currently generated manually, but can be easily mapped to Rust (see [step.rs](../../step.rs)). ### Running the model-based tests From cd484a68151712f1afbf3d1533ab6f4c70580809 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 9 Feb 2021 00:03:07 +0100 Subject: [PATCH 42/52] Remove MBT intro --- modules/tests/README.md | 46 ----------------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 modules/tests/README.md diff --git a/modules/tests/README.md b/modules/tests/README.md deleted file mode 100644 index 3e4b1fb7cc..0000000000 --- a/modules/tests/README.md +++ /dev/null @@ -1,46 +0,0 @@ -### Model-based testing - -Let's say we want to test a system where: -- users can be created and deleted -- users have an id (say `u`) -- existing users have different ids - -#### i. Testing -- sequence of actions and expected outcomes - - `Test :: Vec<(Action, ActionOutcome)>` - - __test a)__: - - if I create user `u` (`action_1`), the operation should _succeed_ (`action_outcome_1`) - - if then I delete user `u` (`action_2`), the operation should _succeed_ (`action_outcome_2`) - - `Test = [(action_1, action_outcome_1), (action_2, action_outcome_2)]` - - __test b)__: - - if I create a user `u` (`action_1`), the operation should _succeed_ (`action_outcome_1`) - - if I then I create user `u` (`action_2`), the operation should _fail_ with an `ExistingUser` error (`action_outcome_2`) -- let's say that an action + its expected outcome is a `Step` - - `Step :: (Action, ActionOutcome)` - - `Test :: Vec` - - -#### ii. Model - -- what's a model? - - it's description of a system - - in our case, it's a __description of the system under test__ -- how do we write a model? - - in a language called `TLA+` -- what do we do with a model? - - e.g. prove invariants about it using a model checker - - example of an invariant: "existing users have different ids" - - model checkers for `TLA+`: `TLC` and `Apalache` - -#### How do we join i. and ii.? - -1. use the model to generate tests - - let's say we want a test where some user is deleted successfully - - define an invariant __negating the test__: - - "it's not possible that a user is deleted successfully" - - then we ask `Apalache` to prove it - - because the invariant is false, `Apalache` will find a counter example that shows it - - for example, it can find __test a)__ -2. monitor the model - - track each model action and its outcome - - define two `TLA+` variables: `action` and `actionOutcome` \ No newline at end of file From d69d15e9daaf7f7819623b8d83126b14e83aea0f Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 9 Feb 2021 00:08:24 +0100 Subject: [PATCH 43/52] new lines --- modules/tests/support/model_based/IBC.cfg | 2 +- modules/tests/support/model_based/IBCTests.cfg | 2 +- .../model_based/tests/ICS02HeaderVerificationFailureTest.json | 2 +- modules/tests/support/model_based/tests/ICS02UpdateOKTest.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/tests/support/model_based/IBC.cfg b/modules/tests/support/model_based/IBC.cfg index 25f40c9106..3bcce4efea 100644 --- a/modules/tests/support/model_based/IBC.cfg +++ b/modules/tests/support/model_based/IBC.cfg @@ -9,4 +9,4 @@ NEXT Next INVARIANTS TypeOK - ModelNeverErrors \ No newline at end of file + ModelNeverErrors diff --git a/modules/tests/support/model_based/IBCTests.cfg b/modules/tests/support/model_based/IBCTests.cfg index 0fde2766a1..8ce87d740b 100644 --- a/modules/tests/support/model_based/IBCTests.cfg +++ b/modules/tests/support/model_based/IBCTests.cfg @@ -5,4 +5,4 @@ CONSTANTS MaxConnectionsPerChain = 2 INIT Init -NEXT Next \ No newline at end of file +NEXT Next diff --git a/modules/tests/support/model_based/tests/ICS02HeaderVerificationFailureTest.json b/modules/tests/support/model_based/tests/ICS02HeaderVerificationFailureTest.json index dbad8fc3ba..e19671052a 100644 --- a/modules/tests/support/model_based/tests/ICS02HeaderVerificationFailureTest.json +++ b/modules/tests/support/model_based/tests/ICS02HeaderVerificationFailureTest.json @@ -46,4 +46,4 @@ } } } -] \ No newline at end of file +] diff --git a/modules/tests/support/model_based/tests/ICS02UpdateOKTest.json b/modules/tests/support/model_based/tests/ICS02UpdateOKTest.json index 309d6aa953..86f427443f 100644 --- a/modules/tests/support/model_based/tests/ICS02UpdateOKTest.json +++ b/modules/tests/support/model_based/tests/ICS02UpdateOKTest.json @@ -112,4 +112,4 @@ } } } -] \ No newline at end of file +] From 5e9dd634a3bc04c0f193ab3872b83dc00505d1d4 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 9 Feb 2021 12:38:58 +0100 Subject: [PATCH 44/52] Make MBT README more easily discoverable --- modules/tests/{support/model_based => }/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename modules/tests/{support/model_based => }/README.md (71%) diff --git a/modules/tests/support/model_based/README.md b/modules/tests/README.md similarity index 71% rename from modules/tests/support/model_based/README.md rename to modules/tests/README.md index d2d0497221..e3d19d2813 100644 --- a/modules/tests/support/model_based/README.md +++ b/modules/tests/README.md @@ -1,15 +1,15 @@ ## Model-based tests for IBC modules -This directory contains the model-based tests for the IBC modules. They are "model-based" because they are generated from a `TLA+` model of the IBC modules (see [IBC.tla](IBC.tla)). +This directory contains the model-based tests for the IBC modules. They are "model-based" because they are generated from a `TLA+` model of the IBC modules (see [IBC.tla](support/model_based/IBC.tla)). -Tests are `TLA+` assertions that describe the desired shape of the test. One of the assertions in [IBCTests.tla](IBCTests.tla) is the following: +Tests are `TLA+` assertions that describe the desired shape of the test. One of the assertions in [IBCTests.tla](support/model_based/IBCTests.tla) is the following: ```tla ICS02UpdateOKTest == /\ actionOutcome = "ICS02UpdateOK" ``` -This very simple assertion describes a test where the [model](IBC.tla) variable `actionOutcome` reaches the value `"ICS02UpdateOK"`, which occurs when a light client is successfully updated to a new height (see [ICS02.tla](ICS02.tla)). +This very simple assertion describes a test where the [model](support/model_based/IBC.tla) variable `actionOutcome` reaches the value `"ICS02UpdateOK"`, which occurs when a light client is successfully updated to a new height (see [ICS02.tla](support/model_based/ICS02.tla)). To generate a test from the `ICS02UpdateOKTest` assertion, we first define an invariant negating it: ```tla @@ -26,8 +26,8 @@ Because the invariant is wrong, `Apalache` will find a counterexample showing th ### Current limitations -The counterexamples currently produced by `Apalache` are not easy to parse and have traditionally required tools like [`jsonatr`](https://github.com/informalsystems/jsonatr). Fortunately, [that will change soon](https://github.com/informalsystems/apalache/issues/530), and `Apalache` will be able to produce counterexamples like those in [tests/](tests/). -These are currently generated manually, but can be easily mapped to Rust (see [step.rs](../../step.rs)). +The counterexamples currently produced by `Apalache` are not easy to parse and have traditionally required tools like [`jsonatr`](https://github.com/informalsystems/jsonatr). Fortunately, [that will change soon](https://github.com/informalsystems/apalache/issues/530), and `Apalache` will be able to produce counterexamples like those in [support/model_based/tests/](support/model_based/tests/). +These are currently generated manually, but can be easily mapped to Rust (see [step.rs](step.rs)). ### Running the model-based tests From fb20ce0161ff8ad7009b68af9ff8978e508f4ee9 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 9 Feb 2021 14:17:31 +0100 Subject: [PATCH 45/52] IBCTestExecutor: Map from ChainId (instead of String) to MockContext --- modules/tests/model_based.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index 906aa64a2f..1b411914e5 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -26,7 +26,7 @@ use tendermint::account::Id as AccountId; #[derive(Debug)] struct IBCTestExecutor { // mapping from chain identifier to its context - contexts: HashMap, + contexts: HashMap, } impl IBCTestExecutor { @@ -39,9 +39,10 @@ impl IBCTestExecutor { /// 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( - ChainId::new(chain_id.clone(), Self::epoch()), + chain_id.clone(), HostType::Mock, max_history_size, Height::new(Self::epoch(), initial_height), @@ -51,17 +52,17 @@ impl IBCTestExecutor { /// 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 { + fn chain_context(&self, chain_id: String) -> &MockContext { self.contexts - .get(chain_id) + .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 { + fn chain_context_mut(&mut self, chain_id: String) -> &mut MockContext { self.contexts - .get_mut(chain_id) + .get_mut(&Self::chain_id(chain_id)) .expect("chain context should have been initialized") } @@ -92,6 +93,10 @@ impl IBCTestExecutor { .clone() } + fn chain_id(chain_id: String) -> ChainId { + ChainId::new(chain_id, Self::epoch()) + } + // TODO: this is sometimes called version/revision number but seems // unrelated with the `Version` type; is that so? fn epoch() -> u64 { @@ -130,7 +135,7 @@ impl IBCTestExecutor { /// Check that chain heights match the ones in the model. fn check_chain_heights(&self, chains: HashMap) -> bool { chains.into_iter().all(|(chain_id, chain)| { - let ctx = self.chain_context(&chain_id); + let ctx = self.chain_context(chain_id); ctx.query_latest_height() == Self::height(chain.height) }) } @@ -170,7 +175,7 @@ impl modelator::TestExecutor for IBCTestExecutor { .expect("create client action should have a client height"); // get chain's context - let ctx = self.chain_context_mut(&chain_id); + let ctx = self.chain_context_mut(chain_id); // create ICS26 message and deliver it let msg = ICS26Envelope::ICS2Msg(ClientMsg::CreateClient(MsgCreateAnyClient { @@ -205,7 +210,7 @@ impl modelator::TestExecutor for IBCTestExecutor { .expect("update client action should have a client height"); // get chain's context - let ctx = self.chain_context_mut(&chain_id); + let ctx = self.chain_context_mut(chain_id); // create ICS26 message and deliver it let msg = ICS26Envelope::ICS2Msg(ClientMsg::UpdateClient(MsgUpdateAnyClient { From 75db6bb9621e3a6db269100332e137d3a526a822 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 9 Feb 2021 14:20:24 +0100 Subject: [PATCH 46/52] s/epoch/revision --- modules/tests/model_based.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index 1b411914e5..8927aa78dc 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -45,7 +45,7 @@ impl IBCTestExecutor { chain_id.clone(), HostType::Mock, max_history_size, - Height::new(Self::epoch(), initial_height), + Height::new(Self::revision(), initial_height), ); assert!(self.contexts.insert(chain_id, ctx).is_none()); } @@ -94,12 +94,10 @@ impl IBCTestExecutor { } fn chain_id(chain_id: String) -> ChainId { - ChainId::new(chain_id, Self::epoch()) + ChainId::new(chain_id, Self::revision()) } - // TODO: this is sometimes called version/revision number but seems - // unrelated with the `Version` type; is that so? - fn epoch() -> u64 { + fn revision() -> u64 { 0 } @@ -109,7 +107,7 @@ impl IBCTestExecutor { } fn height(height: u64) -> Height { - Height::new(Self::epoch(), height) + Height::new(Self::revision(), height) } fn mock_header(height: u64) -> MockHeader { From a10286398eee2ff375e727720b716d1293bcf12c Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 9 Feb 2021 15:45:43 +0100 Subject: [PATCH 47/52] Move from eyre to thiserror --- modules/Cargo.toml | 1 - modules/tests/model_based.rs | 6 ++--- modules/tests/modelator.rs | 52 +++++++++++++++++++++++++----------- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/modules/Cargo.toml b/modules/Cargo.toml index c84b1ff5b9..1a4a2c29b9 100644 --- a/modules/Cargo.toml +++ b/modules/Cargo.toml @@ -54,6 +54,5 @@ version = "=0.18.0" optional = true [dev-dependencies] -eyre = "0.6.5" tokio = { version = "1.0", features = ["macros"] } tendermint-testgen = { version = "=0.18.0" } # Needed for generating (synthetic) light blocks. diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index 8927aa78dc..81e5d64d3a 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -258,11 +258,11 @@ fn model_based() { let tests = vec!["ICS02UpdateOKTest", "ICS02HeaderVerificationFailureTest"]; for test in tests { - let path = format!("{}/{}.json", TESTS_DIR, test); - let executor = IBCTestExecutor::new(); + let test_path = format!("{}/{}.json", TESTS_DIR, test); + let test_executor = IBCTestExecutor::new(); // we should be able to just return the `Result` once the following issue // is fixed: https://github.com/rust-lang/rust/issues/43301 - if let Err(e) = modelator::test_driver(executor, path) { + if let Err(e) = modelator::test(test_executor, &test_path) { panic!("{:?}", e); } } diff --git a/modules/tests/modelator.rs b/modules/tests/modelator.rs index 6f0f9b7b22..61e0c3e95f 100644 --- a/modules/tests/modelator.rs +++ b/modules/tests/modelator.rs @@ -1,29 +1,50 @@ -use eyre::{eyre, Context, Result}; use serde::de::DeserializeOwned; use std::fmt::Debug; use std::fs::File; use std::io::BufReader; -use std::path::Path; +use thiserror::Error; +#[derive(Error, Debug)] +pub enum ModelatorError { + #[error("test '{path}' not found: {error}")] + TestNotFound { path: String, error: String }, + #[error("test '{path}' could not be deserialized: {error}")] + InvalidTest { path: String, error: String }, + #[error("test '{path}' failed on step {step_index}/{step_count}:\nstep:\n{step:#?}\nexecutor:\n{executor:#?}")] + FailedTest { + path: String, + step_index: usize, + step_count: usize, + step: Step, + executor: Executor, + }, +} pub trait TestExecutor { fn initial_step(&mut self, step: S) -> bool; fn next_step(&mut self, step: S) -> bool; } -pub fn test_driver(mut executor: Executor, path: P) -> Result<()> +pub fn test( + mut executor: Executor, + path: &str, +) -> Result<(), ModelatorError> where - Executor: TestExecutor + Debug, - Step: DeserializeOwned + Debug + Clone, - P: AsRef, + Executor: TestExecutor + Debug, + State: DeserializeOwned + Debug + Clone, { // open test file - let file = File::open(path.as_ref()) - .wrap_err_with(|| format!("test {:?} not found.", path.as_ref()))?; + let file = File::open(path).map_err(|error| ModelatorError::TestNotFound { + path: path.to_string(), + error: error.to_string(), + })?; let reader = BufReader::new(file); // parse test file - let steps: Vec = serde_json::de::from_reader(reader) - .wrap_err_with(|| format!("test {:?} could not be deserialized", path.as_ref()))?; + let steps: Vec = + serde_json::de::from_reader(reader).map_err(|error| ModelatorError::InvalidTest { + path: path.to_string(), + error: error.to_string(), + })?; let step_count = steps.len(); for (i, step) in steps.into_iter().enumerate() { @@ -35,14 +56,13 @@ where }; if !ok { - return Err(eyre!( - "test {:?} failed on step {}/{}:\n{:#?}\n\nexecutor:\n{:#?}", - path.as_ref(), - i + 1, + return Err(ModelatorError::FailedTest { + path: path.to_string(), + step_index: i + 1, step_count, step, - executor - )); + executor, + }); } } Ok(()) From 2e5d6de82f69a14046b8555f95c706701b140415 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 9 Feb 2021 16:33:56 +0100 Subject: [PATCH 48/52] Improve README --- modules/tests/README.md | 31 +++++++++++++++++-- modules/tests/support/model_based/IBC.cfg | 1 - .../tests/support/model_based/IBCTests.cfg | 1 - 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/modules/tests/README.md b/modules/tests/README.md index e3d19d2813..23bab24365 100644 --- a/modules/tests/README.md +++ b/modules/tests/README.md @@ -1,27 +1,52 @@ ## Model-based tests for IBC modules +### The model + This directory contains the model-based tests for the IBC modules. They are "model-based" because they are generated from a `TLA+` model of the IBC modules (see [IBC.tla](support/model_based/IBC.tla)). -Tests are `TLA+` assertions that describe the desired shape of the test. One of the assertions in [IBCTests.tla](support/model_based/IBCTests.tla) is the following: +To instantiate the model, we define in [IBC.cfg](support/model_based/IBC.cfg) the following model constants: + - `ChainIds = {"chain-A", "chain-B"}`, indicating that two chains, `chain-A` and `chain-B`, will be created + - `MaxClientsPerChain = 1`, indicating that at most 1 client per chain will be created + - `MaxClientHeight = 2`, indicating that clients will reach at most height 2 + +The [IBC.cfg](support/model_based/IBC.cfg) file also defines two simple invariants: +```tla +INVARIANTS + TypeOK + ModelNeverErrors +``` + +Then, we can ask [`Apalache`](https://apalache.informal.systems), a model checker for `TLA+`, to check that these invariants hold: +```bash +apalache-mc check --inv=ICS02UpdateOKTestNeg IBC.tla +``` + +The above command automatically reads what we have defined in [IBC.cfg](support/model_based/IBC.cfg). + +### The tests + +Tests are `TLA+` assertions that describe the desired shape of the test (see [IBCTests.tla](support/model_based/IBCTests.tla)). One of the assertions in [IBCTests.tla](support/model_based/IBCTests.tla) is the following: ```tla ICS02UpdateOKTest == /\ actionOutcome = "ICS02UpdateOK" ``` -This very simple assertion describes a test where the [model](support/model_based/IBC.tla) variable `actionOutcome` reaches the value `"ICS02UpdateOK"`, which occurs when a light client is successfully updated to a new height (see [ICS02.tla](support/model_based/ICS02.tla)). +This very simple assertion describes a test where the [model](support/model_based/IBC.tla) variable `actionOutcome` reaches the value `"ICS02UpdateOK"`, which occurs when a client is successfully updated to a new height (see [ICS02.tla](support/model_based/ICS02.tla)). To generate a test from the `ICS02UpdateOKTest` assertion, we first define an invariant negating it: ```tla ICS02UpdateOKTestNeg == ~ICS02UpdateOKTest ``` -Then, we ask [`Apalache`](https://apalache.informal.systems), a model checker for `TLA+`, to prove it: +Then, we ask [`Apalache`], to prove it: ```bash apalache-mc check --inv=ICS02UpdateOKTestNeg IBCTests.tla ``` +(Again, the above command automatically reads what we have defined in [IBCTests.cfg](support/model_based/IBCTests.cfg).) + Because the invariant is wrong, `Apalache` will find a counterexample showing that it is indeed possible that a light-client is sucessfully updated to a new height. This counterexample is our test. ### Current limitations diff --git a/modules/tests/support/model_based/IBC.cfg b/modules/tests/support/model_based/IBC.cfg index 3bcce4efea..b9ed63584a 100644 --- a/modules/tests/support/model_based/IBC.cfg +++ b/modules/tests/support/model_based/IBC.cfg @@ -2,7 +2,6 @@ CONSTANTS ChainIds = {"chain-A", "chain-B"} MaxClientsPerChain = 1 MaxClientHeight = 2 - MaxConnectionsPerChain = 2 INIT Init NEXT Next diff --git a/modules/tests/support/model_based/IBCTests.cfg b/modules/tests/support/model_based/IBCTests.cfg index 8ce87d740b..a499c75463 100644 --- a/modules/tests/support/model_based/IBCTests.cfg +++ b/modules/tests/support/model_based/IBCTests.cfg @@ -2,7 +2,6 @@ CONSTANTS ChainIds = {"chain-A", "chain-B"} MaxClientsPerChain = 1 MaxClientHeight = 2 - MaxConnectionsPerChain = 2 INIT Init NEXT Next From dfb683ddde7bff90236621189a2db3177a2c9711 Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 9 Feb 2021 16:38:09 +0100 Subject: [PATCH 49/52] Improve README --- modules/tests/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/tests/README.md b/modules/tests/README.md index 23bab24365..da1c7a3546 100644 --- a/modules/tests/README.md +++ b/modules/tests/README.md @@ -47,7 +47,7 @@ apalache-mc check --inv=ICS02UpdateOKTestNeg IBCTests.tla (Again, the above command automatically reads what we have defined in [IBCTests.cfg](support/model_based/IBCTests.cfg).) -Because the invariant is wrong, `Apalache` will find a counterexample showing that it is indeed possible that a light-client is sucessfully updated to a new height. This counterexample is our test. +Because the invariant is wrong, `Apalache` will find a counterexample showing that it is indeed possible that a client is sucessfully updated to a new height. This counterexample is our test. ### Current limitations From 7e24841f3d61eafe83ed76f745deca05fb54508d Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Tue, 9 Feb 2021 16:44:10 +0100 Subject: [PATCH 50/52] Improve arguments order in modelator::test --- modules/tests/model_based.rs | 6 +++--- modules/tests/modelator.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/tests/model_based.rs b/modules/tests/model_based.rs index 81e5d64d3a..5f11385afd 100644 --- a/modules/tests/model_based.rs +++ b/modules/tests/model_based.rs @@ -258,11 +258,11 @@ fn model_based() { let tests = vec!["ICS02UpdateOKTest", "ICS02HeaderVerificationFailureTest"]; for test in tests { - let test_path = format!("{}/{}.json", TESTS_DIR, test); - let test_executor = IBCTestExecutor::new(); + let test = format!("{}/{}.json", TESTS_DIR, test); + let executor = IBCTestExecutor::new(); // we should be able to just return the `Result` once the following issue // is fixed: https://github.com/rust-lang/rust/issues/43301 - if let Err(e) = modelator::test(test_executor, &test_path) { + if let Err(e) = modelator::test(&test, executor) { panic!("{:?}", e); } } diff --git a/modules/tests/modelator.rs b/modules/tests/modelator.rs index 61e0c3e95f..cbd15b18e3 100644 --- a/modules/tests/modelator.rs +++ b/modules/tests/modelator.rs @@ -25,8 +25,8 @@ pub trait TestExecutor { } pub fn test( - mut executor: Executor, path: &str, + mut executor: Executor, ) -> Result<(), ModelatorError> where Executor: TestExecutor + Debug, From 5d6bf08c7fcfe4d9040c661f0f044ebf641ea31f Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Wed, 10 Feb 2021 15:00:53 +0100 Subject: [PATCH 51/52] Improve chain identifiers --- modules/tests/support/model_based/IBC.cfg | 2 +- .../tests/support/model_based/IBCTests.cfg | 2 +- .../ICS02HeaderVerificationFailureTest.json | 16 ++++---- .../model_based/tests/ICS02UpdateOKTest.json | 40 +++++++++---------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/modules/tests/support/model_based/IBC.cfg b/modules/tests/support/model_based/IBC.cfg index b9ed63584a..73976184c4 100644 --- a/modules/tests/support/model_based/IBC.cfg +++ b/modules/tests/support/model_based/IBC.cfg @@ -1,5 +1,5 @@ CONSTANTS - ChainIds = {"chain-A", "chain-B"} + ChainIds = {"chainA-0", "chainB-0"} MaxClientsPerChain = 1 MaxClientHeight = 2 diff --git a/modules/tests/support/model_based/IBCTests.cfg b/modules/tests/support/model_based/IBCTests.cfg index a499c75463..e4c28bc288 100644 --- a/modules/tests/support/model_based/IBCTests.cfg +++ b/modules/tests/support/model_based/IBCTests.cfg @@ -1,5 +1,5 @@ CONSTANTS - ChainIds = {"chain-A", "chain-B"} + ChainIds = {"chainA-0", "chainB-0"} MaxClientsPerChain = 1 MaxClientHeight = 2 diff --git a/modules/tests/support/model_based/tests/ICS02HeaderVerificationFailureTest.json b/modules/tests/support/model_based/tests/ICS02HeaderVerificationFailureTest.json index e19671052a..32a85c138a 100644 --- a/modules/tests/support/model_based/tests/ICS02HeaderVerificationFailureTest.json +++ b/modules/tests/support/model_based/tests/ICS02HeaderVerificationFailureTest.json @@ -5,43 +5,43 @@ }, "actionOutcome": "None", "chains": { - "chain-A": { + "chainA-0": { "height": 0 }, - "chain-B": { + "chainB-0": { "height": 0 } } }, { "action": { - "chainId": "chain-B", + "chainId": "chainB-0", "clientHeight": 1, "type": "ICS02CreateClient" }, "actionOutcome": "ICS02CreateOK", "chains": { - "chain-A": { + "chainA-0": { "height": 0 }, - "chain-B": { + "chainB-0": { "height": 1 } } }, { "action": { - "chainId": "chain-B", + "chainId": "chainB-0", "clientHeight": 1, "clientId": 0, "type": "ICS02UpdateClient" }, "actionOutcome": "ICS02HeaderVerificationFailure", "chains": { - "chain-A": { + "chainA-0": { "height": 0 }, - "chain-B": { + "chainB-0": { "height": 1 } } diff --git a/modules/tests/support/model_based/tests/ICS02UpdateOKTest.json b/modules/tests/support/model_based/tests/ICS02UpdateOKTest.json index 86f427443f..5275bfcda3 100644 --- a/modules/tests/support/model_based/tests/ICS02UpdateOKTest.json +++ b/modules/tests/support/model_based/tests/ICS02UpdateOKTest.json @@ -5,109 +5,109 @@ }, "actionOutcome": "None", "chains": { - "chain-A": { + "chainA-0": { "height": 0 }, - "chain-B": { + "chainB-0": { "height": 0 } } }, { "action": { - "chainId": "chain-B", + "chainId": "chainB-0", "clientHeight": 1, "type": "ICS02CreateClient" }, "actionOutcome": "ICS02CreateOK", "chains": { - "chain-A": { + "chainA-0": { "height": 0 }, - "chain-B": { + "chainB-0": { "height": 1 } } }, { "action": { - "chainId": "chain-B", + "chainId": "chainB-0", "clientHeight": 2, "clientId": 0, "type": "ICS02UpdateClient" }, "actionOutcome": "ICS02UpdateOK", "chains": { - "chain-A": { + "chainA-0": { "height": 0 }, - "chain-B": { + "chainB-0": { "height": 2 } } }, { "action": { - "chainId": "chain-A", + "chainId": "chainA-0", "clientHeight": 1, "type": "ICS02CreateClient" }, "actionOutcome": "ICS02CreateOK", "chains": { - "chain-A": { + "chainA-0": { "height": 1 }, - "chain-B": { + "chainB-0": { "height": 2 } } }, { "action": { - "chainId": "chain-A", + "chainId": "chainA-0", "clientHeight": 2, "clientId": 0, "type": "ICS02UpdateClient" }, "actionOutcome": "ICS02UpdateOK", "chains": { - "chain-A": { + "chainA-0": { "height": 2 }, - "chain-B": { + "chainB-0": { "height": 2 } } }, { "action": { - "chainId": "chain-A", + "chainId": "chainA-0", "clientHeight": 1, "type": "ICS02CreateClient" }, "actionOutcome": "ICS02CreateOK", "chains": { - "chain-A": { + "chainA-0": { "height": 3 }, - "chain-B": { + "chainB-0": { "height": 2 } } }, { "action": { - "chainId": "chain-A", + "chainId": "chainA-0", "clientHeight": 2, "clientId": 1, "type": "ICS02UpdateClient" }, "actionOutcome": "ICS02UpdateOK", "chains": { - "chain-A": { + "chainA-0": { "height": 4 }, - "chain-B": { + "chainB-0": { "height": 2 } } From 60e4aba3fba998c10243058095e54e97a7a7cfbb Mon Sep 17 00:00:00 2001 From: Vitor Enes Date: Wed, 10 Feb 2021 20:06:51 +0100 Subject: [PATCH 52/52] Improve README --- modules/tests/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/tests/README.md b/modules/tests/README.md index da1c7a3546..54e6491585 100644 --- a/modules/tests/README.md +++ b/modules/tests/README.md @@ -39,7 +39,7 @@ To generate a test from the `ICS02UpdateOKTest` assertion, we first define an in ICS02UpdateOKTestNeg == ~ICS02UpdateOKTest ``` -Then, we ask [`Apalache`], to prove it: +Then, we ask `Apalache`, to prove it: ```bash apalache-mc check --inv=ICS02UpdateOKTestNeg IBCTests.tla