Skip to content

Commit 8fb1d03

Browse files
committed
docs(voyager): more docs
1 parent 255cd83 commit 8fb1d03

File tree

1 file changed

+156
-22
lines changed

1 file changed

+156
-22
lines changed

voyager/CONCEPTS.md

Lines changed: 156 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,32 @@
44

55
## Modules and Plugins
66

7-
All functionality in voyager is provided by modules and plugins. Modules provide various forms of read-only data, such as the latest height of a chain or a state proof. Plugins, on the other hand, directly interact with the queue - every plugin has their own [topic queue](../lib/voyager-vm/README.md) with it's plugin name as the topic, along with an interest filter that can pull messages into this queue. Plugins also define their own internal message types that they can use to pass data around between calls to their internal queue (or even between other plugins).
7+
All functionality in voyager is provided by modules and plugins. Modules provide various forms of read-only data, such as the latest height of a chain or a state proof. Plugins, on the other hand, directly interact with the queue (see [*Plugins and the Queue*](#plugins-and-the-queue) for more information).
88

99
## Types
1010

1111
### IBC Specification
1212

1313
An IBC specification defines the semantics of a light client based bridging protocol. A specification must have the following properties:
1414

15-
- some notion of a "light client update"
16-
- a store specification, where client and consensus states are stored (among any other states required by the IBC specification)
17-
- this store is required to be provable (i.e. the host environment must have some form of "proof" for it's storage, most likely merkleized)
15+
- Some notion of a "light client update"
16+
- A store specification, where client and consensus states are stored (among any other states required by the IBC specification)
17+
- This store is required to be provable, i.e. the host environment must have some form of "proof" for it's storage. This is most likely achieved via a merkleized state trie, although this is not strictly required.
1818

19-
Everything else is an implementation detail of the IBC specification.
19+
Everything else is an implementation detail of the IBC specification. This flexibility allows voyager to trivially support other IBC-like protocols, such as traditional IBC (referred to as `ibc-classic` throughout these docs) alongside `ibc-union`.
2020

2121
### Chain
2222

2323
A chain is defined by a few basic properties:
2424

25-
- produces blocks with an incrementing height (sometimes also referred to as "block number" or "slot")
26-
- a consensus with some notion of finality, where blocks older than the latest finalized height will never reorg and are considered finalized
27-
- a storage layer with provable state
28-
- one or more IBC interfaces
25+
- Produces blocks with an incrementing height (sometimes also referred to as "block number" or "slot")
26+
- A consensus with some notion of finality, where blocks older than the latest finalized height will never reorg and are considered finalized
27+
- A storage layer with provable state
28+
- One or more IBC interfaces
2929

3030
### Consensus
3131

32-
A chain's consensus defines the client and consensus state types stored in the clients that verify this consensus.
32+
A chain's consensus defines the client and consensus state types stored in the clients that verify said consensus mechanism.
3333

3434
#### Examples
3535

@@ -51,18 +51,152 @@ An IBC interface defines the entrypoints of an IBC specification implementation
5151

5252
Clients are the mechanism used to verify a counterparty consensus. Clients are defined by 4 properties:
5353

54-
- compatible with an IBC specification
55-
- on an IBC interface
56-
- for a specific consensus mechanism
57-
- which is verified via a consensus verification specification
54+
- Compatible with an IBC specification
55+
- On an IBC interface
56+
- Verifies a specific consensus mechanism
57+
- For a specific IBC specification
5858

5959
#### Examples
6060

61-
| IBC interface | consensus | verifier |
62-
|---------------------|--------------|--------------------|
63-
| `ibc-go-v8/08-wasm` | `cometbls` | `cometbls-groth16` |
64-
| `ibc-go-v8/08-wasm` | `cometbls` | `11-cometbls` |
65-
| `ibc-go-v8/native` | `cometbls` | `cometbls-groth16` |
66-
| `ibc-solidity` | `cometbls` | `cometbls-groth16` |
67-
| `ibc-go-v8/native` | `tendermint` | `07-tendermint` |
68-
| `ibc-go-v8/08-wasm` | `tendermint` | `07-tendermint` |
61+
| IBC interface | consensus | verifier | IBC Specification |
62+
|---------------------|--------------------------|--------------------------|--------------------|
63+
| `ibc-cosmwasm` | `cometbls` | `cometbls-groth16` | `ibc-union` |
64+
| `ibc-cosmwasm` | `tendermint` | `tendermint` | `ibc-union` |
65+
| `ibc-cosmwasm` | `ethereum` | `ethereum-sync-protocol` | `ibc-union` |
66+
| `ibc-solidity` | `state-lens/ics23/ics23` | `state-lens/ics23/ics23` | `ibc-union` |
67+
| `ibc-solidity` | `cometbls` | `cometbls-groth16` | `ibc-union` |
68+
| `ibc-go-v8/08-wasm` | `tendermint` | `07-tendermint` | `ibc-union` |
69+
| `ibc-go-v8/08-wasm` | `cometbls` | `cometbls-groth16` | `ibc-classic` |
70+
| `ibc-go-v8/08-wasm` | `cometbls` | `11-cometbls` | `ibc-classic` |
71+
| `ibc-go-v8/native` | `cometbls` | `cometbls-groth16` | `ibc-classic` |
72+
| `ibc-go-v8/native` | `tendermint` | `07-tendermint` | `ibc-classic` |
73+
74+
## Features
75+
76+
The voyager binary exposes a JSON-RPC interface to allow for querying any configured chain. For example, you can query the state of any client on any chain, as long as the state module for the host chain is configured (using the voyager binary's cli):
77+
78+
```sh
79+
voyager rpc -r voy.run client-state union-1 1 --height 2194359 | jq
80+
```
81+
82+
```json
83+
{
84+
"height": "2194359",
85+
"state": "0x000000000100000000000000010000004b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe955730c65f000000001c6e5f0100000000000000000000000000ee4ea8d358473f0fcebf0329feed95d56e8c04d700"
86+
}
87+
```
88+
89+
If there is a finality module configured for the host chain as well, then `--height` can be omitted (as it will default to `latest`):
90+
91+
```sh
92+
voyager rpc -r voy.run client-state union-1 1 | jq
93+
```
94+
95+
```json
96+
{
97+
"height": "1-2194387",
98+
"state": "0x000000000100000000000000010000004b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe955730c65f000000001c6e5f0100000000000000000000000000ee4ea8d358473f0fcebf0329feed95d56e8c04d700"
99+
}
100+
```
101+
102+
And finally, if the client module is configured for whatever type of client this is (in this case, it happens to be `ethereum` on `ibc-cosmwasm`), `--decode` can be passed as well to receive the client state as a JSON value instead of the raw bytes:
103+
104+
```sh
105+
voyager rpc -r voy.run client-state union-1 1 --decode | jq
106+
```
107+
108+
```json
109+
{
110+
"height": "1-2194412",
111+
"state": {
112+
"data": {
113+
"chain_id": 1,
114+
"chain_spec": "mainnet",
115+
"frozen_height": "0",
116+
"genesis_time": 1606824023,
117+
"genesis_validators_root": "0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95",
118+
"ibc_contract_address": "0xee4ea8d358473f0fcebf0329feed95d56e8c04d7",
119+
"latest_height": 23031324
120+
},
121+
"version": "v1"
122+
}
123+
}
124+
```
125+
126+
This general concept of modularity is present in all areas of voyager. As another example, many EVM chains (various EVM L2s, custom geth fork L1s such as BSC, or fully custom EVM-compatible chains such as SEI), many of the interfaces are the exact same as ethereum mainnet. In these cases, the ethereum state module can be completely reused for these chains, just configured with a different chain ID and RPC url. The same applies to all modules, meaning that when adding support to voyager for a new chain, often times a vast majority of the work required can be fully reused from existing plugins and modules.
127+
128+
## Plugins and the queue
129+
130+
Plugins are a special type of module that also have access to the message queue. Every plugin has their own [topic queue](../lib/voyager-vm/README.md) with it's plugin name as the topic, along with an interest filter that can pull messages into this queue. Plugins also define their own internal message types that they can use to pass data around between calls to their internal queue (or even between other plugins).
131+
132+
For more information about plugin lifecycle and management, see the [`voyager-plugin-protocol`](../lib/voyager-plugin-protocol) crate.
133+
134+
## Putting it all together
135+
136+
The ability to query any chain in an abstract manner also drastically improves the DX and reliability of writing new plugins and modules. One area in particular where this architecture shines is when dealing with [recursive clients] (sometimes also referred to as conditional clients). Recursive clients inherently rely on state from other chains, such as L2 settlement in relation to L1 finality for the L2 finality, or requiring potentially multiple clients to be updated before the recursive client itself can be updated.
137+
138+
A good example of this is our [state lens client architecture][state-lens], where many modules are fully reused from existing modules. The finality of a state lens client is the finality of the "L2" client being tracked through the hop chain - this means that no additional module is required for finality, as the target chain's finality module will be used directly. Additionally, no *new* state or proof modules are required to be loaded when dealing with state lens clients, since these modules will need to be loaded for the host chain where the state lens client is on anyways. There are, however, several new plugins and modules that are required for this architecture to work:
139+
140+
- **Client Module**: This is standard for all new client types. The client module provides the coded for encoding and decoding various states for this client.
141+
- **Client Bootstrap Module**: Similar to the client module, this is also standard for all new client types, however this is only required for creating new clients.
142+
- **Client Update Plugin**: This is the most complex part of the state lens architecture. Up to two individual client updates are required to update a state lens client: the L2 client on the L1 and the L1 client on the L0 (the host chain).
143+
144+
This is trivially achieved by leveraging the voyager-vm messages:
145+
146+
```rs
147+
// do all contained operations concurrently
148+
conc([
149+
// update the l2 client on the l1
150+
promise(
151+
[
152+
// fetch the update headers of the l2 client
153+
call(FetchUpdateHeaders { /* snip */ })
154+
],
155+
// this is the data queue of the promise callback, this allows for configuring data on creation of the promise
156+
// in this case, there is no extra data, so it can be left empty
157+
[],
158+
// this is the callback that will process the data once all messages in the internal queue are processed
159+
AggregateSubmitTxFromOrderedHeaders { /* snip */ },
160+
),
161+
// do all contained operations in sequence, waiting until the head message fully resolves (i.e. returns no additional non-data messages) before processing the next message
162+
seq([
163+
// wait for the trusted height of the client we just updated to be finalized on the hop chain
164+
// without this, weird things can happen with transaction ordering and reorgs
165+
call(WaitForTrustedHeight { /* snip */ }),
166+
// call back into this plugin to update the other clients
167+
call(PluginMessage::new(
168+
self.plugin_name(),
169+
ModuleCall::from(FetchUpdateAfterL1Update { /* snip */ }),
170+
))
171+
]),
172+
])
173+
```
174+
175+
The handling of `FetchUpdateAfterL1Update` is as follows:
176+
177+
```rs
178+
conc([
179+
// this promimse is the same as the one above, except this time we're updating the L1 client on the L0
180+
promise(
181+
[call(FetchUpdateHeaders { /* snip */ })],
182+
[],
183+
AggregateSubmitTxFromOrderedHeaders { /* snip */ },
184+
),
185+
seq([
186+
call(WaitForTrustedHeight { /* snip */ }),
187+
// wait for 1 extra block to ensure that the L1 update is in state, and this update will not end up in the same block (and potentially get reordered)
188+
call(WaitForHeightRelative { /* snip */ }),
189+
// this contains the actual headers for *this* client update.
190+
data(OrderedHeaders { /* snip */ }),
191+
]),
192+
])
193+
```
194+
195+
In building these messages, several additional modules and plugins are also needed. To update the L2 on the L1, the L2 client update plugin is required (as well as all of it's transitive requirements), and the same goes for the L1 on the L0. Additionally, in order to actually *submit* these intermediate client updates on chain, transactiopn plugins for both the L1 and L0 are required to be loaded. All of state, proof, and finality modules are also required to be loaded for the L1 as well (recall that the client update of the state lens client contains a state proof of the L2 state in the L1).
196+
197+
This may seem like a lot of requirements, however remember that all of the dependencies listed above were in this case already written - all that needed to be done was to configure them for the chains we need to use here, and to build the state lens client logic only 1 plugin (client update) and 2 modules (client and client bootstrap) needed to be written from scratch. The same concepts, with differing degrees of reusability, apply to L2s (arbitrum, optimism, various types of rollups), customized execution environments (SEI/ethermint), novel consensus mechanisms (beacon-kit), and even entirely new chains (sui, aptos).
198+
199+
The full non-abridged implementation of the state lens client update plugin can be found [here](../voyager/plugins/client-update/state-lens).
200+
201+
[state-lens]: https://research.union.build/State-Lenses-9e3d6578ec0e48fca8e502a0d28f485c
202+
[recursive clients]: https://docs.union.build/protocol/connections/recursive

0 commit comments

Comments
 (0)