Replies: 1 comment
-
Yeah I think overal this is a good design. As a plugin builder, I most likely want as little complex types as possible. Just a bunch of if statements on a json object gets you very far. Configuration wise, if voyager exposes an API that I can call, for querying counterparties etc, even better. That way voyager is still SoT and I don't need to maintain a separate DB. |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
WIP!!!
The Problem
The voyager codebase is currently very monolithic, with every chain and chain connection hardcoded. While this does work very well, it very difficult to extend without intimate knowledge of the entire codebase. Ideally, it would be possible to write a "module"/"plugin" that for a single chain, data source, ibc endpoint, transaction submission, transformation, etc.
One of the main reasons that voyager is designed the way it is currently is due to how 08-wasm clients work in ibc-go currently (as of ibc-go v8): the native module wraps the counterparty state in a
Wasm<>
wrapper, which means the counterparty must be aware of this (since the state isn't just stored verbatim). This causes massive complexity when dealing with cosmos chains:Other than the above (main) points, the relaying is effectively the same - we make this all seamless in voyager by (ab)using rust's type system, and handling the
Wasm<>
wrapping as transparently as possible.Current State
An ideal interface for voyager plugins/modules would be:
* no promises. solana is weird
This is obviously quite difficult, especially point 2 - it is very difficult to express trait bounds at run time. However, looking at the voyager codebase, the vast majority of trait bounds fall into one of three broad categories:
What is the {chain/ibc stack} interface I'm interacting with?
Although not the original design, the codebase of voyager (although still quite monolithic) has evolved into a separation of "execution/ ibc interface" vs "consensus". Often times there are trait bounds such as:
link
Which is basically just a very verbose, monolithic way of saying "this is an ethereum-like chain with an evm ibc interface (our solidity ibc stack)", or:
link
Which similarly says "this is a cosmos-like chain with ibc-go".
While this design is very verbose and complex (a lot of which could be improved with an overhaul of the traits used here), it is also incredibly powerful in that it allows one to just implement a set of traits for their type and it becomes possible to use it within the message system as defined by the relay-message library. link
Can X be encoded with encoding scheme Y?
The two main IBC interfaces we interact with currently - ibc-go and our solidity ibc stack - use different encodings, protobuf and ethabi respectively. As seen above, we frequently make use of these trait bounds when defining what chains are capable of interacting with each other. For example, the implementation of
DoFetchState
for cosmos-sdk based chains (link):This states that in order to fetch state on this chain, in the context of this chain
Hc
(host chain) tracking a counterpartyTr
(tracking), both the way the host chain stores it's state ofTr
must be protobuf decodable, and the state types ofTr
must also be protobuf decodable. Similar trait bounds can be seen throughout therelay-message
library.Is the tuple
(Hc, Tr)
a valid chain connection/ doHc
andTr
know how to "talk" to each other?This one is a bit more complex - given the traits defined above, it is possible to implement any chain connecting to any chain, as long as the trait bounds are satisfied. However, just because the trait bounds are satisfied doesn't mean that
Hc
andTr
actually know how to talk to each other (for example, while it would be trivial to satisfy the trait bounds required for an ethereum<->ethereum connection, it's not actually usable since there isn't an ethereum light client that can be run on ethereum).The actual valid pairs are expressed via the top-level enum in relay-message:
union/lib/relay-message/src/lib.rs
Lines 215 to 253 in e170469
...and then expressed in the trait system as follows:
union/lib/relay-message/src/chain/ethereum.rs
Lines 1194 to 1212 in e170469
Without getting into too much detail, this is basically a way of expressing "
(Hc, Tr)
is a valid tuple as defined inAnyLightClientIdentified
". The important part here is thatHc
never knows whatTr
actually is - it just knows that it's some valid counterparty that it can connect to, and this is fully statically verified at compile time. This is, of course, extremely limiting as well, as a 3rd-party integration now needs to fork voyager, implement all the required interfaces, and modify the top-levelAnyLightClientIdentified
enum. (Incredibly non-trivial, even I find it difficult at times and I designed the whole system!)Potential Pieces to the Puzzle
We use json to represent everything in the voyager queue. For the state types that end up being encoded/decoded via various formats, they are still represented as json at the boundaries, and then (via the trait bounds mentioned in the previous section) converted to/from bytes as required.
Essentially, we need a way for a chain module to export the following functionality:
Given a counterparty ibc interface, decode my state the way it's encoded by my light client on the counterparty
As an example, the scroll light client on union is an 08-wasm client. The client running on union stores the state wrapped in
Any<Wasm<T>>
(outside the control of the light client). As such, the scroll chain plugin to voyager would need to export functionality to decode the wrapper and internal state, returning a json representation of it. An potential design could look like this (assuming json-rpc):And then there would also be the inverse operation, to encode the client state back to bytes as stored on the counterparty:
A new issue arises in the fact that these client/ consensus state types have traits implemented on them:
union/lib/unionlabs/src/traits.rs
Lines 148 to 154 in 1812ee6
union/lib/unionlabs/src/traits.rs
Lines 302 to 304 in 1812ee6
To emulate this at runtime (outside of the type system), there are two possible solutions:
Expose a json-rpc method to, given bytes (or a decoded client state json), call a "function" on it a la
client_state.height()
-> json-rpcplugin_clientStateHeight(client_state)
Use jsonpath or jaq (or something similar). Modules would expose an rpc method that would be called at startup, which would return all supported ibc interfaces (think
ibc-go/08-wasm
as used above) and the required jsonpaths to emulate calling trait methods on client state, consensus state, header, etc. This would enable a nice wrapper type pattern like this:Given an ibc event I have fetched from my chain, figure out what the counterparty is
This requirement can basically be boiled down to "what is the counterparty chain id (and module) of a client id?", and is currently handled here:
union/lib/voyager-message/src/lib.rs
Lines 252 to 320 in 1812ee6
and here (cosmos):
union/lib/block-message/src/chain/cosmos_sdk.rs
Lines 243 to 276 in 1812ee6
and here (eth):
union/lib/block-message/src/chain/ethereum.rs
Line 340 in 1812ee6
(note that for eth we assume all clients are cometbls, as that is currently the case)
There are two possible solutions to this:
union/lib/unionlabs/src/lib.rs
Lines 132 to 254 in 1812ee6
since there is no interface in ibc-go to query the "type" of a wasm client, and all wasm clients' client ids are of the form
08-wasm-{N}
, we would need to pass additional information in this query along with the client id itself (the query would essentially be a tagged enum, where the tag is the ibc interface;ibc-go/08-wasm
,ibc-go/native
,evm
, etc). Any chain module that exposes theibc-go/08-wasm
ibc interface would need to include the client type as parsed out of the wasm blob here.Transformations/ Optimization Passes
Along with supporting a custom interface for chain plugins/modules, we should also allow for writing arbitrary plugins that build on top of the vm infrastructure directly, as defined here:
union/lib/queue-msg/src/lib.rs
Lines 76 to 156 in 1812ee6
This would be a very low-level, yet extremely powerful tool for advances use-cases, such as
Beta Was this translation helpful? Give feedback.
All reactions