Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Model-based tests for ICS02 #601

Merged
merged 57 commits into from
Feb 12, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
413d3f4
Add ICS02 model
vitorenesduarte Feb 2, 2021
065631e
Add MBT test driver
vitorenesduarte Feb 2, 2021
b98c7d3
Add ICS02TestExecutor
vitorenesduarte Feb 2, 2021
6685cc1
Add another apalache counterexample
vitorenesduarte Feb 2, 2021
f863486
Fix ICS02.tla: client counter starts at 0
vitorenesduarte Feb 2, 2021
cd0d0a6
Check for errors in MockContext.deliver
vitorenesduarte Feb 2, 2021
02c020b
Handle errors in MBT tests
vitorenesduarte Feb 2, 2021
a5d06c5
Remove SyntheticTendermint mock context
vitorenesduarte Feb 2, 2021
356c6d2
More idiomatic check_next_state
vitorenesduarte Feb 2, 2021
6ab8611
Buffered file reads
vitorenesduarte Feb 3, 2021
490d77b
Make extract_handler_error_kind generic over the IBC handler
vitorenesduarte Feb 3, 2021
92b843c
Support multiple chains in MBT
vitorenesduarte Feb 3, 2021
d57cf41
Make eyre a dev-dependency
vitorenesduarte Feb 3, 2021
f4dbe00
s/ICS0/IBC on TLA files
vitorenesduarte Feb 3, 2021
20867a5
Initial support for conn open init
vitorenesduarte Feb 3, 2021
e5e2f7b
Start connection and channel identifiers at 0
vitorenesduarte Feb 3, 2021
b9ca90a
Add conn open init to TLA spec
vitorenesduarte Feb 3, 2021
f3cd434
Represent clients with a record in TLA spec
vitorenesduarte Feb 3, 2021
4167d29
Finish connection open init
vitorenesduarte Feb 3, 2021
7755fec
s/state/step
vitorenesduarte Feb 3, 2021
70e70f6
Minimize diff
vitorenesduarte Feb 4, 2021
44747d3
Merge branch 'master' into vitor/ics02_mbt
vitorenesduarte Feb 4, 2021
41d34d3
Modularize TLA spec
vitorenesduarte Feb 4, 2021
1f9881c
TLA format convention
vitorenesduarte Feb 4, 2021
e2605bb
s/Null/None
vitorenesduarte Feb 4, 2021
5b98a7e
Sketch conn open try; Model chain's height
vitorenesduarte Feb 4, 2021
244efa6
Bound model space
vitorenesduarte Feb 4, 2021
7d9202a
Only update chain height upon success
vitorenesduarte Feb 4, 2021
28879f0
Check that chain heights match the ones in the model
vitorenesduarte Feb 4, 2021
a870537
Sketch conn open try
vitorenesduarte Feb 4, 2021
a533def
Sketch conn open try
vitorenesduarte Feb 4, 2021
1cdb02a
Model missing connections and connection mismatches in conn open try
vitorenesduarte Feb 4, 2021
0186a1c
Trigger bug in conn open try
vitorenesduarte Feb 4, 2021
f777144
Merge branch 'master' into vitor/ics02_mbt
vitorenesduarte Feb 4, 2021
6b6cd43
Go back to previous way of generating connection and channel ids
vitorenesduarte Feb 4, 2021
6186bec
Disable failing MBT test
vitorenesduarte Feb 4, 2021
a7b50d7
Fix panic in conn open try when no connection id is provided
vitorenesduarte Feb 5, 2021
5b66aac
ICS02TestExecutor -> IBCTestExecutor
vitorenesduarte Feb 5, 2021
095433e
Merge branch 'vitor/conn_open_try' into vitor/ics02_mbt
vitorenesduarte Feb 5, 2021
f2194cf
Failing MBT test now passes with #615
vitorenesduarte Feb 5, 2021
7bdd3b6
Add notes on MBT
vitorenesduarte Feb 8, 2021
9ee48bd
Remove ICS02
vitorenesduarte Feb 8, 2021
035651e
Add README
vitorenesduarte Feb 8, 2021
b6854ce
Improve README
vitorenesduarte Feb 8, 2021
cd484a6
Remove MBT intro
vitorenesduarte Feb 8, 2021
d69d15e
new lines
vitorenesduarte Feb 8, 2021
b4aea95
Merge branch 'master' into vitor/ics02_mbt
vitorenesduarte Feb 9, 2021
5e9dd63
Make MBT README more easily discoverable
vitorenesduarte Feb 9, 2021
fb20ce0
IBCTestExecutor: Map from ChainId (instead of String) to MockContext
vitorenesduarte Feb 9, 2021
75db6bb
s/epoch/revision
vitorenesduarte Feb 9, 2021
a102863
Move from eyre to thiserror
vitorenesduarte Feb 9, 2021
2e5d6de
Improve README
vitorenesduarte Feb 9, 2021
dfb683d
Improve README
vitorenesduarte Feb 9, 2021
7e24841
Improve arguments order in modelator::test
vitorenesduarte Feb 9, 2021
5d6bf08
Improve chain identifiers
vitorenesduarte Feb 10, 2021
60e4aba
Improve README
vitorenesduarte Feb 10, 2021
3a991e7
Merge branch 'master' into vitor/ics02_mbt
vitorenesduarte Feb 11, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions modules/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ dyn-clonable = "0.9.0"
regex = "1"
bech32 = "0.7.2"

eyre = "0.6.5"
vitorenesduarte marked this conversation as resolved.
Show resolved Hide resolved

[dependencies.tendermint]
version = "=0.18.0"

Expand Down
6 changes: 3 additions & 3 deletions modules/src/ics02_client/msgs/create_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
vitorenesduarte marked this conversation as resolved.
Show resolved Hide resolved
pub consensus_state: AnyConsensusState,
pub signer: AccountId,
}

impl MsgCreateAnyClient {
Expand Down
2 changes: 2 additions & 0 deletions modules/tests/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
test:
cargo t --features mocks -- --nocapture main
198 changes: 198 additions & 0 deletions modules/tests/model_based.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
mod modelator;
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::{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;
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;

#[derive(Debug)]
struct ICS02TestExecutor {
version: u64,
ctx: MockContext,
}

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 }
}
}

impl modelator::TestExecutor<State> for ICS02TestExecutor {
fn check_initial_state(&mut self, state: State) -> bool {
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!("unexpected action type"),
ActionType::CreateClient => {
// get action parameters
let height = state
.action
.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)));
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,
}));
let result = self.ctx.deliver(msg);

// check the expected outcome: client create always succeeds
assert_eq!(
state.action_outcome,
ActionOutcome::CreateOK,
"unexpected action outcome"
);
result.is_ok()
}
ActionType::UpdateClient => {
// 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 and deliver it
let msg = ICS26Envelope::ICS2Msg(ClientMsg::UpdateClient(MsgUpdateAnyClient {
client_id: client_id.clone(),
header,
signer,
}));
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
result.is_ok()
vitorenesduarte marked this conversation as resolved.
Show resolved Hide resolved
}
ActionOutcome::UpdateClientNotFound => {
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);
handler_error_kind == ICS02ErrorKind::HeaderVerificationFailure
vitorenesduarte marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
}
}

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_ics02_handler_error_kind(&self, 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::<ICS26Error>()
.expect("ICS18 source should be an ICS26 error");
assert!(matches!(
ics26_error.kind(),
ICS26ErrorKind::HandlerRaisedError,
));
ics26_error
.source()
.expect("expected source in ICS26 error")
.downcast_ref::<ICS02Error>()
.expect("ICS26 source should be an ICS02 error")
.kind()
.clone()
}
}

const TESTS_DIR: &str = "tests/support/model_based";

#[test]
fn main() {
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);
}
}
}
52 changes: 52 additions & 0 deletions modules/tests/modelator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use eyre::{eyre, Context, Result};
adizere marked this conversation as resolved.
Show resolved Hide resolved
use serde::de::DeserializeOwned;
use std::fmt::Debug;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;

pub trait TestExecutor<S> {
fn check_initial_state(&mut self, state: S) -> bool;

fn check_next_state(&mut self, state: S) -> bool;
}

pub fn test_driver<E, S, P>(mut executor: E, path: P) -> Result<()>
where
E: TestExecutor<S> + Debug,
S: DeserializeOwned + Debug + Clone,
P: AsRef<Path>,
{
// 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<S> = 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));
}
} else {
println!("WARNING: test file {:?} had 0 states", path.as_ref());
return Ok(());
}

// check all the remaining states
for state in states {
if !executor.check_next_state(state.clone()) {
return Err(eyre!(
vitorenesduarte marked this conversation as resolved.
Show resolved Hide resolved
"check failed on state:\n{:#?}\n\nexecutor:\n{:#?}",
state,
executor
));
}
}
Ok(())
}
37 changes: 37 additions & 0 deletions modules/tests/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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<u64>,

pub height: Option<u64>,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
pub enum ActionType {
Null,
CreateClient,
UpdateClient,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
pub enum ActionOutcome {
Null,
CreateOK,
UpdateOK,
UpdateClientNotFound,
UpdateHeightVerificationFailure,
}
9 changes: 9 additions & 0 deletions modules/tests/support/model_based/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
*.out
states/
x/
detailed.log
bfs.csv
log0.smt
profile-rules.txt
counterexample.json
counterexample.tla
10 changes: 10 additions & 0 deletions modules/tests/support/model_based/ICS02.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CONSTANTS
MaxClients = 5
MaxHeight = 5

INIT Init
NEXT Next

INVARIANTS
TypeOK
ModelNeverErrors
Loading