-
Notifications
You must be signed in to change notification settings - Fork 39
Architecture Overview
This page provides an in-depth overview of the projects that make up the main Union repository. This page aims to ensure that auditors and contributors are familiar with Union's projects and how these projects work together.
While the Union repo hosts many projects, the projects below comprise our core infrastructure.
-
The Union Node (
uniond
) - Our CosmosSDK-based node built on top of CometBLS. -
Unionvisor - A supervisor for the
uniond
binary, ensuring it is updated correctly. - Galois - The Union prover, used to conduct zero-knowledge proofs.
- Voyager - A relayer between Union's projects and its counterparties.
The Union Node, often called by its binary uniond
, is our CosmosSDK-based node running CometBLS for consensus.
You can find information on how to run and build uniond
in the uniond
project root.
The Union Node is running our fork of the cosmos-sdk. In our fork of the cosmos-sdk, we have implemented epoch staking, CometBLS, and the bn254 signature scheme.
The Union Node contains a basic go test suite to ensure our cryptographic primitives are functioning and that the app/modules build successfully.
We also supply several linting tools via Nix commands in uniond/uniond.nix
Inspired by programs such as cosmovisor, Unionvisor monitors and performs upgrades of the uniond
binary. It is designed in order to work with reproducibly built bundles of uniond
binaries.
You can find information on how to run and build Unionvisor in the unionvisor
project root.
The project files contain extensive test data to simulate upgrades within our cargo test suite.
Galois, the Union prover, can be found in the galoisd
project root.
CLI definitions: galoisd/cmd/galoisd
.
The GRPC interface definitions: galoisd/grpc/api/v1
Minimal testing for non-adjacent blocks can be found in galoisd/pkg/lightclient/nonadjacent/circuit_test.go
.
Our relayer that integrates with Galois, optimised for relaying ZK proofs.
Warning Voyager is pending a major rewrite. The current implementation can be found in the
voyager
project root.
Voyager has minimal testing for converting between events.
Besides running our binaries, there are several workflows to be aware of to understand how our products work together.
This section will walk you through those workflows and help you understand what parts of the codebase they touch.
An IBC connection and channel need to be opened before transactions between Union and its counterparties can begin. This can be accomplished by interfacing with the Voyager following the workflow below.
These steps will demonstrate opening a connection between a local Union and Ethereum devnet.
Note This documentation aims to provide a code walk-through alongside the execution of the commands to open a channel. See our tutorial for how to open a channel for more information.
Before creating a connection and channel to conduct transactions, Voyager needs to be made aware of the client networks. This can be done with the client create
sub-command of Voyager:
# Create a union client
relayer client create union ethereum08-wasm \
--on union-devnet \
--counterparty ethereum-devnet \
--evm-preset minimal
# Create an evm client
relayer client create evm cometbls \
--on ethereum-devnet \
--counterparty union-devnet
The client create
command has two sub-commands used above: union ethereum08-wasm
and evm cometbls
.
This will use the call the create_client
function implemented by both Union
and Evm
. This function will contact the Union or Ethereum chain to get a client ID unique to the respective network.
The entry-point for the client create
command can be found here. After this point, the command follows this sequence diagram:
sequenceDiagram
participant Union
participant Relayer
participant Evm
par Create WASM Client
Relayer->>+Union: create_client
Union-->>-Relayer: client_id
and Create IBC Handler Instance
Relayer->>+Evm: create_client
Evm-->>-Relayer: client_id
end
With the client ID retrieved, the connection handshake can begin.
To start the connection handshake, we can use the connection open
sub-command of Voyager.
relayer connection open \
--to-chain union-devnet \
--to-client 08-wasm-0 \
--from-chain ethereum-devnet \
--from-client cometbls-new-0
The example above creates a connection to the Union devnet from the Ethereum devnet.
This will start the process of collecting state data from both chains, submitting a proof request to Galois, and relaying the chain state and proof between networks. While relaying this data, a connection attempt is made and confirmed.
The entry-point for this command is here. After this point, the command follows this sequence diagram:
sequenceDiagram
participant Union
participant Relayer
participant Evm
note over Union, Evm: connection handshake
Relayer->>+Evm: MsgConnectionOpenInit
Evm-->>-Relayer: connection_id
Relayer->>+Union: MsgConnectionOpenTry
Union-->>-Relayer: <<success>>
Relayer->>+Evm: MsgConnectionOpenAck
Evm-->>-Relayer: <<success>>
Relayer->>+Union: MsgConnectionOpenConfirm
Union-->>-Relayer: <<success>>
Once success has been reported, the IBC connection is established.
Finally, a channel needs to be opened for the transaction to be conducted.
To start a channel handshake, you can use the channel open
sub-command of Voyager.
relayer channel open \
--from-chain union-devnet \
--from-connection connection-1 \
--from-port wasm.CONTRACT_ADDRESS \
--to-chain ethereum-devnet \
--to-connection connection-2 \
--to-port transfer
Similar to the connection open
sub-command, the channel open
sub-command will collect state data from both chains, submit a proof request to Galois, and relay the chain state and proof between networks.
The entry-point of this command can be found here. After this point, the command follows this sequence diagram:
sequenceDiagram
participant Union
participant Relayer
participant Evm
note over Union, Evm: channel handshake
Relayer->>+Evm: MsgChannelOpenInit
Evm-->>-Relayer: channel_id
Relayer->>+Union: MsgChannelOpenTry
Union-->>-Relayer: <<success>>
Relayer->>+Evm: MsgChannelOpenAck
Evm-->>-Relayer: <<success>>
Relayer->>+Union: MsgChannelOpenConfirm
Union-->>-Relayer: <<success>>
Once success has been reported, the IBC channel is established.
Voyager and our bridged transactions depend on our ICS20 contract being deployed and instantiated via the uniond
wasm module.
In our devnet, we use nix to deploy our ICS20 transfer and ping-pong contract through our devnet genesis. However, after the contract is deployed - it still needs to be instantiated with the following command:
# Instantiate the contract
uniond tx wasm instantiate 1 \
'{"default_timeout":10000,"gov_contract":"union1jk9psyhvgkrt2cumz8eytll2244m2nnz4yt2g2","allowlist":[]}' \
--label blah \
--from alice \
--gas auto \
--keyring-backend test \
--gas-adjustment 1.3 \
--amount 100stake \
--no-admin \
--chain-id union-devnet-1 \
-y
# Check for success
uniond query wasm list-contracts-by-code 1 | yq '.contracts[-1]' --raw-output
There are two main transaction workflows: Cosmos → Ethereum and Ethereum → Cosmos.
To start a transaction from Cosmos to Ethereum, you can use the wasm command that we import into uniond
.
We use the tx wasm execute
sub-command of uniond
to call the ICS20 contract we have deployed.
Note The
channel
value comes from the channel IDs established by the channel handshake.*
uniond tx wasm execute \
union14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s3e9fe2 \
'{"transfer":{"channel":"channel-7","remote_address":"be68fc2d8249eb60bfcf0e71d5a0d2f2e292c4ed"}}' \
--amount 100stake \
--from alice \
--keyring-backend test \
--chain-id union-devnet-1 \
--gas 10000000 \
-y
To conduct a transaction from Ethereum to Cosmos, Voyager can be used to initiate the transaction. This can be done with the transfer
sub-command.
The entry point for the transfer command can be found here.
relayer transfer \
--from ethereum-devnet --to union-devnet \
--amount 10 \
--denom "transfer/channel-0/stake" \
--receiver union1jk9psyhvgkrt2cumz8eytll2244m2nnz4yt2g2 \
--source-port transfer \
--source-channel channel-0
Following the execution of this command, we'll call into the relayer::chain::evm::Evm::transfer()
function which is responsible for calling the send_transfer
function of our ICS20 contract.
Galois proofs are initiated by Voyager as part of the relayer::chain::cosmos::Ethereum::update_counterparty_client()
function. The update_counterparty_client()
function is called from three places:
- Multiple times in the connection handshake.
- Multiple times in the channel handshake.
- Once in the
relay_packets()
function that is part of therelay
sub-command of Voyager.
This section will walk through the interaction between Voyager and Galois.
Before a proof can be conducted a ProveRequest
must be constructed. A ProveRequest
is made from a vote, and a trusted and untrusted commit.
The ProveRequest
is constructed from the signatures and metadata of the most recent signed commit of the Cosmos chain.
Until we support validator set drift, the trusted and untrusted commit are the same. This commit is made up of: the list of validators, the list of signatures, and a bitmap indicating if the validators signed or not.
The vote is constructed from information about the block and chain from the commit.
The ProveRequest
is then submitted to Galois which conducts the proof and returns a ProveResponse
.
The ProveResponse
contains a zero-knowledge proof, a trusted and untrusted validator set root.
The ProveResponse
is then used in combination with the most recent signed Cosmos commit to construct a cometbls::header::Header
. The SignedHeader
is built from information form the commit, and combined with the untrusted validator set and zero-knowledge poof to form the Header
which is submitted to update_client()
to update the EVM via the IBC contract.