Skip to content

Commit 484d8b7

Browse files
authored
docs(voyager): more docs (#4914)
2 parents 96cf6fc + d6ba4a9 commit 484d8b7

File tree

3 files changed

+325
-56
lines changed

3 files changed

+325
-56
lines changed

docs/src/content/docs/architecture/voyager/concepts.mdx

Lines changed: 165 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,65 +6,201 @@ title: "Voyager Concepts"
66

77
## Modules and Plugins
88

9-
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](https://github.com/unionlabs/union/blob/main/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).
9+
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).
1010

1111
## Types
1212

1313
### IBC Specification
1414

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

17-
- some notion of a "light client update"
18-
- a store specification, where client and consensus states are stored (among any other states required by the IBC specification)
19-
- 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)
17+
- Some notion of a "light client update"
18+
- A store specification, where client and consensus states are stored (among any other states required by the IBC specification)
19+
- 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.
2020

21-
Everything else is an implementation detail of the ibc specification.
21+
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`.
2222

2323
### Chain
2424

2525
A chain is defined by a few basic properties:
2626

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

3232
### Consensus
3333

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

3636
#### Examples
3737

38-
- cometbls
39-
- tendermint
40-
- ethereum
38+
- `cometbls`
39+
- `tendermint`
40+
- `ethereum`
4141

4242
### IBC Interface
4343

4444
An IBC interface defines the entrypoints of an IBC specification implementation on a chain. A chain can potentially have many different IBC interfaces (for example, `ibc-go` native clients vs. `08-wasm` clients), and a consensus can be verified by the same client specification on different IBC interfaces.
4545

4646
#### Examples
4747

48-
- ibc-go-v8/08-wasm
49-
- ibc-solidity
50-
- ibc-cosmwasm
48+
- `ibc-go-v8/08-wasm`
49+
- `ibc-solidity`
50+
- `ibc-cosmwasm`
5151

52-
### Client
52+
### Client Type
5353

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

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

6161
#### Examples
6262

63-
| IBC interface | consensus | verifier |
64-
|-------------------|------------|------------------|
65-
| ibc-go-v8/08-wasm | cometbls | cometbls-groth16 |
66-
| ibc-go-v8/08-wasm | cometbls | 11-cometbls |
67-
| ibc-go-v8/native | cometbls | cometbls-groth16 |
68-
| ibc-solidity | cometbls | cometbls-groth16 |
69-
| ibc-go-v8/native | tendermint | 07-tendermint |
70-
| ibc-go-v8/08-wasm | tendermint | 07-tendermint |
63+
| IBC interface | consensus | verifier | IBC Specification |
64+
|---------------------|--------------------------|--------------------------|--------------------|
65+
| `ibc-cosmwasm` | `cometbls` | `cometbls-groth16` | `ibc-union` |
66+
| `ibc-cosmwasm` | `tendermint` | `tendermint` | `ibc-union` |
67+
| `ibc-cosmwasm` | `ethereum` | `ethereum-sync-protocol` | `ibc-union` |
68+
| `ibc-solidity` | `state-lens/ics23/ics23` | `state-lens/ics23/ics23` | `ibc-union` |
69+
| `ibc-solidity` | `cometbls` | `cometbls-groth16` | `ibc-union` |
70+
| `ibc-go-v8/08-wasm` | `tendermint` | `07-tendermint` | `ibc-union` |
71+
| `ibc-go-v8/08-wasm` | `cometbls` | `cometbls-groth16` | `ibc-classic` |
72+
| `ibc-go-v8/08-wasm` | `cometbls` | `11-cometbls` | `ibc-classic` |
73+
| `ibc-go-v8/native` | `cometbls` | `cometbls-groth16` | `ibc-classic` |
74+
| `ibc-go-v8/native` | `tendermint` | `07-tendermint` | `ibc-classic` |
75+
76+
## Features
77+
78+
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):
79+
80+
```sh
81+
voyager rpc -r voy.run client-state union-1 1 --height 2194359 | jq
82+
```
83+
84+
```json
85+
{
86+
"height": "2194359",
87+
"state": "0x000000000100000000000000010000004b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe955730c65f000000001c6e5f0100000000000000000000000000ee4ea8d358473f0fcebf0329feed95d56e8c04d700"
88+
}
89+
```
90+
91+
If there is a finality module configured for the host chain as well, then `--height` can be omitted (as it will default to `latest`):
92+
93+
```sh
94+
voyager rpc -r voy.run client-state union-1 1 | jq
95+
```
96+
97+
```json
98+
{
99+
"height": "1-2194387",
100+
"state": "0x000000000100000000000000010000004b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe955730c65f000000001c6e5f0100000000000000000000000000ee4ea8d358473f0fcebf0329feed95d56e8c04d700"
101+
}
102+
```
103+
104+
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:
105+
106+
```sh
107+
voyager rpc -r voy.run client-state union-1 1 --decode | jq
108+
```
109+
110+
```json
111+
{
112+
"height": "1-2194412",
113+
"state": {
114+
"data": {
115+
"chain_id": 1,
116+
"chain_spec": "mainnet",
117+
"frozen_height": "0",
118+
"genesis_time": 1606824023,
119+
"genesis_validators_root": "0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95",
120+
"ibc_contract_address": "0xee4ea8d358473f0fcebf0329feed95d56e8c04d7",
121+
"latest_height": 23031324
122+
},
123+
"version": "v1"
124+
}
125+
}
126+
```
127+
128+
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.
129+
130+
## Plugins and the queue
131+
132+
Plugins are a special type of module that also have access to the message queue. Every plugin has their own [topic queue](https://github.com/unionlabs/union/blob/main/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).
133+
134+
For more information about plugin lifecycle and management, see the [`voyager-plugin-protocol`](https://github.com/unionlabs/union/blob/main/lib/voyager-plugin-protocol) crate.
135+
136+
## Putting it all together
137+
138+
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.
139+
140+
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:
141+
142+
- **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.
143+
144+
- **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.
145+
146+
- **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).
147+
148+
This is trivially achieved by leveraging the voyager-vm messages:
149+
150+
```rs
151+
// do all contained operations concurrently
152+
conc([
153+
// update the l2 client on the l1
154+
promise(
155+
[
156+
// fetch the update headers of the l2 client
157+
call(FetchUpdateHeaders { /* snip */ })
158+
],
159+
// this is the data queue of the promise callback, this allows for configuring data on creation of the promise
160+
// in this case, there is no extra data, so it can be left empty
161+
[],
162+
// this is the callback that will process the data once all messages in the internal queue are processed
163+
AggregateSubmitTxFromOrderedHeaders { /* snip */ },
164+
),
165+
// 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
166+
seq([
167+
// wait for the trusted height of the client we just updated to be finalized on the hop chain
168+
// without this, weird things can happen with transaction ordering and reorgs
169+
call(WaitForTrustedHeight { /* snip */ }),
170+
// call back into this plugin to update the other clients
171+
call(PluginMessage::new(
172+
self.plugin_name(),
173+
ModuleCall::from(FetchUpdateAfterL1Update { /* snip */ }),
174+
))
175+
]),
176+
])
177+
```
178+
179+
The handling of `FetchUpdateAfterL1Update` is as follows:
180+
181+
```rs
182+
conc([
183+
// this promimse is the same as the one above, except this time we're updating the L1 client on the L0
184+
promise(
185+
[call(FetchUpdateHeaders { /* snip */ })],
186+
[],
187+
AggregateSubmitTxFromOrderedHeaders { /* snip */ },
188+
),
189+
seq([
190+
call(WaitForTrustedHeight { /* snip */ }),
191+
// 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)
192+
call(WaitForHeightRelative { /* snip */ }),
193+
// this contains the actual headers for *this* client update.
194+
data(OrderedHeaders { /* snip */ }),
195+
]),
196+
])
197+
```
198+
199+
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).
200+
201+
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).
202+
203+
The full non-abridged implementation of the state lens client update plugin can be found [here](https://github.com/unionlabs/union/blob/main/voyager/plugins/client-update/state-lens).
204+
205+
[recursive clients]: /protocol/connections/recursive
206+
[state-lens]: https://research.union.build/State-Lenses-9e3d6578ec0e48fca8e502a0d28f485c

docs/src/content/docs/architecture/voyager/overview.mdx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
title: "Voyager"
33
---
44

5-
6-
import Mermaid from "#/components/Mermaid.astro";
7-
85
Relaying is hard. There are several key properties to a reliable relayer, the most important of
96
which being:
107

@@ -19,7 +16,7 @@ which being:
1916
https://github.com/clemensgg/xion-relayer-postmortem)
2017

2118
Voyager takes a novel approach to solving these problems. Internally, everything is modeled as a
22-
finite state machine, ([`voyager-vm`](https://github.com/unionlabs/union/tree/ab76fd72e114a2b8db8ad469dc587aec865e2095/lib/voyager-vm)), which is stored in postgres to ensure transactional integrity ([`pg-queue`](https://github.com/unionlabs/union/tree/main/lib/pg-queue)). Every chain
19+
finite state machine, ([`voyager-vm`](https://github.com/unionlabs/union/blob/main/lib/voyager-vm/README.md)), which is stored in postgres to ensure transactional integrity ([`pg-queue`](https://github.com/unionlabs/union/blob/main/lib/pg-queue/README.md)). Every chain
2320
query, transaction submission, and even the data itself is represented as a state within the queue.
2421
This design solves two of the properties mentioned above out of the box: **Data Integrity** and
2522
**Quick Startup Times**. Since no state is stored in Voyager itself, it is able to crash and restart
@@ -31,7 +28,7 @@ messages can safely be executed in parallel. This means, for instance, that whil
3128
fetching events from a block, another could be submitting a light client update, and another could
3229
be generating a state proof, and so on.
3330

34-
For more information on voyager's architecture, see [CONCEPTS.md](https://github.com/unionlabs/union/blob/ab76fd72e114a2b8db8ad469dc587aec865e2095/voyager/CONCEPTS.md)
31+
For more information on voyager's architecture, see [Concepts](/architecture/voyager/concepts).
3532

3633
## Light Clients
3734

0 commit comments

Comments
 (0)