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

ADR 003: Handler implementation #197

Merged
merged 29 commits into from
Aug 19, 2020
Merged

Conversation

romac
Copy link
Member

@romac romac commented Aug 7, 2020

Closes: cosmos/ibc-rs#118

Description

This PR introduces a initial version of ADR 003 which provides recommendations for the implementation of IBC handlers.

Rendered


For contributor use:

  • Unit tests written
  • Added test to CI if applicable
  • Updated CHANGELOG_PENDING.md
  • Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
  • Updated relevant documentation (docs/) and code comments
  • Re-reviewed Files changed in the Github PR explorer

@romac
Copy link
Member Author

romac commented Aug 7, 2020

This PR also provides an implementation of the "Create Client" handler together with unit tests for it.


## Decision

In this chapter, we provide recommendation for implementing IBC handlers within the `ibc-rs` crate.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ADR :P not chapter.

docs/architecture/003-handler-implementation.md Outdated Show resolved Hide resolved

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. TODO: Describe event type.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this TODO here, maybe we can link to here for an overview (though they are not precisely the same)?

https://github.com/informalsystems/ibc-rs/blob/master/modules/src/events.rs#L15


### Logging

IBC handlers must be able to log information for introspectability and ease of debugging.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if introspectability is right (does that mean self-examination?). Maybe we mean here to say that the handlers log information for exporting it in a simple/plain format?

@@ -0,0 +1,552 @@
# ADR 003: Handler implementation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I propose we use the term "protocol" to replace "handlers".
The "message processing logic" for a given codebase is effectively implementing the "protocol".

The term "protocol" is still imperfect, but easier to talk about than "message processing logic" and less contentious than "handler". A protocol consists of processing functions process_msg_x, process_msg_y etc. so that any input message is processed by such a function.

Copy link
Collaborator

@ancazamfir ancazamfir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Loved reading this ADR and code! Looks great! I haven't checked all the details of the actual message processing logic, that will be easy to fix if needed. My doubts are still around the context and keeper in general, thinking also about the context for connection, channel and packets. Left a comment against the tests for create client. Wondering if it would be easier to have separate test infrastructure to help with the setup of a mock chain/ app state.


#[test]
fn test_create_client_ok() {
let mock = MockClientContext {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As shown below, this is missing the client_id.
What is the expectation on the caller that needs to give a context to process() in addition to the message? Specifically for clients, if the client does not exist, the context should still include a valid client_id and this test passes as long as one is given. Outside tests, is the caller expected to check if this is a MsgCreateClient create a context with None for state and type if the client doesn't exist or return existing if it does? We briefly talked about this last week and this is still an aspect we should clarify. For connections the context/ keeper passed to process() needs both client and connection context, for channels we need client, connection and channel, etc. Things will become more complex when setting up all the mocks for tests.

But the bigger concern is that the IBC handler (as per ICS) logic is split somewhat between handle() and the caller.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expectations are captured by the CientContext trait:

pub trait ClientContext<CD>
where
    CD: ClientDef,
{
    fn client_type(&self, client_id: &ClientId) -> Option<ClientType>;
    fn client_state(&self, client_id: &ClientId) -> Option<CD::ClientState>;
    fn consensus_state(&self, client_id: &ClientId, height: Height) -> Option<CD::ConsensusState>;
}

The fact that the mock requires a client id is an implementation detail of this particular mock struct.
One could imagine implementing the ClientContext trait in a different way, but the way it's done here is imho pretty straightforward as one can exercise all code paths in the process function just by constructing different values of the mock struct.

But the bigger concern is that the IBC handler (as per ICS) logic is split somewhat between handle() and the caller.

Could you expand on that, in the context of what I wrote above, if that still makes sense?

@romac
Copy link
Member Author

romac commented Aug 11, 2020

My doubts are still around the context and keeper in general, thinking also about the context for connection, channel and packets.

Agreed, definitely something we need to take a closer look at, by eg. writing tests for the module-level handler/dispatcher.

@romac romac marked this pull request as ready for review August 11, 2020 12:13
Comment on lines 8 to 24
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 MsgCreateClient<CD: ClientDef> {
pub client_id: ClientId,
pub client_type: ClientType,
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 MsgUpdateClient<CD: ClientDef> {
pub client_id: ClientId,
pub header: CD::Header,
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The structure of MsgCreateClient is different depending on the client type. Same for MsgUpdateClient. For example for tendermint client, the MsgCreateClient includes fields like trusting_period, unbonding_period, etc. While for loopback (not implemented) or other client types, these are not present while others might be. Not sure how we can implement the ics02 messages without traits.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I think this will work if we create the concrete ClientState, ConsensusState, etc, from the wire/ raw message and then we call dispatch -> process. So some of the logic will be in this pre-processing step. I will give it a try.

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
Copy link
Member

@ebuchman ebuchman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, very well written!

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Here is the [list of all IBB-related events][events], as seen by the relayer.
Here is the [list of all IBC-related events][events], as seen by the relayer.

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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not clear what this means?

impl<T> HandlerOutputBuilder<T> {
pub fn log(&mut self, log: impl Into<Log>);
pub fn emit(&mut self, event: impl Into<Event>);
pub fn with_result(self, result: T) -> HandlerOutput<T>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just result like the others? Otherwise it makes one wonder why it's not with_event? Just a minor nit maybe fine as is

struct MockConnectionReader {
connection_id: ConnectionId,
connection_end: Option<ConnectionEnd>,
client_reader: MockClientReader,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this for?


#### Keeper

Once a message processor executes successfully, some data will typically need to be persisted in the chain state
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should mention the need for atomicity here

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I read more about this somewhere?

@ebuchman ebuchman merged commit 788c36b into master Aug 19, 2020
@ebuchman ebuchman deleted the romain/ics02-client-handlers branch August 19, 2020 18:12
hu55a1n1 pushed a commit to hu55a1n1/hermes that referenced this pull request Sep 13, 2022
* 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 <adi@informal.systems>
Co-authored-by: Anca Zamfir <zamfiranca@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants