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

Generic query, channel query and reduce code #152

Merged
merged 18 commits into from
Jul 20, 2020

Conversation

greg-szabo
Copy link
Member

@greg-szabo greg-szabo commented Jul 16, 2020

Closes: cosmos/ibc-rs#132 - Use TryFrom to map protobuf data structures to our domain types
Closes: cosmos/ibc-rs#131 - Reduce number of query related traits and types
Closes: cosmos/ibc-proto-rs#22 - Using serde for deserializing prost proto structs

Description

  • Implements a generic query for abci_query for the relayer, removes the now obsolete code.
  • Implements a basic channel query to show off the query function and the reduced codebase - validation still needs implementing!
  • Changes the connection and channel queries to use the generic query.
  • Disables the client query because it needs to be reimplemented using this generic query.

Instead of the built-in TryFrom trait a new TryFromRaw trait was implemented with a try_from(raw) method. This has the same purpose as the built-in TryForm would have but it also allows us to store the RawType (in an associated type) and implement transparent deserialization from the wire, through the compiled proto struct into our domain struct. Much like serde would have done in cosmos/ibc-proto-rs#22.

channel query by id was implemented as an example to see how much code needs to be written. It's not a complete implementation, the validate function needs to be properly implemented. (Just as it has to be expanded on the connection query too.)


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

Comment on lines 114 to 123
let res = block_on(query::<TendermintChain, ProtoChannel, ChannelEnd>(
&chain,
opts.height,
opts.port_id.clone(),
opts.channel_id.clone(),
opts.proof,
Request {
path: Some(TendermintPath::from_str(&"store/ibc/key").unwrap()),
data: ChannelEndsPath::new(opts.port_id, opts.channel_id)
.to_string()
.into_bytes(),
height: Some(Height::from(opts.height)),
prove: opts.proof,
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we discuss about this as I am not entirely sure about the change here and in similar places. IMO it would still be nice to have a function like query_channel that takes the chain, height, proof and channel key. This function could be called from the CLI or as part of relaying action. The peculiarities of how the query is done depending on the chain type should be in the chain implementation.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good find. :) This part of the code is admittedly not ready yet, it's an example implementation so we can see the whole code running and to be able to highlight the chain-specific instructions. (This was all over the place generating redundant code.)
As a minimum, the Request struct would need a constructor (::new). Then, the store path should be stored somewhere so we don't hardwire it in each query. Then we should have some kind of helper function so we don't do this horrible .to_string().into_bytes() stuff, which is there because the Request struct is just not optimal. The Request struct should come from the tendermint RPC client, which it can't today because it's not fully public there. (Hence it's a copy, which is also a horrible idea.)

But apart from too much wiring shown here, it has exactly the properties you're looking for with one addition: you can customize it to any query. A query_channel function's real value is that you don't have to remember the path (ChannelEndsPath::new(opts.port_id, opts.channel_id)) but everything else is the same even in another query_connection function. This will be specific to every query, so if we write a query_channel, we'll have to write it for all queries.

I guess it would be nice to see how this works with a more complex query, like with the client. Client already implements helper functions, maybe implementing it using these tools would surface the optimal ratio for helper functions vs generic functions.

Copy link
Member Author

Choose a reason for hiding this comment

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

To understand why I left this so ugly: I was focusing on the lower part of the query function, which was calling build_response and it had to be implemented for each query separately. Now it's generic enough that it's only implemented once and you don't have to do it for each query.

Copy link
Member Author

@greg-szabo greg-szabo Jul 16, 2020

Choose a reason for hiding this comment

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

More notes that I was going to mention on the catchup call: Why this specific implementation?

I wanted to separate the generics of an abci_query from the actual important information, the data. An abci_query is always the same, only its input is different each time. The same with the response: we really only care about the data in it. I think the current code is a good enough example of the response part.

The request part is up for discussion. But I would like to keep it slightly generic so we don't produce code that has to be copy-pasted. You can see that the actual input is generic (the request struct) and all details are encapsulated in it. (As it would be on a "next layer" of a layered protocol.) But we can make it simpler.

Copy link
Member Author

Choose a reason for hiding this comment

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

Here's an alternative approach where the formatting for the request is kept within the QueryXYZOptions struct by using Into<>. greg/query-channel-and-reduce-code...greg/different-query-input

The point is that the request will always require standardized parameters (within a chain), so having different structs for different queries only make sense if we can standardize their content for the RPC call.

@greg-szabo greg-szabo marked this pull request as draft July 16, 2020 12:34
if pc.id == "" {
fn try_from(value: ProtoConnectionEnd) -> Result<Self, Self::Error> {
// Todo: Is validation complete here? (Code was moved from `from_proto_connection_end`.)
if value.id == "" {
Copy link
Member

@adizere adizere Jul 17, 2020

Choose a reason for hiding this comment

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

Following the recent changes in the code, this if does not make sense anymore.

I think we can now delete this if statement and remove the todo. The caller of try_from is relayer/relay/src/query.rs:query() and this should figure out that the response value is null and consequently return an error that the response is empty (without even reaching the call to try_from). Relevant code:

https://github.com/informalsystems/ibc-rs/blob/85a94394d7cba7beac6b2b305b3c160ef6ed3ecb/relayer/relay/src/query.rs#L74-L77

Copy link
Member

@adizere adizere Jul 17, 2020

Choose a reason for hiding this comment

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

I made a commit to capture concretely my suggestion from the comment above. Full commit details here.

Relevant code:

https://github.com/informalsystems/ibc-rs/blob/980d0391e9b2c33f67834a4074c6627046aeb3bd/relayer/relay/src/query.rs#L64-L67

@greg-szabo if this makes sense with what you have in mind, then we can delete the if above. Also, according to clippy, we should use abci_response.value.is_empty() instead of my shady choice of abci_response.value.len() == 0 so this needs fixing as well.

Copy link
Member Author

Choose a reason for hiding this comment

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

Nice, thanks! I submitted a followup that fixes the Clippy warning.

I'm not sure how much we want to manually check different results of the response before we return, but this case seems straightforward.

There's a TODO in the type validation which I think should go into it's own issue. There's a lot to improve on that piece of code.

Copy link
Member

Choose a reason for hiding this comment

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

Awesome!
That works, we can leave the TODOs for the moment (also in Counterparty), and fix validation, perhaps add also some unit tests in a separate issue.

@@ -7,6 +7,7 @@ use serde_derive::{Deserialize, Serialize};
// Import proto declarations.
use ibc_proto::connection::ConnectionEnd as ProtoConnectionEnd;
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should consider renaming the way we import proto data structures to be as RawConnectionEnd; instead of as ProtoConnectionEnd;. The same would go for RawCounterparty and others. The reason to do this renaming is purely aesthetic and in the interest of consistency with the tendermint-rs codebase (I am referring to the RawCommitSig struct, though I am not sure that the Raw types are analogous). WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good idea to keep the same naming schema. Proto was used here for the underlying implementation which you never know. Raw makes more sense. Raw, as in coming straight from the wire.

Copy link
Member Author

Choose a reason for hiding this comment

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

Submitted a commit for it.

Ok(cs) => status_info!("connection query result: ", "{:?}", cs.connection),
Err(e) => status_info!("connection query error", "{}", e),
}
println!("{:?}", res);
Copy link
Member

Choose a reason for hiding this comment

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

The previous code with the match was slightly better, because it did not expose the whole stacktrace in case of errors, and printed the specific cs.connection object details.

Suggested change
println!("{:?}", res);
match res {
Ok(cs) => status_info!("connection query result: ", "{:?}", cs.connection),
Err(e) => status_info!("connection query error", "{}", e),
}

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought that thing is just for debugging. The CLI should return a better-formed result. But sure, the result has to be unpacked. I'll submit a commit for it. (and for the same in channel.)

(Sidenote: Please note that the type of res is different than the earlier cs. res is already the data, there's no need to call .connection.)

* Moved query parameters into the command parameters with Into.
* Implemented better Raw type handling
* Some minor error-handling was fixed
@greg-szabo greg-szabo marked this pull request as ready for review July 18, 2020 05:35
@greg-szabo
Copy link
Member Author

Please note that this PR is now ready for review. Summary of work done was updated in the PR description.

match res {
Ok(cs) => status_info!("channel query result: ", "{:?}", cs.channel),
Err(e) => status_info!("channel query error: ", "{:?}", e),
Ok(cs) => status_info!("connection query result: ", "{:?}", cs),
Copy link
Member

Choose a reason for hiding this comment

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

Typo? Let's keep "channel" instead of "connection" in these two lines.

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 curse of copy-paste

@greg-szabo greg-szabo merged commit 91bb40e into master Jul 20, 2020
@greg-szabo greg-szabo deleted the greg/query-channel-and-reduce-code branch July 20, 2020 14:14
hu55a1n1 pushed a commit to hu55a1n1/hermes that referenced this pull request Sep 13, 2022
* Added channel query and TryFromRaw trait

* Implemented generic query for abci queries, converted connection query and channel query to use that and disabled client query for now. Removed unused code.

* Fix for handling empty response value.

* Moved query parameters into the command parameters with Into. (informalsystems#155)

Co-authored-by: Adi Seredinschi <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants