From 788c36be9e14725c542bd586b4fe4593edb3ca80 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Wed, 19 Aug 2020 20:12:03 +0200 Subject: [PATCH] ADR 003: Handler implementation (#197) * Add doc comments and error types to ICS 02 module * Rename ics02_client::client module to ics02_client::raw * Replace message traits with structs * Fix formatting error * Add implementation specific error kind * Fixup client message structs definitions * Fix clippy warnings * Add basic handler definitions * Add initial implementation of CreateClient handler * Add implementation of ClientDef for Tendermint * Re-use existing traits * Add valid test for create_client handler * Add tests for case where client already exists * WIP: Update client handler * Add initial proposal for ADR 003 * Rename file to adr-003-handler-implementation.md * Replace "handler" with "message processor" in plain text * Formatting * Fix create client handler tests * Rename module handler to dispatch * Formatting * Add sketch of update client processor * Move mocks into their own module * Remove genericity over client def in client submodule * Lift chain specific data into an enum * Update ADR to match new handling of chain-specific data * Fix the connection specifics in ADR * Added Tendermint to the new "Any*" enums Added client state to MsgCreateAnyClient, result, keeper, etc Test for concrete tendermint MsgCreateClient Updated TM client and consensus states, MsgCreateClient Extract client and consensus states from MsgCreateClient for TM Extract consensus state from MsgUpdateClient for TM Co-authored-by: Adi Seredinschi Co-authored-by: Anca Zamfir --- .../adr-003-handler-implementation.md | 679 ++++++++++++++++++ modules/Cargo.toml | 1 + modules/src/handler.rs | 95 +++ modules/src/ics02_client/client_def.rs | 110 +++ modules/src/ics02_client/client_type.rs | 6 +- modules/src/ics02_client/error.rs | 17 +- modules/src/ics02_client/events.rs | 5 + modules/src/ics02_client/handler.rs | 102 +++ .../src/ics02_client/handler/create_client.rs | 235 ++++++ .../src/ics02_client/handler/update_client.rs | 110 +++ modules/src/ics02_client/header.rs | 3 +- modules/src/ics02_client/mocks.rs | 185 +++++ modules/src/ics02_client/mod.rs | 5 +- modules/src/ics02_client/msgs.rs | 40 +- .../src/ics02_client/{client.rs => raw.rs} | 0 modules/src/ics02_client/state.rs | 22 +- modules/src/ics03_connection/msgs.rs | 8 +- modules/src/ics04_channel/channel.rs | 4 +- modules/src/ics04_channel/msgs.rs | 12 +- modules/src/ics07_tendermint/client_def.rs | 13 + modules/src/ics07_tendermint/client_state.rs | 89 +-- .../src/ics07_tendermint/consensus_state.rs | 17 +- modules/src/ics07_tendermint/header.rs | 31 +- modules/src/ics07_tendermint/mod.rs | 1 + .../ics07_tendermint/msgs/create_client.rs | 72 +- .../ics07_tendermint/msgs/update_client.rs | 21 +- modules/src/ics23_commitment/mod.rs | 6 + modules/src/lib.rs | 3 +- relayer-cli/src/commands/query/channel.rs | 2 +- relayer-cli/src/commands/query/client.rs | 8 +- relayer-cli/src/commands/query/connection.rs | 6 +- relayer-cli/tests/acceptance.rs | 2 +- relayer-cli/tests/integration.rs | 2 +- 33 files changed, 1744 insertions(+), 168 deletions(-) create mode 100644 docs/architecture/adr-003-handler-implementation.md create mode 100644 modules/src/handler.rs create mode 100644 modules/src/ics02_client/client_def.rs create mode 100644 modules/src/ics02_client/handler.rs create mode 100644 modules/src/ics02_client/handler/create_client.rs create mode 100644 modules/src/ics02_client/handler/update_client.rs create mode 100644 modules/src/ics02_client/mocks.rs rename modules/src/ics02_client/{client.rs => raw.rs} (100%) create mode 100644 modules/src/ics07_tendermint/client_def.rs diff --git a/docs/architecture/adr-003-handler-implementation.md b/docs/architecture/adr-003-handler-implementation.md new file mode 100644 index 0000000000..f0f5ada59a --- /dev/null +++ b/docs/architecture/adr-003-handler-implementation.md @@ -0,0 +1,679 @@ +# ADR 003: IBC protocol implementation + +## Changelog +* 2020-08-06: Initial proposal +* 2020-08-10: Rename Handler to Message Processor +* 2020-08-14: Revamp definition of chain-specific messages, readers and keepers + +## Reader + +> This section contains all the context one needs to understand the current state, and why there is a problem. It should be as succinct as possible and introduce the high level idea behind the solution. + +TODO + +## Decision + +In this ADR, we provide recommendations for implementing the IBC message processing logic within the `ibc-rs` crate. +Concepts are introduced in the order given by a topological sort of their dependencies on each other. + +### Events + +IBC message processors must be able to emit events which will then be broadcasted via the node's pub/sub mechanism, +and eventually picked up by the IBC relayer. + +A generic interface for events is provided below, where an event is represented +as a pair of an event type and a list of attributes. An attribute is simply a pair +of a key and a value, both represented as strings. + +Here is the [list of all IBB-related events][events], as seen by the relayer. +Because the structure of these events do not match the ones which are emitted by the IBC message processors, +each IBC submodule should defined its own event type and associated variants. + +[events]: https://github.com/informalsystems/ibc-rs/blob/bf84a73ef7b3d5e9a434c9af96165997382dcc9d/modules/src/events.rs#L15-L43 + +```rust +pub struct Attribute { + key: String, + value: String, +} + +impl Attribute { + pub fn new(key: String, value: String) -> Self; +} + +pub enum EventType { + Message, + Custom(String), +} + +pub struct Event { + typ: EventType, + attributes: Vec, +} + +impl Event { + pub fn new(typ: EventType, attrs: Vec<(String, String)>) -> Self; +} +``` + +### Logging + +IBC message processors must be able to log information for introspectability and ease of debugging. +A message processor can output multiple log records, which are expressed as a pair of a status and a +log line. The interface for emitting log records is described in the next section. + +```rust +pub enum LogStatus { + Success, + Info, + Warning, + Error, +} + +pub struct Log { + status: LogStatus, + body: String, +} + +impl Log { + fn success(msg: impl Display) -> Self; + fn info(msg: impl Display) -> Self; + fn warning(msg: impl Display) -> Self; + fn error(msg: impl Display) -> Self; +} +``` + +### Message processor output + +IBC message processors must be able to return arbitrary data, together with events and log records, as descibed above. +As a message processor may fail, it is necessary to keep track of errors. + +To this end, we introduce a type for the return value of a message processor: + +```rust +pub type HandlerResult = Result, E>; + +pub struct HandlerOutput { + pub result: T, + pub log: Vec, + pub events: Vec, +} +``` + +We introduce a builder interface to be used within the message processor implementation to incrementally build a `HandlerOutput` value. + +```rust +impl HandlerOutput { + pub fn builder() -> HandlerOutputBuilder { + HandlerOutputBuilder::new() + } +} + +pub struct HandlerOutputBuilder { + log: Vec, + events: Vec, + marker: PhantomData, +} + +impl HandlerOutputBuilder { + pub fn log(&mut self, log: impl Into); + pub fn emit(&mut self, event: impl Into); + pub fn with_result(self, result: T) -> HandlerOutput; +} +``` + +We provide below an example usage of the builder API: + +```rust +fn some_ibc_handler() -> HandlerResult { + let mut output = HandlerOutput::builder(); + + // ... + + output.log(Log::info("did something")) + + // ... + + output.log(Log::success("all good")); + output.emit(SomeEvent::AllGood); + + Ok(output.with_result(42)); +} +``` + +### IBC Submodule + +The various IBC messages and their processing logic, as described in the IBC specification, +are split into a collection of submodules, each pertaining to a specific aspect of +the IBC protocol, eg. client lifecycle management, connection lifecycle management, +packet relay, etc. + +In this section we propose a general approach to implement the message processors for a submodule. +As a running example we will use a dummy submodule that deals with connections, which should not +be mistaken for the actual ICS 003 Connection submodule. + +#### Events + +The events which may be emitted by the message processors of a submodule should be defined +as an enumeration, while a way of converting those into the generic `Event` type +defined in a previous section should be provided via the `From` trait. + +```rust +pub enum ConnectionEvent { + ConnectionOpenInit(ConnectionId), + ConnectionOpenTry(ConnectionId), +} + +impl From for Event { + fn from(ce: ConnectionEvent) -> Event { + match ce { + ConnectionEvent::ConnectionOpenInit(connection_id) => Event::new( + EventType::Custom("ConnectionOpenInit".to_string()), + vec![("connection_id".to_string(), connection_id.to_string())], + ), + ConnectionEvent::ConnectionOpenTry(connection_id) => Event::new( + EventType::Custom("ConnectionOpenTry".to_string()), + vec![("connection_id".to_string(), connection_id.to_string())], + ), + } + } +} +``` + +#### Reader + +A typical message processor will need to read data from the chain state at the current height, +via the private and provable stores. + +To avoid coupling between the message processor interface and the store API, we introduce an interface +for accessing this data. This interface, called a `Reader`, is shared between all message processors +in a submodule, as those typically access the same data. + +Having a high-level interface for this purpose helps avoiding coupling which makes +writing unit tests for the message processors easier, as one does not need to provide a concrete +store, or to mock one. + +```rust +pub trait ConnectionReader +{ + fn connection_end(&self, connection_id: &ConnectionId) -> Option; +} +``` + +A production implementation of this `Reader` would hold references to both the private and provable +store at the current height where the message processor executes, but we omit the actual implementation as +the store interfaces are yet to be defined, as is the general IBC top-level module machinery. + +A mock implementation of the `ConnectionReader` trait could look as follows: + +```rust +struct MockConnectionReader { + connection_id: ConnectionId, + connection_end: Option, + client_reader: MockClientReader, +} + +impl ConnectionReader for MockConnectionReader { + fn connection_end(&self, connection_id: &ConnectionId) -> Option { + if connection_id == &self.connection_id { + self.connection_end.clone() + } else { + None + } + } +} +``` + +#### Keeper + +Once a message processor executes successfully, some data will typically need to be persisted in the chain state +via the private/provable store interfaces. In the same vein as for the reader defined in the previous section, +a submodule should define a trait which provides operations to persist such data. +The same considerations w.r.t. to coupling and unit-testing apply here as well. + +```rust +pub trait ConnectionKeeper { + fn store_connection( + &mut self, + client_id: ConnectionId, + client_type: ConnectionType, + ) -> Result<(), Error>; + + fn add_connection_to_client( + &mut self, + client_id: ClientId, + connection_id: ConnectionId, + ) -> Result<(), Error>; +} +``` + +#### Submodule implementation + +We now come to the actual definition of a message processor for a submodule. + +We recommend each message processor to be defined within its own Rust module, named +after the message processor itself. For example, the "Create Client" message processor of ICS 002 would +be defined in `ibc_modules::ics02_client::handler::create_client`. + +##### Message type + +Each message processor must define a datatype which represent the message it can process. + +```rust +pub struct MsgConnectionOpenInit { + connection_id: ConnectionId, + client_id: ClientId, + counterparty: Counterparty, +} +``` + +##### Message processor implementation + +In this section we provide guidelines for implementating an actual message processor. + +We divide the message processor in two parts: processing and persistance. + +###### Processing + +The actual logic of the message processor is expressed as a pure function, typically named +`process`, which takes as arguments a `Reader` and the corresponding message, and returns +a `HandlerOutput`, where `T` is a concrete datatype and `E` is an error type which defines +all potential errors yielded by the message processors of the current submodule. + +```rust +pub struct ConnectionMsgProcessingResult { + connection_id: ConnectionId, + connection_end: ConnectionEnd, +} +``` + +The `process` function will typically read data via the `Reader`, perform checks and validation, construct new +datatypes, emit log records and events, and eventually return some data together with objects to be persisted. + +To this end, this `process` function will create and manipulate a `HandlerOutput` value like described in +the corresponding section. + +```rust +pub fn process( + reader: &dyn ConnectionReader, + msg: MsgConnectionOpenInit, +) -> HandlerResult +{ + let mut output = HandlerOutput::builder(); + + let MsgConnectionOpenInit { connection_id, client_id, counterparty, } = msg; + + if reader.connection_end(&connection_id).is_some() { + return Err(Kind::ConnectionAlreadyExists(connection_id).into()); + } + + output.log("success: no connection state found"); + + if reader.client_reader.client_state(&client_id).is_none() { + return Err(Kind::ClientForConnectionMissing(client_id).into()); + } + + output.log("success: client found"); + + output.emit(ConnectionEvent::ConnectionOpenInit(connection_id.clone())); + + Ok(output.with_result(ConnectionMsgProcessingResult { + connection_id, + client_id, + counterparty, + })) +} +``` + +###### Persistence + +If the `process` function specified above succeeds, the result value it yielded is then +passed to a function named `keep`, which is responsible for persisting the objects constructed +by the processing function. This `keep` function takes the submodule's `Keeper` and the result +type defined above, and performs side-effecting calls to the keeper's methods to persist the result. + +Below is given an implementation of the `keep` function for the "Create Connection" message processors: + +```rust +pub fn keep( + keeper: &mut dyn ConnectionKeeper, + result: ConnectionMsgProcessingResult, +) -> Result<(), Error> +{ + keeper.store_connection(result.connection_id.clone(), result.connection_end)?; + keeper.add_connection_to_client(result.client_id, result.connection_id)?; + + Ok(()) +} +``` + +##### Submodule dispatcher + +> This section is very much a work in progress, as further investigation into what +> a production-ready implementation of the `ctx` parameter of the top-level dispatcher +> is required. As such, implementors should feel free to disregard the recommendations +> below, and are encouraged to come up with amendments to this ADR to better capture +> the actual requirements. + +Each submodule is responsible for dispatching the messages it is given to the appropriate +message processing function and, if successful, pass the resulting data to the persistance +function defined in the previous section. + +To this end, the submodule should define an enumeration of all messages, in order +for the top-level submodule dispatcher to forward them to the appropriate processor. +Such a definition for the ICS 003 Connection submodule is given below. + +```rust +pub enum ConnectionMsg { + ConnectionOpenInit(MsgConnectionOpenInit), + ConnectionOpenTry(MsgConnectionOpenTry), + ... +} +``` +The actual implementation of a submodule dispatcher is quite straightforward and unlikely to vary +much in substance between submodules. We give an implementation for the ICS 003 Connection module below. + +```rust +pub fn dispatch(ctx: &mut Ctx, msg: Msg) -> Result, Error> +where + Ctx: ConnectionReader + ConnectionKeeper, +{ + match msg { + Msg::ConnectionOpenInit(msg) => { + let HandlerOutput { + result, + log, + events, + } = connection_open_init::process(ctx, msg)?; + + connection::keep(ctx, result)?; + + Ok(HandlerOutput::builder() + .with_log(log) + .with_events(events) + .with_result(())) + } + + Msg::ConnectionOpenTry(msg) => // omitted + } +} +``` + +In essence, a top-level dispatcher is a function of a message wrapped in the enumeration introduced above, +and a "context" which implements both the `Reader` and `Keeper` interfaces. + +### Dealing with chain-specific datatypes + +The ICS 002 Client submodule stands out from the other submodules as it needs +to deal with chain-specific datatypes, such as `Header`, `ClientState`, and +`ConsensusState`. + +To abstract over chain-specific datatypes, we introduce a trait which specifies +both which types we need to abstract over, and their interface. + +For the ICS 002 Client submodule, this trait looks as follow: + +```rust +pub trait ClientDef { + type Header: Header; + type ClientState: ClientState; + type ConsensusState: ConsensusState; +} +``` + +The `ClientDef` trait specifies three datatypes, and their corresponding interface, which is provided +via a trait defined in the same submodule. + +A production implementation of this interface would instantiate these types with the concrete +types used by the chain, eg. Tendermint datatypes. Each concrete datatype must be provided +with a `From` instance to lift it into its corresponding `Any...` enumeration. + +For the purpose of unit-testing, a mock implementation of the `ClientDef` trait could look as follows: + +```rust +struct MockHeader(u32); + +impl Header for MockHeader { + // omitted +} + +impl From for AnyHeader { + fn from(mh: MockHeader) -> Self { + Self::Mock(mh) + } +} + +struct MockClientState(u32); + +impl ClientState for MockClientState { + // omitted +} + +impl From for AnyClientState { + fn from(mcs: MockClientState) -> Self { + Self::Mock(mcs) + } +} + +struct MockConsensusState(u32); + +impl ConsensusState for MockConsensusState { + // omitted +} + +impl From for AnyConsensusState { + fn from(mcs: MockConsensusState) -> Self { + Self::Mock(mcs) + } +} + +struct MockClient; + +impl ClientDef for MockClient { + type Header = MockHeader; + type ClientState = MockClientState; + type ConsensusState = MockConsensusState; +} +``` + +Since the actual type of client can only be determined at runtime, we cannot encode +the type of client within the message itself. + +Because of some limitations of the Rust type system, namely the lack of proper support +for existential types, it is currently impossible to define `Reader` and `Keeper` traits +which are agnostic to the actual type of client being used. + +We could alternatively model all chain-specific datatypes as boxed trait objects (`Box`), +but this approach runs into a lot of limitations of trait objects, such as the inability to easily +require such trait objects to be Clonable, or Serializable, or to define an equality relation on them. +Some support for such functionality can be found in third-party libraries, but the overall experience +for the developer is too subpar. + +We thus settle on a different strategy: lifting chain-specific data into an `enum` over all +possible chain types. + +For example, to model a chain-specific `Header` type, we would define an enumeration in the following +way: + +```rust +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] // TODO: Add Eq +pub enum AnyHeader { + Mock(mocks::MockHeader), + Tendermint(tendermint::header::Header), +} + +impl Header for AnyHeader { + fn height(&self) -> Height { + match self { + Self::Mock(header) => header.height(), + Self::Tendermint(header) => header.height(), + } + } + + fn client_type(&self) -> ClientType { + match self { + Self::Mock(header) => header.client_type(), + Self::Tendermint(header) => header.client_type(), + } + } +} +``` + +This enumeration dispatches method calls to the underlying datatype at runtime, while +hiding the latter, and is thus akin to a proper existential type without running +into any limitations of the Rust type system (`impl Header` bounds not being allowed +everywhere, `Header` not being able to be treated as a trait objects because of `Clone`, +`PartialEq` and `Serialize`, `Deserialize` bounds, etc.) + +Other chain-specific datatypes, such as `ClientState` and `ConsensusState` require their own +enumeration over all possible implementations. + +On top of that, we also need to lift the specific client definitions (`ClientDef` instances), +into their own enumeration, as follows: + +```rust +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum AnyClient { + Mock(mocks::MockClient), + Tendermint(tendermint::TendermintClient), +} + +impl ClientDef for AnyClient { + type Header = AnyHeader; + type ClientState = AnyClientState; + type ConsensusState = AnyConsensusState; +} +``` + +Messages can now be defined generically over the `ClientDef` instance: + + +```rust +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MsgCreateClient { + pub client_id: ClientId, + pub client_type: ClientType, + pub consensus_state: CD::ConsensusState, +} + +pub struct MsgUpdateClient { + pub client_id: ClientId, + pub header: CD::Header, +} +``` + +The `Keeper` and `Reader` traits are defined for any client: + +```rust +pub trait ClientReader { + fn client_type(&self, client_id: &ClientId) -> Option; + fn client_state(&self, client_id: &ClientId) -> Option; + fn consensus_state(&self, client_id: &ClientId, height: Height) -> Option; +} + +pub trait ClientKeeper { + fn store_client_type( + &mut self, + client_id: ClientId, + client_type: ClientType, + ) -> Result<(), Error>; + + fn store_client_state( + &mut self, + client_id: ClientId, + client_state: AnyClientState, + ) -> Result<(), Error>; + + fn store_consensus_state( + &mut self, + client_id: ClientId, + consensus_state: AnyConsensusState, + ) -> Result<(), Error>; +} +``` + +This way, only one implementation of the `ClientReader` and `ClientKeeper` trait is required, +as it can delegate eg. the serialization of the underlying datatypes to the `Serialize` bound +of the `Any...` wrappper. + +Both the `process` and `keep` function are defined to take a message generic over +the actual client type: + +```rust +pub fn process( + ctx: &dyn ClientReader, + msg: MsgCreateClient, +) -> HandlerResult, Error>; + +pub fn keep( + keeper: &mut dyn ClientKeeper, + result: CreateClientResult, +) -> Result<(), Error>; +``` + +Same for the top-level dispatcher: + +```rust +pub fn dispatch(ctx: &mut Ctx, msg: ClientMsg) -> Result, Error> +where + Ctx: ClientReader + ClientKeeper; +``` + +With this boilerplate out of way, one can write tests using a mock client, and associated mock datatypes +in a fairly straightforward way, taking advantage of the `From` instance to lift concerete mock datatypes +into the `Any...` enumeration: + +```rust + #[test] + fn test_create_client_ok() { + let client_id: ClientId = "mockclient".parse().unwrap(); + + let reader = MockClientReader { + client_id: client_id.clone(), + client_type: None, + client_state: None, + consensus_state: None, + }; + + let msg = MsgCreateClient { + client_id, + client_type: ClientType::Tendermint, + consensus_state: MockConsensusState(42).into(), // lift into `AnyConsensusState` + }; + + let output = process(&reader, msg.clone()); + + match output { + Ok(HandlerOutput { + result, + events, + log, + }) => { + // snip + } + Err(err) => { + panic!("unexpected error: {}", err); + } + } + } +``` + +## Status + +Proposed + +## Consequences + +> This section describes the consequences, after applying the decision. All consequences should be summarized here, not just the "positive" ones. + +### Positive + +### Negative + +### Neutral + +## References + +> Are there any relevant PR comments, issues that led up to this, or articles refernced for why we made the given design choice? If so link them here! + +* {reference link} diff --git a/modules/Cargo.toml b/modules/Cargo.toml index 71758f5d8b..04aff3f55c 100644 --- a/modules/Cargo.toml +++ b/modules/Cargo.toml @@ -29,6 +29,7 @@ serde_json = "1" tracing = "0.1.13" prost = "0.6.1" bytes = "0.5.5" +dyn-clonable = "0.9.0" [dev-dependencies] tokio = { version = "0.2", features = ["macros"] } diff --git a/modules/src/handler.rs b/modules/src/handler.rs new file mode 100644 index 0000000000..05448f412f --- /dev/null +++ b/modules/src/handler.rs @@ -0,0 +1,95 @@ +use std::marker::PhantomData; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Attribute { + key: String, + value: String, +} + +impl Attribute { + pub fn new(key: String, value: String) -> Self { + Self { key, value } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum EventType { + Message, + Custom(String), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Event { + tpe: EventType, + attributes: Vec, +} + +impl Event { + pub fn new(tpe: EventType, attrs: Vec<(String, String)>) -> Self { + Self { + tpe, + attributes: attrs + .into_iter() + .map(|(k, v)| Attribute::new(k, v)) + .collect(), + } + } +} + +pub type HandlerResult = Result, E>; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HandlerOutput { + pub result: T, + pub log: Vec, + pub events: Vec, +} + +impl HandlerOutput { + pub fn builder() -> HandlerOutputBuilder { + HandlerOutputBuilder::new() + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct HandlerOutputBuilder { + log: Vec, + events: Vec, + marker: PhantomData, +} + +impl HandlerOutputBuilder { + pub fn new() -> Self { + Self { + log: vec![], + events: vec![], + marker: PhantomData, + } + } + + pub fn with_log(mut self, log: impl Into>) -> Self { + self.log.append(&mut log.into()); + self + } + + pub fn log(&mut self, log: impl Into) { + self.log.push(log.into()); + } + + pub fn with_events(mut self, events: impl Into>) -> Self { + self.events.append(&mut events.into()); + self + } + + pub fn emit(&mut self, event: impl Into) { + self.events.push(event.into()); + } + + pub fn with_result(self, result: T) -> HandlerOutput { + HandlerOutput { + result, + log: self.log, + events: self.events, + } + } +} diff --git a/modules/src/ics02_client/client_def.rs b/modules/src/ics02_client/client_def.rs new file mode 100644 index 0000000000..592281dbc0 --- /dev/null +++ b/modules/src/ics02_client/client_def.rs @@ -0,0 +1,110 @@ +use serde_derive::{Deserialize, Serialize}; + +use crate::ics02_client::client_type::ClientType; +use crate::ics02_client::header::Header; +use crate::ics02_client::state::{ClientState, ConsensusState}; +use crate::ics23_commitment::CommitmentRoot; +use crate::Height; + +use crate::ics02_client::mocks; +use crate::ics07_tendermint as tendermint; +use crate::ics07_tendermint::client_def::TendermintClient; + +pub trait ClientDef: Clone { + type Header: Header; + type ClientState: ClientState; + type ConsensusState: ConsensusState; +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] // TODO: Add Eq +#[allow(clippy::large_enum_variant)] +pub enum AnyHeader { + Mock(mocks::MockHeader), + Tendermint(tendermint::header::Header), +} + +impl Header for AnyHeader { + fn client_type(&self) -> ClientType { + match self { + Self::Mock(header) => header.client_type(), + Self::Tendermint(header) => header.client_type(), + } + } + + fn height(&self) -> Height { + match self { + Self::Mock(header) => header.height(), + Self::Tendermint(header) => header.height(), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum AnyClientState { + Mock(mocks::MockClientState), + Tendermint(crate::ics07_tendermint::client_state::ClientState), +} + +impl ClientState for AnyClientState { + fn chain_id(&self) -> String { + todo!() + } + + fn client_type(&self) -> ClientType { + todo!() + } + + fn get_latest_height(&self) -> Height { + match self { + AnyClientState::Tendermint(tm_state) => tm_state.get_latest_height(), + AnyClientState::Mock(mock_state) => mock_state.get_latest_height(), + } + } + + fn is_frozen(&self) -> bool { + todo!() + } + + fn verify_client_consensus_state( + &self, + _root: &CommitmentRoot, + ) -> Result<(), Box> { + todo!() + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum AnyConsensusState { + Mock(mocks::MockConsensusState), + Tendermint(crate::ics07_tendermint::consensus_state::ConsensusState), +} + +impl ConsensusState for AnyConsensusState { + fn client_type(&self) -> ClientType { + todo!() + } + + fn height(&self) -> Height { + todo!() + } + + fn root(&self) -> &CommitmentRoot { + todo!() + } + + fn validate_basic(&self) -> Result<(), Box> { + todo!() + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum AnyClient { + Mock(mocks::MockClient), + Tendermint(TendermintClient), +} + +impl ClientDef for AnyClient { + type Header = AnyHeader; + type ClientState = AnyClientState; + type ConsensusState = AnyConsensusState; +} diff --git a/modules/src/ics02_client/client_type.rs b/modules/src/ics02_client/client_type.rs index 9837514d76..a9d1e7ab60 100644 --- a/modules/src/ics02_client/client_type.rs +++ b/modules/src/ics02_client/client_type.rs @@ -38,8 +38,8 @@ mod tests { let client_type = ClientType::from_str("tendermint"); match client_type { - Ok(ClientType::Tendermint) => assert!(true), - Err(_) => assert!(false, "parse failed"), + Ok(ClientType::Tendermint) => (), + _ => panic!("parse failed"), } } @@ -52,7 +52,7 @@ mod tests { format!("{}", err), "unknown client type: some-random-client-type" ), - _ => assert!(false, "parse didn't fail"), + _ => panic!("parse didn't fail"), } } } diff --git a/modules/src/ics02_client/error.rs b/modules/src/ics02_client/error.rs index 8bca154a27..7c1acb0b4b 100644 --- a/modules/src/ics02_client/error.rs +++ b/modules/src/ics02_client/error.rs @@ -1,12 +1,27 @@ use anomaly::{BoxError, Context}; use thiserror::Error; +use crate::ics24_host::identifier::ClientId; +use crate::Height; + pub type Error = anomaly::Error; -#[derive(Clone, Debug, Error)] +#[derive(Clone, Debug, Error, PartialEq, Eq)] pub enum Kind { #[error("unknown client type")] UnknownClientType, + + #[error("client already exists: {0}")] + ClientAlreadyExists(ClientId), + + #[error("client not found: {0}")] + ClientNotFound(ClientId), + + #[error("consensus state not found at: {0} at height {1}")] + ConsensusStateNotFound(ClientId, Height), + + #[error("implementation specific")] + ImplementationSpecific, } impl Kind { diff --git a/modules/src/ics02_client/events.rs b/modules/src/ics02_client/events.rs index d2c6b364b6..e2ef2eb2a4 100644 --- a/modules/src/ics02_client/events.rs +++ b/modules/src/ics02_client/events.rs @@ -9,6 +9,7 @@ use serde_derive::{Deserialize, Serialize}; use std::convert::TryFrom; use tendermint::block; +/// NewBlock event signals the committing & execution of a new block. // TODO - find a better place for NewBlock #[derive(Debug, Deserialize, Serialize, Clone)] pub struct NewBlock { @@ -27,6 +28,7 @@ impl From for IBCEvent { } } +/// CreateClient event signals the creation of a new on-chain client (IBC client). #[derive(Debug, Deserialize, Serialize, Clone)] pub struct CreateClient { pub height: block::Height, @@ -51,6 +53,7 @@ impl From for IBCEvent { } } +/// UpdateClient event signals a recent update of an on-chain client (IBC Client). #[derive(Debug, Deserialize, Serialize, Clone)] pub struct UpdateClient { pub height: block::Height, @@ -75,6 +78,8 @@ impl From for IBCEvent { } } +/// ClientMisbehavior event signals the update of an on-chain client (IBC Client) with evidence of +/// misbehavior. #[derive(Debug, Deserialize, Serialize, Clone)] pub struct ClientMisbehavior { pub height: block::Height, diff --git a/modules/src/ics02_client/handler.rs b/modules/src/ics02_client/handler.rs new file mode 100644 index 0000000000..cb80a63919 --- /dev/null +++ b/modules/src/ics02_client/handler.rs @@ -0,0 +1,102 @@ +#![allow(unused_imports)] + +use crate::handler::{Event, EventType, HandlerOutput}; +use crate::ics02_client::client_def::{AnyClient, AnyClientState, AnyConsensusState, ClientDef}; +use crate::ics02_client::client_type::ClientType; +use crate::ics02_client::error::Error; +use crate::ics02_client::msgs::{MsgCreateAnyClient, MsgUpdateAnyClient}; +use crate::ics02_client::state::{ClientState, ConsensusState}; +use crate::ics24_host::identifier::ClientId; + +use crate::Height; + +pub mod create_client; +pub mod update_client; + +pub trait ClientReader { + fn client_type(&self, client_id: &ClientId) -> Option; + fn client_state(&self, client_id: &ClientId) -> Option; + fn consensus_state(&self, client_id: &ClientId, height: Height) -> Option; +} + +pub trait ClientKeeper { + fn store_client_type( + &mut self, + client_id: ClientId, + client_type: ClientType, + ) -> Result<(), Error>; + + fn store_client_state( + &mut self, + client_id: ClientId, + client_state: AnyClientState, + ) -> Result<(), Error>; + + fn store_consensus_state( + &mut self, + client_id: ClientId, + consensus_state: AnyConsensusState, + ) -> Result<(), Error>; +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ClientEvent { + ClientCreated(ClientId), + ClientUpdated(ClientId), +} + +impl From for Event { + fn from(ce: ClientEvent) -> Event { + match ce { + ClientEvent::ClientCreated(client_id) => Event::new( + EventType::Custom("ClientCreated".to_string()), + vec![("client_id".to_string(), client_id.to_string())], + ), + ClientEvent::ClientUpdated(client_id) => Event::new( + EventType::Custom("ClientUpdated".to_string()), + vec![("client_id".to_string(), client_id.to_string())], + ), + } + } +} + +pub enum ClientMsg { + CreateClient(MsgCreateAnyClient), + UpdateClient(MsgUpdateAnyClient), +} + +pub fn dispatch(ctx: &mut Ctx, msg: ClientMsg) -> Result, Error> +where + Ctx: ClientReader + ClientKeeper, +{ + match msg { + ClientMsg::CreateClient(msg) => { + let HandlerOutput { + result, + log, + events, + } = create_client::process(ctx, msg)?; + + create_client::keep(ctx, result)?; + + Ok(HandlerOutput::builder() + .with_log(log) + .with_events(events) + .with_result(())) + } + ClientMsg::UpdateClient(msg) => { + let HandlerOutput { + result, + log, + events, + } = update_client::process(ctx, msg)?; + + update_client::keep(ctx, result)?; + + Ok(HandlerOutput::builder() + .with_log(log) + .with_events(events) + .with_result(())) + } + } +} diff --git a/modules/src/ics02_client/handler/create_client.rs b/modules/src/ics02_client/handler/create_client.rs new file mode 100644 index 0000000000..45f19200b9 --- /dev/null +++ b/modules/src/ics02_client/handler/create_client.rs @@ -0,0 +1,235 @@ +#![allow(unreachable_code, unused_variables)] + +use crate::handler::{HandlerOutput, HandlerResult}; +use crate::ics02_client::client_def::{AnyClient, ClientDef}; +use crate::ics02_client::client_type::ClientType; +use crate::ics02_client::error::{Error, Kind}; +use crate::ics02_client::handler::{ClientEvent, ClientKeeper, ClientReader}; +use crate::ics02_client::msgs::MsgCreateAnyClient; +use crate::ics02_client::state::{ClientState, ConsensusState}; +use crate::ics24_host::identifier::ClientId; +use std::time::Duration; + +#[derive(Debug)] +pub struct CreateClientResult { + client_id: ClientId, + client_type: ClientType, + client_state: CD::ClientState, + consensus_state: CD::ConsensusState, +} + +pub fn process( + ctx: &dyn ClientReader, + msg: MsgCreateAnyClient, +) -> HandlerResult, Error> { + let mut output = HandlerOutput::builder(); + + let MsgCreateAnyClient { + client_id, + client_type, + client_state, + consensus_state, + } = msg; + + if ctx.client_state(&client_id).is_some() { + return Err(Kind::ClientAlreadyExists(client_id).into()); + } + + output.log("success: no client state found"); + + if ctx.client_type(&client_id).is_some() { + return Err(Kind::ClientAlreadyExists(client_id).into()); + } + + output.log("success: no client type found"); + + output.emit(ClientEvent::ClientCreated(client_id.clone())); + + Ok(output.with_result(CreateClientResult { + client_id, + client_type, + client_state, + consensus_state, + })) +} + +pub fn keep( + keeper: &mut dyn ClientKeeper, + result: CreateClientResult, +) -> Result<(), Error> { + keeper.store_client_type(result.client_id.clone(), result.client_type)?; + keeper.store_client_state(result.client_id.clone(), result.client_state)?; + keeper.store_consensus_state(result.client_id, result.consensus_state)?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ics02_client::header::Header; + use crate::ics02_client::mocks::*; + use crate::ics02_client::state::{ClientState, ConsensusState}; + use crate::ics07_tendermint::client_def::TendermintClient; + use crate::ics07_tendermint::header::test_util::get_dummy_header; + use crate::ics07_tendermint::msgs::create_client::MsgCreateClient; + use crate::ics23_commitment::CommitmentRoot; + use crate::Height; + use std::str::FromStr; + use thiserror::Error; + + #[test] + fn test_create_client_ok() { + let client_id: ClientId = "mockclient".parse().unwrap(); + + let reader = MockClientReader { + client_id: client_id.clone(), + client_type: None, + client_state: None, + consensus_state: None, + }; + + let msg = MsgCreateAnyClient { + client_id, + client_type: ClientType::Tendermint, + client_state: MockClientState(42).into(), + consensus_state: MockConsensusState(42).into(), + }; + + let output = process(&reader, msg.clone()); + + match output { + Ok(HandlerOutput { + result, + events, + log, + }) => { + assert_eq!(result.client_type, ClientType::Tendermint); + assert_eq!( + events, + vec![ClientEvent::ClientCreated(msg.client_id).into()] + ); + assert_eq!( + log, + vec![ + "success: no client state found".to_string(), + "success: no client type found".to_string() + ] + ); + } + Err(err) => { + panic!("unexpected error: {}", err); + } + } + } + + #[test] + fn test_create_client_existing_client_type() { + let client_id: ClientId = "mockclient".parse().unwrap(); + + let reader = MockClientReader { + client_id: client_id.clone(), + client_type: Some(ClientType::Tendermint), + client_state: None, + consensus_state: None, + }; + + let msg = MsgCreateAnyClient { + client_id, + client_type: ClientType::Tendermint, + client_state: MockClientState(42).into(), + consensus_state: MockConsensusState(42).into(), + }; + + let output = process(&reader, msg.clone()); + + if let Err(err) = output { + assert_eq!(err.kind(), &Kind::ClientAlreadyExists(msg.client_id)); + } else { + panic!("expected an error"); + } + } + + #[test] + fn test_create_client_existing_client_state() { + let client_id: ClientId = "mockclient".parse().unwrap(); + + let reader = MockClientReader { + client_id: client_id.clone(), + client_type: None, + client_state: Some(MockClientState(0)), + consensus_state: None, + }; + + let msg = MsgCreateAnyClient { + client_id, + client_type: ClientType::Tendermint, + client_state: MockClientState(42).into(), + consensus_state: MockConsensusState(42).into(), + }; + + let output = process(&reader, msg.clone()); + + if let Err(err) = output { + assert_eq!(err.kind(), &Kind::ClientAlreadyExists(msg.client_id)); + } else { + panic!("expected an error"); + } + } + #[test] + fn test_tm_create_client_ok() { + use tendermint::account::Id as AccountId; + + let client_id: ClientId = "tendermint".parse().unwrap(); + + let reader = MockClientReader { + client_id: client_id.clone(), + client_type: None, + client_state: None, + consensus_state: None, + }; + + let ics_msg = MsgCreateClient { + client_id, + header: get_dummy_header(), + trusting_period: Duration::from_secs(64000), + unbonding_period: Duration::from_secs(128000), + max_clock_drift: Duration::from_millis(3000), + signer: AccountId::from_str("7C2BB42A8BE69791EC763E51F5A49BCD41E82237").unwrap(), + }; + + //let msg = ics_msg.pre_process(); + let msg = MsgCreateAnyClient { + client_id: ics_msg.client_id().clone(), + client_type: ics_msg.client_type(), + client_state: ics_msg.client_state(), + consensus_state: ics_msg.consensus_state(), + }; + + let output = process(&reader, msg.clone()); + + match output { + Ok(HandlerOutput { + result, + events, + log, + }) => { + assert_eq!(result.client_type, ClientType::Tendermint); + assert_eq!( + events, + vec![ClientEvent::ClientCreated(msg.client_id).into()] + ); + assert_eq!( + log, + vec![ + "success: no client state found".to_string(), + "success: no client type found".to_string() + ] + ); + } + Err(err) => { + panic!("unexpected error: {}", err); + } + } + } +} diff --git a/modules/src/ics02_client/handler/update_client.rs b/modules/src/ics02_client/handler/update_client.rs new file mode 100644 index 0000000000..d548bfed9e --- /dev/null +++ b/modules/src/ics02_client/handler/update_client.rs @@ -0,0 +1,110 @@ +#![allow(unreachable_code, unused_variables)] + +use crate::handler::{HandlerOutput, HandlerResult}; +use crate::ics02_client::client_def::{AnyClient, ClientDef}; +use crate::ics02_client::error::{Error, Kind}; +use crate::ics02_client::handler::{ClientEvent, ClientKeeper, ClientReader}; +use crate::ics02_client::msgs::MsgUpdateAnyClient; +use crate::ics02_client::state::{ClientState, ConsensusState}; +use crate::ics24_host::identifier::ClientId; + +#[derive(Debug)] +pub struct UpdateClientResult { + client_id: ClientId, + client_state: CD::ClientState, + consensus_state: CD::ConsensusState, +} + +pub fn process( + ctx: &dyn ClientReader, + msg: MsgUpdateAnyClient, +) -> HandlerResult, Error> { + let mut output = HandlerOutput::builder(); + + let MsgUpdateAnyClient { client_id, header } = msg; + + let client_type = ctx + .client_type(&client_id) + .ok_or_else(|| Kind::ClientNotFound(client_id.clone()))?; + + let client_state = ctx + .client_state(&client_id) + .ok_or_else(|| Kind::ClientNotFound(client_id.clone()))?; + + let latest_height = client_state.get_latest_height(); + let consensus_state = ctx + .consensus_state(&client_id, latest_height) + .ok_or_else(|| Kind::ConsensusStateNotFound(client_id.clone(), latest_height))?; + + // Use client_state to validate the new header against the latest consensus_state. + // This function will return the new client_state (its latest_height changed) and a + // consensus_state obtained from header. These will be later persisted by the keeper. + // FIXME + // (new_client_state, new_consensus_state) = + // CD::check_validity_and_update_state(client_state, consensus_state, &header)?; + + output.emit(ClientEvent::ClientUpdated(client_id.clone())); + + Ok(output.with_result(UpdateClientResult { + client_id, + client_state, // new_client_state + consensus_state, // new_consensus_state + })) +} + +pub fn keep( + keeper: &mut dyn ClientKeeper, + result: UpdateClientResult, +) -> Result<(), Error> { + keeper.store_client_state(result.client_id.clone(), result.client_state)?; + keeper.store_consensus_state(result.client_id, result.consensus_state)?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ics02_client::client_type::ClientType; + use crate::ics02_client::header::Header; + use crate::ics02_client::mocks::*; + use crate::ics02_client::state::{ClientState, ConsensusState}; + use crate::ics23_commitment::CommitmentRoot; + use crate::Height; + use thiserror::Error; + + #[test] + fn test_update_client_ok() { + let mock = MockClientReader { + client_id: "mockclient".parse().unwrap(), + client_type: Some(ClientType::Tendermint), + client_state: MockClientState(42).into(), + consensus_state: MockConsensusState(42).into(), + }; + + let msg = MsgUpdateAnyClient { + client_id: "mockclient".parse().unwrap(), + header: MockHeader(46).into(), + }; + + let output = process(&mock, msg.clone()); + + match output { + Ok(HandlerOutput { + result: _, + events, + log, + }) => { + // assert_eq!(result.client_state, MockClientState(0)); + assert_eq!( + events, + vec![ClientEvent::ClientUpdated(msg.client_id).into()] + ); + assert!(log.is_empty()); + } + Err(err) => { + panic!("unexpected error: {}", err); + } + } + } +} diff --git a/modules/src/ics02_client/header.rs b/modules/src/ics02_client/header.rs index bc54fd2aa6..255b70ed52 100644 --- a/modules/src/ics02_client/header.rs +++ b/modules/src/ics02_client/header.rs @@ -2,7 +2,8 @@ use super::client_type::ClientType; use crate::Height; /// Abstract of consensus state update information -pub trait Header { +#[dyn_clonable::clonable] +pub trait Header: Clone + std::fmt::Debug { /// The type of client (eg. Tendermint) fn client_type(&self) -> ClientType; diff --git a/modules/src/ics02_client/mocks.rs b/modules/src/ics02_client/mocks.rs new file mode 100644 index 0000000000..1900fc379f --- /dev/null +++ b/modules/src/ics02_client/mocks.rs @@ -0,0 +1,185 @@ +use crate::ics02_client::client_def::{AnyClientState, AnyConsensusState, AnyHeader, ClientDef}; +use crate::ics02_client::client_type::ClientType; +use crate::ics02_client::error::Error; +use crate::ics02_client::handler::{ClientKeeper, ClientReader}; +use crate::ics02_client::header::Header; +use crate::ics02_client::state::{ClientState, ConsensusState}; +use crate::ics23_commitment::CommitmentRoot; +use crate::ics24_host::identifier::ClientId; +use crate::Height; + +use serde_derive::{Deserialize, Serialize}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum MockError {} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct MockHeader(pub u32); + +impl From for AnyHeader { + fn from(mh: MockHeader) -> Self { + Self::Mock(mh) + } +} + +impl Header for MockHeader { + fn client_type(&self) -> ClientType { + todo!() + } + + fn height(&self) -> Height { + todo!() + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct MockClientState(pub u32); + +impl From for AnyClientState { + fn from(mcs: MockClientState) -> Self { + Self::Mock(mcs) + } +} + +impl ClientState for MockClientState { + fn chain_id(&self) -> String { + todo!() + } + + fn client_type(&self) -> ClientType { + todo!() + } + + fn get_latest_height(&self) -> Height { + Height::from(self.0 as u64) + } + + fn is_frozen(&self) -> bool { + todo!() + } + + fn verify_client_consensus_state( + &self, + _root: &CommitmentRoot, + ) -> Result<(), Box> { + todo!() + } +} + +impl From for MockClientState { + fn from(cs: MockConsensusState) -> Self { + Self(cs.0) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct MockConsensusState(pub u32); + +impl From for AnyConsensusState { + fn from(mcs: MockConsensusState) -> Self { + Self::Mock(mcs) + } +} + +impl ConsensusState for MockConsensusState { + fn client_type(&self) -> ClientType { + todo!() + } + + fn height(&self) -> Height { + todo!() + } + + fn root(&self) -> &CommitmentRoot { + todo!() + } + + fn validate_basic(&self) -> Result<(), Box> { + todo!() + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MockClient; + +impl ClientDef for MockClient { + type Header = MockHeader; + type ClientState = MockClientState; + type ConsensusState = MockConsensusState; +} + +#[derive(Clone, Debug, PartialEq)] +pub struct MockClientContext { + reader: MockClientReader, + keeper: MockClientKeeper, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct MockClientReader { + pub client_id: ClientId, + pub client_state: Option, + pub client_type: Option, + pub consensus_state: Option, +} + +impl ClientReader for MockClientReader { + fn client_type(&self, client_id: &ClientId) -> Option { + if client_id == &self.client_id { + self.client_type.clone() + } else { + None + } + } + + #[allow(trivial_casts)] + fn client_state(&self, client_id: &ClientId) -> Option { + if client_id == &self.client_id { + self.client_state.map(Into::into) + } else { + None + } + } + + #[allow(trivial_casts)] + fn consensus_state(&self, client_id: &ClientId, _height: Height) -> Option { + if client_id == &self.client_id { + self.consensus_state.map(Into::into) + } else { + None + } + } +} + +#[derive(Clone, Debug, PartialEq, Default)] +pub struct MockClientKeeper { + pub client_state: Option, + pub client_type: Option, + pub consensus_state: Option, +} + +impl ClientKeeper for MockClientKeeper { + fn store_client_type( + &mut self, + _client_id: ClientId, + _client_type: ClientType, + ) -> Result<(), Error> { + todo!() + } + + fn store_client_state( + &mut self, + _client_id: ClientId, + _client_state: AnyClientState, + ) -> Result<(), Error> { + todo!() + } + + fn store_consensus_state( + &mut self, + _client_id: ClientId, + _consensus_state: AnyConsensusState, + ) -> Result<(), Error> { + todo!() + } +} diff --git a/modules/src/ics02_client/mod.rs b/modules/src/ics02_client/mod.rs index 81ad985e35..9f5caea073 100644 --- a/modules/src/ics02_client/mod.rs +++ b/modules/src/ics02_client/mod.rs @@ -1,9 +1,12 @@ //! ICS 02: IBC Client implementation -pub mod client; +pub mod client_def; pub mod client_type; pub mod error; pub mod events; +pub mod handler; pub mod header; +pub mod mocks; pub mod msgs; +pub mod raw; pub mod state; diff --git a/modules/src/ics02_client/msgs.rs b/modules/src/ics02_client/msgs.rs index a25d49ba4d..6ba0bda9be 100644 --- a/modules/src/ics02_client/msgs.rs +++ b/modules/src/ics02_client/msgs.rs @@ -1,25 +1,25 @@ -use super::client_type::ClientType; -use super::header::Header; -use super::state::ConsensusState; +//! These are definitions of messages that a relayer submits to a chain. Specific implementations of +//! these messages can be found, for instance, in ICS 07 for Tendermint-specific chains. A chain +//! handles these messages in two layers: first with the general ICS 02 client handler, which +//! subsequently calls into the chain-specific (e.g., ICS 07) client handler. See: +//! https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics#create. + +use crate::ics02_client::client_def::ClientDef; +use crate::ics02_client::client_type::ClientType; use crate::ics24_host::identifier::ClientId; -use crate::tx_msg::Msg; -pub trait MsgUpdateClient -where - Self: Msg, -{ - type Header: Header; - fn client_id(&self) -> &ClientId; - fn header(&self) -> &Self::Header; +/// A type of message that triggers the creation of a new on-chain (IBC) client. +#[derive(Clone, Debug)] +pub struct MsgCreateAnyClient { + pub client_id: ClientId, + pub client_type: ClientType, + pub client_state: CD::ClientState, + pub consensus_state: CD::ConsensusState, } -pub trait MsgCreateClient -where - Self: Msg, -{ - type ConsensusState: ConsensusState; - - fn client_id(&self) -> &ClientId; - fn client_type(&self) -> ClientType; - fn consensus_state(&self) -> Self::ConsensusState; +/// A type of message that triggers the update of an on-chain (IBC) client with new headers. +#[derive(Clone, Debug)] +pub struct MsgUpdateAnyClient { + pub client_id: ClientId, + pub header: CD::Header, } diff --git a/modules/src/ics02_client/client.rs b/modules/src/ics02_client/raw.rs similarity index 100% rename from modules/src/ics02_client/client.rs rename to modules/src/ics02_client/raw.rs diff --git a/modules/src/ics02_client/state.rs b/modules/src/ics02_client/state.rs index d441112fcf..eede4f6ec1 100644 --- a/modules/src/ics02_client/state.rs +++ b/modules/src/ics02_client/state.rs @@ -1,12 +1,9 @@ use super::client_type::ClientType; - use crate::ics23_commitment::CommitmentRoot; -use crate::ics24_host::identifier::ClientId; use crate::Height; -pub trait ConsensusState { - type ValidationError: std::error::Error; - +#[dyn_clonable::clonable] +pub trait ConsensusState: Clone + std::fmt::Debug { /// Type of client associated with this consensus state (eg. Tendermint) fn client_type(&self) -> ClientType; @@ -17,14 +14,13 @@ pub trait ConsensusState { fn root(&self) -> &CommitmentRoot; /// Performs basic validation of the consensus state - fn validate_basic(&self) -> Result<(), Self::ValidationError>; + fn validate_basic(&self) -> Result<(), Box>; } -pub trait ClientState { - type ValidationError: std::error::Error; - +#[dyn_clonable::clonable] +pub trait ClientState: Clone + std::fmt::Debug { /// Client ID of this state - fn client_id(&self) -> ClientId; + fn chain_id(&self) -> String; /// Type of client associated with this state (eg. Tendermint) fn client_type(&self) -> ClientType; @@ -35,9 +31,11 @@ pub trait ClientState { /// Freeze status of the client fn is_frozen(&self) -> bool; - // TODO: It's unclear what this function is expected to achieve. Document this. + /// Verifies a proof of the consensus state of the specified client stored on the target machine. + /// FIXME: Definition is incomplete. + /// See https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics#required-functions fn verify_client_consensus_state( &self, root: &CommitmentRoot, - ) -> Result<(), Self::ValidationError>; + ) -> Result<(), Box>; } diff --git a/modules/src/ics03_connection/msgs.rs b/modules/src/ics03_connection/msgs.rs index adeccf1ba3..1a5192e8b0 100644 --- a/modules/src/ics03_connection/msgs.rs +++ b/modules/src/ics03_connection/msgs.rs @@ -338,7 +338,7 @@ mod tests { counterparty_connection_id: "abcdefghijksdffjssdkflweldflsfladfsfwjkrekcmmsdfsdfjflddmnopqrstu" .to_string(), - ..default_con_params.clone() + ..default_con_params }, want_pass: false, }, @@ -484,7 +484,7 @@ mod tests { name: "Empty proof".to_string(), params: ConOpenTryParams { proof_init: CommitmentProof { ops: vec![] }, - ..default_con_params.clone() + ..default_con_params }, want_pass: false, }, @@ -584,7 +584,7 @@ mod tests { name: "Bad consensus height, height is 0".to_string(), params: ConOpenAckParams { consensus_height: 0, - ..default_con_params.clone() + ..default_con_params }, want_pass: false, }, @@ -658,7 +658,7 @@ mod tests { name: "Bad proof height, height is 0".to_string(), params: ConOpenConfirmParams { proof_height: 0, - ..default_con_params.clone() + ..default_con_params }, want_pass: false, }, diff --git a/modules/src/ics04_channel/channel.rs b/modules/src/ics04_channel/channel.rs index df7c633dff..321f5e7605 100644 --- a/modules/src/ics04_channel/channel.rs +++ b/modules/src/ics04_channel/channel.rs @@ -259,7 +259,7 @@ mod tests { let tests: Vec = vec![ Test { name: "Raw channel end with missing counterparty".to_string(), - params: empty_raw_channel_end.clone(), + params: empty_raw_channel_end, want_pass: false, }, Test { @@ -307,7 +307,7 @@ mod tests { }, Test { name: "Raw channel end with correct params".to_string(), - params: raw_channel_end.clone(), + params: raw_channel_end, want_pass: true, }, ] diff --git a/modules/src/ics04_channel/msgs.rs b/modules/src/ics04_channel/msgs.rs index 11229d3099..6b5f4d8b2b 100644 --- a/modules/src/ics04_channel/msgs.rs +++ b/modules/src/ics04_channel/msgs.rs @@ -662,7 +662,7 @@ mod tests { name: "Bad connection hops (conn id too short, must be 10 chars)".to_string(), params: OpenInitParams { connection_hops: vec!["conn124".to_string()].into_iter().collect(), - ..default_params.clone() + ..default_params }, want_pass: false, }, @@ -866,7 +866,7 @@ mod tests { name: "Correct counterparty channel identifier".to_string(), params: OpenTryParams { counterparty_channel_id: "channelid34".to_string(), - ..default_params.clone() + ..default_params }, want_pass: true, }, @@ -996,7 +996,7 @@ mod tests { name: "Bad proof height, height = 0".to_string(), params: OpenAckParams { proof_height: 0, - ..default_params.clone() + ..default_params }, want_pass: false, }, @@ -1111,7 +1111,7 @@ mod tests { name: "Bad proof height, height = 0".to_string(), params: OpenConfirmParams { proof_height: 0, - ..default_params.clone() + ..default_params }, want_pass: false, }, @@ -1213,7 +1213,7 @@ mod tests { name: "Bad channel, name too long".to_string(), params: CloseInitParams { channel_id: "abcdeasdfasdfasdfasdfasdfasdfasdfasdfdgasdfasdfasdfghijklmnopqrstu".to_string(), - ..default_params.clone() + ..default_params }, want_pass: false, }, @@ -1325,7 +1325,7 @@ mod tests { name: "Bad proof height, height = 0".to_string(), params: CloseConfirmParams { proof_height: 0, - ..default_params.clone() + ..default_params }, want_pass: false, }, diff --git a/modules/src/ics07_tendermint/client_def.rs b/modules/src/ics07_tendermint/client_def.rs new file mode 100644 index 0000000000..c7611315be --- /dev/null +++ b/modules/src/ics07_tendermint/client_def.rs @@ -0,0 +1,13 @@ +use crate::ics02_client::client_def::ClientDef; +use crate::ics07_tendermint::client_state::ClientState; +use crate::ics07_tendermint::consensus_state::ConsensusState; +use crate::ics07_tendermint::header::Header; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TendermintClient; + +impl ClientDef for TendermintClient { + type Header = Header; + type ClientState = ClientState; + type ConsensusState = ConsensusState; +} diff --git a/modules/src/ics07_tendermint/client_state.rs b/modules/src/ics07_tendermint/client_state.rs index fa5f3fa6d5..e223867dea 100644 --- a/modules/src/ics07_tendermint/client_state.rs +++ b/modules/src/ics07_tendermint/client_state.rs @@ -1,29 +1,34 @@ use crate::ics02_client::client_type::ClientType; +use crate::ics07_tendermint::consensus_state::ConsensusState; +use crate::ics07_tendermint::error::{Error, Kind}; use crate::ics23_commitment::CommitmentRoot; -use crate::ics07_tendermint::error::{Error, Kind}; -use crate::ics07_tendermint::header::Header; -use crate::ics24_host::identifier::ClientId; use serde_derive::{Deserialize, Serialize}; use std::time::Duration; -use tendermint::lite::Header as liteHeader; +use tendermint::block::Height; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ClientState { - id: ClientId, - trusting_period: Duration, - unbonding_period: Duration, - frozen_height: crate::Height, - latest_header: Header, + pub chain_id: String, + // pub trust_level: TrustLevel, + pub trusting_period: Duration, + pub unbonding_period: Duration, + pub max_clock_drift: Duration, + pub latest_height: crate::Height, + pub frozen_height: crate::Height, + //pub proof_specs: Specs } impl ClientState { pub fn new( - id: String, + chain_id: String, + // trust_level: TrustLevel, trusting_period: Duration, unbonding_period: Duration, - latest_header: Header, + max_clock_drift: Duration, + latest_height: crate::Height, frozen_height: crate::Height, + // proof_specs: Specs ) -> Result { // Basic validation of trusting period and unbonding period: each should be non-zero. if trusting_period <= Duration::new(0, 0) { @@ -43,32 +48,40 @@ impl ClientState { } // Basic validation for the frozen_height parameter. - if frozen_height != 0 { + if frozen_height != Height(0) { return Err(Kind::ValidationError .context("ClientState cannot be frozen at creation time") .into()); } - // Initially, no validation is needed for the `latest_header`. This has to be validated - // upon updating a client (see `update_client.rs` and fn - // `ClientState::verify_client_consensus_state`). + // Basic validation for the frozen_height parameter. + if latest_height <= Height(0) { + return Err(Kind::ValidationError + .context("ClientState latest height cannot be smaller than zero") + .into()); + } Ok(Self { // TODO: Consider adding a specific 'IdentifierError' Kind, akin to the one in ICS04. - id: id.parse().map_err(|e| Kind::ValidationError.context(e))?, + chain_id, trusting_period, unbonding_period, - latest_header, + max_clock_drift, frozen_height, + latest_height, }) } } -impl crate::ics02_client::state::ClientState for ClientState { - type ValidationError = Error; +impl From for ClientState { + fn from(_: ConsensusState) -> Self { + todo!() + } +} - fn client_id(&self) -> ClientId { - self.id.clone() +impl crate::ics02_client::state::ClientState for ClientState { + fn chain_id(&self) -> String { + self.chain_id.clone() } fn client_type(&self) -> ClientType { @@ -76,18 +89,18 @@ impl crate::ics02_client::state::ClientState for ClientState { } fn get_latest_height(&self) -> crate::Height { - self.latest_header.signed_header.header.height() + self.latest_height } fn is_frozen(&self) -> bool { // If 'frozen_height' is set to a non-zero value, then the client state is frozen. - self.frozen_height != 0 + self.frozen_height != Height(0) } fn verify_client_consensus_state( &self, _root: &CommitmentRoot, - ) -> Result<(), Self::ValidationError> { + ) -> Result<(), Box> { unimplemented!() } } @@ -95,10 +108,9 @@ impl crate::ics02_client::state::ClientState for ClientState { #[cfg(test)] mod tests { use crate::ics07_tendermint::client_state::ClientState; - use crate::ics07_tendermint::header::test_util::get_dummy_header; - use crate::ics07_tendermint::header::Header; use crate::test::test_serialization_roundtrip; use std::time::Duration; + use tendermint::block::Height; use tendermint_rpc::endpoint::abci_query::AbciQuery; #[test] @@ -123,17 +135,19 @@ mod tests { id: String, trusting_period: Duration, unbonding_period: Duration, - latest_header: Header, + max_clock_drift: Duration, + latest_height: crate::Height, frozen_height: crate::Height, } // Define a "default" set of parameters to reuse throughout these tests. let default_params: ClientStateParams = ClientStateParams { - id: "abcdefghijkl".to_string(), + id: "thisisthechainid".to_string(), trusting_period: Duration::from_secs(64000), unbonding_period: Duration::from_secs(128000), - latest_header: get_dummy_header(), - frozen_height: 0, + max_clock_drift: Duration::from_millis(3000), + latest_height: Height(10), + frozen_height: Height(0), }; struct Test { @@ -148,18 +162,10 @@ mod tests { params: default_params.clone(), want_pass: true, }, - Test { - name: "Invalid client id".to_string(), - params: ClientStateParams { - id: "9000".to_string(), - ..default_params.clone() - }, - want_pass: false, - }, Test { name: "Invalid frozen height parameter (should be 0)".to_string(), params: ClientStateParams { - frozen_height: 1, + frozen_height: Height(1), ..default_params.clone() }, want_pass: false, @@ -185,7 +191,7 @@ mod tests { params: ClientStateParams { trusting_period: Duration::from_secs(11), unbonding_period: Duration::from_secs(10), - ..default_params.clone() + ..default_params }, want_pass: false, }, @@ -200,7 +206,8 @@ mod tests { p.id, p.trusting_period, p.unbonding_period, - p.latest_header, + p.max_clock_drift, + p.latest_height, p.frozen_height, ); diff --git a/modules/src/ics07_tendermint/consensus_state.rs b/modules/src/ics07_tendermint/consensus_state.rs index dd4ba0ab80..1b6d0ab8bb 100644 --- a/modules/src/ics07_tendermint/consensus_state.rs +++ b/modules/src/ics07_tendermint/consensus_state.rs @@ -2,13 +2,14 @@ use crate::ics02_client::client_type::ClientType; use crate::ics23_commitment::CommitmentRoot; use serde_derive::{Deserialize, Serialize}; +use tendermint::Hash; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ConsensusState { - root: CommitmentRoot, - height: crate::Height, - timestamp: tendermint::time::Time, - validator_set: tendermint::validator::Set, + pub height: crate::Height, + pub timestamp: tendermint::time::Time, + pub root: CommitmentRoot, + pub next_validators_hash: Hash, } impl ConsensusState { @@ -16,20 +17,18 @@ impl ConsensusState { root: CommitmentRoot, height: crate::Height, timestamp: tendermint::time::Time, - validator_set: tendermint::validator::Set, + next_validators_hash: Hash, ) -> Self { Self { root, height, timestamp, - validator_set, + next_validators_hash, } } } impl crate::ics02_client::state::ConsensusState for ConsensusState { - type ValidationError = crate::ics07_tendermint::error::Error; - fn client_type(&self) -> ClientType { ClientType::Tendermint } @@ -42,7 +41,7 @@ impl crate::ics02_client::state::ConsensusState for ConsensusState { &self.root } - fn validate_basic(&self) -> Result<(), Self::ValidationError> { + fn validate_basic(&self) -> Result<(), Box> { unimplemented!() } } diff --git a/modules/src/ics07_tendermint/header.rs b/modules/src/ics07_tendermint/header.rs index ffb1085ca1..d86d13de67 100644 --- a/modules/src/ics07_tendermint/header.rs +++ b/modules/src/ics07_tendermint/header.rs @@ -5,14 +5,28 @@ use tendermint::block::signed_header::SignedHeader; use tendermint::validator::Set as ValidatorSet; use crate::ics02_client::client_type::ClientType; +use crate::ics07_tendermint::consensus_state::ConsensusState; +use crate::ics23_commitment::CommitmentRoot; use crate::Height; /// Tendermint consensus header #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Header { - pub signed_header: SignedHeader, - pub validator_set: ValidatorSet, - pub next_validator_set: ValidatorSet, + pub signed_header: SignedHeader, // contains the commitment root + pub validator_set: ValidatorSet, // the validator set that signed Header + pub trusted_height: Height, // the height of a trusted header seen by client less than or equal to Header + pub trusted_validator_set: ValidatorSet, // the last trusted validator set at trusted height +} + +impl Header { + pub(crate) fn consensus_state(&self) -> ConsensusState { + ConsensusState { + height: self.signed_header.header.height, + timestamp: self.signed_header.header.time, + root: CommitmentRoot::from_bytes(&self.signed_header.header.app_hash), + next_validators_hash: self.signed_header.header.next_validators_hash, + } + } } impl crate::ics02_client::header::Header for Header { @@ -21,9 +35,12 @@ impl crate::ics02_client::header::Header for Header { } fn height(&self) -> Height { - use tendermint::lite::types::Header; - self.signed_header.header.height() + self.signed_header.header.height } + + // fn consensus_state(&self) -> &dyn crate::ics02_client::state::ConsensusState { + // &self.consensus_state() + // } } #[cfg(test)] @@ -31,6 +48,7 @@ pub mod test_util { use crate::ics07_tendermint::header::Header; use subtle_encoding::hex; use tendermint::block::signed_header::SignedHeader; + use tendermint::block::Height; use tendermint::validator::Info as ValidatorInfo; use tendermint::validator::Set as ValidatorSet; use tendermint::{vote, PublicKey}; @@ -64,7 +82,8 @@ pub mod test_util { Header { signed_header: shdr, validator_set: vs.clone(), - next_validator_set: vs.clone(), + trusted_height: Height(9), + trusted_validator_set: vs, } } } diff --git a/modules/src/ics07_tendermint/mod.rs b/modules/src/ics07_tendermint/mod.rs index d384cb4bc0..c2b0fc503e 100644 --- a/modules/src/ics07_tendermint/mod.rs +++ b/modules/src/ics07_tendermint/mod.rs @@ -1,5 +1,6 @@ //! ICS 07: Tendermint Client +pub mod client_def; pub mod client_state; pub mod consensus_state; pub mod error; diff --git a/modules/src/ics07_tendermint/msgs/create_client.rs b/modules/src/ics07_tendermint/msgs/create_client.rs index 53e8935ad7..94489ef169 100644 --- a/modules/src/ics07_tendermint/msgs/create_client.rs +++ b/modules/src/ics07_tendermint/msgs/create_client.rs @@ -1,12 +1,12 @@ -use crate::ics02_client::client_type::ClientType; -use crate::ics07_tendermint::consensus_state::ConsensusState; use crate::ics07_tendermint::header::Header; -use crate::ics23_commitment::CommitmentRoot; use crate::ics24_host::identifier::ClientId; use crate::tx_msg::Msg; use std::time::Duration; +use crate::ics02_client::client_def::{AnyClientState, AnyConsensusState}; +use crate::ics02_client::client_type::ClientType; +use crate::ics07_tendermint::client_state::ClientState; use serde_derive::{Deserialize, Serialize}; use tendermint::account::Id as AccountId; @@ -14,36 +14,58 @@ pub const TYPE_MSG_CREATE_CLIENT: &str = "create_client"; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MsgCreateClient { - client_id: ClientId, - header: Header, - trusting_period: Duration, - bonding_period: Duration, - signer: AccountId, + pub client_id: ClientId, + pub header: Header, + // trust_level: Fraction, + pub trusting_period: Duration, + pub unbonding_period: Duration, + pub max_clock_drift: Duration, + // proof_specs: ProofSpecs, + pub signer: AccountId, } impl MsgCreateClient { pub fn new( client_id: ClientId, header: Header, + // trust_level: Fraction, trusting_period: Duration, - bonding_period: Duration, + unbonding_period: Duration, + max_clock_drift: Duration, + // proof_specs: ProofSpecs, signer: AccountId, ) -> Self { Self { client_id, header, trusting_period, - bonding_period, + unbonding_period, + max_clock_drift, signer, } } - fn get_client_id(&self) -> &ClientId { + pub(crate) fn client_id(&self) -> &ClientId { &self.client_id } - fn get_header(&self) -> &Header { - &self.header + pub(crate) fn client_type(&self) -> ClientType { + ClientType::Tendermint + } + + pub(crate) fn consensus_state(&self) -> AnyConsensusState { + AnyConsensusState::Tendermint(self.header.consensus_state()) + } + + pub(crate) fn client_state(&self) -> AnyClientState { + AnyClientState::Tendermint(ClientState { + chain_id: self.header.signed_header.header.chain_id.to_string(), + trusting_period: self.trusting_period, + unbonding_period: self.unbonding_period, + max_clock_drift: self.max_clock_drift, + latest_height: self.header.signed_header.header.height, + frozen_height: 0.into(), + }) } } @@ -71,27 +93,3 @@ impl Msg for MsgCreateClient { vec![self.signer] } } - -impl crate::ics02_client::msgs::MsgCreateClient for MsgCreateClient { - type ConsensusState = ConsensusState; - - fn client_id(&self) -> &ClientId { - &self.client_id - } - - fn client_type(&self) -> ClientType { - ClientType::Tendermint - } - - fn consensus_state(&self) -> Self::ConsensusState { - let root = CommitmentRoot; // TODO - let header = &self.header.signed_header.header; - - ConsensusState::new( - root, - header.height.into(), - header.time, - self.header.validator_set.clone(), - ) - } -} diff --git a/modules/src/ics07_tendermint/msgs/update_client.rs b/modules/src/ics07_tendermint/msgs/update_client.rs index 5b5cb1350a..597e4d88f0 100644 --- a/modules/src/ics07_tendermint/msgs/update_client.rs +++ b/modules/src/ics07_tendermint/msgs/update_client.rs @@ -2,6 +2,7 @@ use crate::ics07_tendermint::header::Header; use crate::ics24_host::identifier::ClientId; use crate::tx_msg::Msg; +use crate::ics07_tendermint::consensus_state::ConsensusState; use serde_derive::{Deserialize, Serialize}; use tendermint::account::Id as AccountId; @@ -23,13 +24,17 @@ impl MsgUpdateClient { } } - fn get_client_id(&self) -> &ClientId { + fn client_id(&self) -> &ClientId { &self.client_id } - fn get_header(&self) -> &Header { + fn header(&self) -> &Header { &self.header } + + fn consensus_state(&self) -> ConsensusState { + self.header.consensus_state() + } } impl Msg for MsgUpdateClient { @@ -56,15 +61,3 @@ impl Msg for MsgUpdateClient { vec![self.signer] } } - -impl crate::ics02_client::msgs::MsgUpdateClient for MsgUpdateClient { - type Header = Header; - - fn client_id(&self) -> &ClientId { - &self.client_id - } - - fn header(&self) -> &Self::Header { - &self.header - } -} diff --git a/modules/src/ics23_commitment/mod.rs b/modules/src/ics23_commitment/mod.rs index 23251942ff..ced4f91e13 100644 --- a/modules/src/ics23_commitment/mod.rs +++ b/modules/src/ics23_commitment/mod.rs @@ -5,6 +5,12 @@ use tendermint::merkle::proof::Proof; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CommitmentRoot; +impl CommitmentRoot { + pub fn from_bytes(_bytes: &[u8]) -> Self { + // TODO + CommitmentRoot {} + } +} #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CommitmentPath; diff --git a/modules/src/lib.rs b/modules/src/lib.rs index eef337d78d..618dda4c8e 100644 --- a/modules/src/lib.rs +++ b/modules/src/lib.rs @@ -20,6 +20,7 @@ //! - ICS 24: Host Requirements pub mod events; +pub mod handler; pub mod ics02_client; pub mod ics03_connection; pub mod ics04_channel; @@ -33,7 +34,7 @@ pub mod try_from_raw; pub mod tx_msg; /// Height of a block, same as in `tendermint` crate -pub type Height = tendermint::lite::Height; +pub type Height = tendermint::block::Height; #[cfg(test)] mod test; diff --git a/relayer-cli/src/commands/query/channel.rs b/relayer-cli/src/commands/query/channel.rs index 7c26452aaa..71b4609258 100644 --- a/relayer-cli/src/commands/query/channel.rs +++ b/relayer-cli/src/commands/query/channel.rs @@ -189,7 +189,7 @@ mod tests { name: "Bad channel, name too short".to_string(), params: QueryChannelEndCmd { channel_id: Some("chshort".to_string()), - ..default_params.clone() + ..default_params }, want_pass: false, }, diff --git a/relayer-cli/src/commands/query/client.rs b/relayer-cli/src/commands/query/client.rs index a951b6ba80..80aee8facc 100644 --- a/relayer-cli/src/commands/query/client.rs +++ b/relayer-cli/src/commands/query/client.rs @@ -348,7 +348,7 @@ mod tests { name: "No client id specified".to_string(), params: QueryClientStateCmd { client_id: None, - ..default_params.clone() + ..default_params }, want_pass: false, }, @@ -356,7 +356,7 @@ mod tests { name: "Bad client id, non-alpha".to_string(), params: QueryClientStateCmd { client_id: Some("p34".to_string()), - ..default_params.clone() + ..default_params }, want_pass: false, }, @@ -434,7 +434,7 @@ mod tests { name: "No client id specified".to_string(), params: QueryClientConnectionsCmd { client_id: None, - ..default_params.clone() + ..default_params }, want_pass: false, }, @@ -442,7 +442,7 @@ mod tests { name: "Bad client id, non-alpha".to_string(), params: QueryClientConnectionsCmd { client_id: Some("p34".to_string()), - ..default_params.clone() + ..default_params }, want_pass: false, }, diff --git a/relayer-cli/src/commands/query/connection.rs b/relayer-cli/src/commands/query/connection.rs index 74c46f0694..e03e6871d7 100644 --- a/relayer-cli/src/commands/query/connection.rs +++ b/relayer-cli/src/commands/query/connection.rs @@ -141,7 +141,7 @@ mod tests { name: "No connection id specified".to_string(), params: QueryConnectionEndCmd { connection_id: None, - ..default_params.clone() + ..default_params }, want_pass: false, }, @@ -149,7 +149,7 @@ mod tests { name: "Bad connection, non-alpha".to_string(), params: QueryConnectionEndCmd { connection_id: Some("conn01".to_string()), - ..default_params.clone() + ..default_params }, want_pass: false, }, @@ -157,7 +157,7 @@ mod tests { name: "Bad connection, name too short".to_string(), params: QueryConnectionEndCmd { connection_id: Some("connshort".to_string()), - ..default_params.clone() + ..default_params }, want_pass: false, }, diff --git a/relayer-cli/tests/acceptance.rs b/relayer-cli/tests/acceptance.rs index 2da38d04ca..9063229a18 100644 --- a/relayer-cli/tests/acceptance.rs +++ b/relayer-cli/tests/acceptance.rs @@ -27,7 +27,7 @@ use once_cell::sync::Lazy; /// the runner acquire a mutex when executing commands and inspecting /// exit statuses, serializing what would otherwise be multithreaded /// invocations as `cargo test` executes tests in parallel by default. -pub static RUNNER: Lazy = Lazy::new(|| CmdRunner::default()); +pub static RUNNER: Lazy = Lazy::new(CmdRunner::default); /// Use `Config::default()` value if no config or args #[test] diff --git a/relayer-cli/tests/integration.rs b/relayer-cli/tests/integration.rs index ac6efe2704..16de6a4001 100644 --- a/relayer-cli/tests/integration.rs +++ b/relayer-cli/tests/integration.rs @@ -63,7 +63,7 @@ fn query_connection_id() { assert_eq!(query.counterparty().connection_id(), "connectionidtwo"); assert_eq!( query.counterparty().prefix(), - &CommitmentPrefix::new("prefix".as_bytes().to_vec()) + &CommitmentPrefix::new(b"prefix".to_vec()) ); assert_eq!( query.versions(),