Gravity is a bridge between Ethereum and Cosmos-based blockchains. In this post, I’ll try to document how the whole system works, in an understandable way, at a medium level of detail.
First, the high level overview: Gravity lets people transfer tokens from Ethereum to Cosmos and back again by locking up tokens on the Ethereum side, and minting equivalent tokens on the Cosmos side. Unlike many bridges, Gravity is completely non-custodial- you only need to trust in the security of the Cosmos chain, not some third party bridge administrators who could run off with your funds.
Here's how Gravity can be used:
- You send 25 DAI to the Gravity contract on Ethereum, specifying that your address on Cosmos should receive the DAI.
- Validators on the Cosmos chain see that this has happened and mint 25 DAI on Cosmos for your address.
- Now the DAI can be sent to other accounts on Cosmos, used in Cosmos applications, transferred to other Cosmos chains via IBC, etc.
- To go back to Ethereum, you send the DAI to Gravity module on the Cosmos chain, specifying that your address on Ethereum should receive it.
- The Cosmos validators burn the DAI on the Cosmos chain, and send an Ethereum transaction causing the Gravity.sol contract to send the 25 DAI to your Ethereum address.
Parts of Gravity
Gravity consists of 4 parts:
- An Ethereum contract called Gravity.sol
- The Gravity Cosmos module
- The orchestrator program which is run by Cosmos validators alongside the Cosmos blockchain's code
- A market of relayers, who compete to submit transactions to Ethereum on behalf of the Cosmos validators
Gravity.sol's main function is to lock up ERC20 tokens on Ethereum, so that they can be minted on Cosmos. The tokens locked in Gravity.sol back the tokens on Cosmos.
The current Cosmos validators control Gravity.sol, in proportion to their validation power on Cosmos. Gravity.sol stores a checkpoint- this corresponds to the active validator set on the Cosmos side at a given moment in time. A checkpoint is a set of addresses and their validation power. The more validation power an address has, the more its vote counts. The methods on Gravity.sol are public, and can be called by any relayer, but they must contain the signatures of 2/3s of the validator set in the current checkpoint.
The validator set on Cosmos is always changing, as Cosmos token holders delegate tokens to different validators, changing their power. The Cosmos validators use a method called
updateValset on Gravity.sol to update its stored checkpoint to the latest validator set. If a validator does not sign off on an update, or worse, signs a fraudulent update, they can be slashed, losing funds on Cosmos.
To transfer tokens from Ethereum to Cosmos, a user calls the
sendToCosmos method. This method receives a number of ERC20 tokens from the user and locks them in Gravity.sol. It also emits an event which lets the Cosmos validators know that they should mint that number of tokens for the user on the Cosmos side.
Users send tokens on Cosmos to the Gravity module and specify which address on Ethereum should receive them. They also attach a fee to compensate whoever relays their transaction. Tokens are transferred from Cosmos to Ethereum in batches, to save on gas. The Gravity module creates batches of transactions of one type of token, and then the validators sign them.
Relayers can submit batches to the
submitBatch method. Gravity.sol:
- First checks that a batch has signatures from 2/3s of the validator set in the current checkpoint.
- Then checks that the batch has a nonce that is higher than any other batch of the same token type that was already submitted.
- Then sends ERC20 Ethereum transactions transferring the tokens to their destinations. Gravity.sol also sends the fees on the transactions to
msg.sender, the relayer.
The Gravity Cosmos module and the orchestrator
The Gravity module is a module built on Cosmos SDK. Cosmos blockchains consist of many such modules, being run by in lockstep by their validators. Every validator runs the exact same deterministic logic as every other validator. The validators come to consensus on the state of a key-value store. Individual modules own different parts of this state.
The Gravity module is responsible for minting Cosmos tokens representing ERC20 tokens on Ethereum. It works closely together with the orchestrator. The orchestrator is a program that runs on every validator, alongside the Cosmos code. Since Cosmos modules on every validator are running the same logic in lockstep, they cannot do things like sign messages or transactions with a validator's unique key. Validators running a chain with the Gravity module installed use the orchestrator to do these things.
The Gravity module must know what is going on on the Ethereum chain. This way it is able to mint tokens on Cosmos for people when they send tokens to the Gravity.sol contract on Ethereum. It also allows it to see when a batch of transactions going from Cosmos to Ethereum has been successfully processed.
Every validator on a Cosmos chain with the Gravity module must also run an Ethereum full node. The orchestrator on each validator monitors Ethereum events using this full node. There is a parameter called
EthBlockDelay. Orchestrators monitor events this far back on the Ethereum chain. This delay is to allow the validators to have confidence that a given event is fully final on the Ethereum chain, and to make sure that every validator has the same view of the chain. When the orchestrator sees an event from the Gravity.sol contract, it puts the event into a Cosmos message, signs it, and broadcasts it to the Cosmos chain.
The Gravity module receives these messages and keeps track of which validators have voted for each individual Ethereum event. When an event crosses the threshold of 2/3s of the validation power having voted for it, it is considered "observed", and it changes the Cosmos state.
SendToCosmosEvent is observed, the Gravity module mints tokens for the destination address.
TransactionBatchExecutedEvent is observed, the Gravity module eliminates the batch that was executed from the batch pool, and cancels any earlier batches of that token type. More on this process later.
Transactions going from Cosmos to Ethereum are put into batches before being sent to the Ethereum chain. This is because it is expensive to process transactions on Ethereum, and batching splits most of the cost up among all transactions in the batch.
When a user wants to send tokens from Cosmos to an address on Ethereum, they send the tokens to the Gravity module. Gravity deducts the tokens from their account, then puts a transaction in the transaction pool. The transaction pool stores all Cosmos to Ethereum transactions that are not yet in a batch.
Anyone can send a transaction requesting the Gravity module assemble a batch of a particular token type. This will most likely be done by relayers who wish to relay a batch of tokens for profit. When the Gravity module receives this message, it follows this procedure to assemble the batch:
- Find all transactions for the token
- Choose the 100 transactions with the highest fee and put them in a batch. The number of transactions may not be 1oo in the future. It may be configurable, or even dynamic.
- If there is a batch in the batch pool with a higher sum of fees than the newly assembled batch, discard the new batch. If not, include it in the batch pool. More on this later.
It then puts the batch into the batch pool. Once a batch is in the batch pool, all the validator's orchestrators make signatures over the batch and submit them in Cosmos messages. Each signature is stored alongside its batch.
Relayers are continually monitoring the batches in the batch pool. Once a batch has signatures from at least 2/3s of the validator set in the current checkpoint stored by Gravity.sol, it can be submitted to Gravity.sol. Relayers can choose for themselves whether submitting a given batch would be profitable, and submit the ones that are. If the fees included in the batch are higher than the cost of gas to submit it to Ethereum, it is a profitable batch. Of course, this is a very nuanced calculation which takes into account current average gas prices on the Ethereum chain, congestion on the chain, and the exchange rate between that token and Ethereum gas. This is why we have a free market of relayers.
Once a batch is executed on the Ethereum chain, Gravity.sol emits a
TransactionBatchExecutedEvent as detailed above. After
EthBlockDelay blocks on Ethereum, the validators submit the event to the Gravity module. When this event is fully observed (as detailed above), the batch is removed from the batch pool, since it is complete. Any batches of the same token with a lower batch nonce are also removed from the batch pool, but their transactions are also put back into the transaction pool. This is because we can be sure that it is impossible to submit them, since their batch nonces are too low. This mechanism allows transactions that were assembled into unprofitable batches to have a chance to make it into a profitable batch.
As mentioned above, the Gravity module will only assemble a new batch if there are no batches already in the batch pool with a higher sum of fees. This is to prevent the following scenario: If there were many transactions in the transaction pool with an unprofitably low fee, and a few coming in every block with a high fee, each high fee transaction might end up in a batch with a bunch of unprofitable transactions. These batches would not be profitable to submit, and so the profitable transactions would end up in unprofitable batches, and not be submitted.
By making it so that every new batch must be more profitable than any other batch that is waiting to be submitted, it gives the few profitable transactions that come in every block the chance to build up and form a batch profitable enough to submit.
Validator set updates
Validator set updates keep Gravity.sol's knowledge of the validator set on Cosmos current. This is important because Gravity's trust model requires that trusting Gravity.sol is the same as trusting the Cosmos chain running it. A validator set update must be signed by at least 2/3s of the current validator set in the checkpoint stored by Gravity.sol.
Anyone can send a Cosmos message requesting a validator set update. When this happens, the Gravity module saves a snapshot of the current validator set. The validators then sign this snapshot. Once it has the signatures of 2/3s of the validator set in Gravity.sol's current checkpoint, a relayer can submit it to the Ethereum chain. There are a few subtle points to note:
- The Gravity module does not actually know what the current checkpoint stored by Gravity.sol is. Because of the
EthBlockDelay, all of the Gravity module's information about Ethereum is out of date. It is up to relayers to decide whether a validator set update has enough signatures to be submitted.
- There is no reward to relayers for submitting a validator set update. Unlike a transaction batch, there are no fees. This may be fixed in a later update. There are many ways it could be done, on the Cosmos side or the Ethereum side. But for now, the validators of a Cosmos chain running Gravity will likely just set up an altruistic relayer which is responsible for submitting validator set updates.
- Gravity.sol is not a Cosmos light client and it does not have knowledge of the full Cosmos consensus and staking process. It is basically a multisig whose signatories are intended to be the validator set of a particular Cosmos chain. We use some slashing on the Cosmos side to ensure that this is always the case.
Slashing is an important part of Gravity's security. Validators who behave incorrectly on the Cosmos or the Ethereum side of the bridge are slashed on the Cosmos side. This is what links the security of the Gravity bridge to the security of the Cosmos chain it is run on.
Liveness slashing for batches and validator sets
By participating in the Cosmos consensus process, a validator is implicitly stating that all validator set updates and batches produced by that process are correct. If that validator does not sign these validator sets and batches, the validator is not operating correctly. After a grace period, it will be slashed for not signing.
Slashing for fake batches and validator sets
It is possible to submit batches and validator sets to the Gravity.sol contract which were never produced by the Cosmos chain, as long as they are signed by >2/3s of the validators in the contract's current checkpoint. To prevent malicious validators from forming a cartel to steal from the bridge with a fake transaction batch or validator set update, Gravity slashes validators whose signatures appear on batches and validator set updates that were never produced by the chain. This is accomplished through an evidence mechanism, where anyone can submit evidence of a signature over a fake batch or valset, and the chain will slash the validator responsible.