The Generalised Relayer is built to act as a relayer for Arbitrary Message Bridges (AMBs) using the Generalised Incentives scheme.
The goal for the Generalised Relayer is 2 fold:
- Acts as a reference implementation of a relayer that understands Generalised Incentives.
- Lower the barrier to entry resulting in greater competition which will improve relaying speed, robustness, and resistance to censorship.
Currently, the Relayer supports the following AMBs:
- Wormhole
- LayerZero
It also supports a 'Mock' AMB implementation that operates with signed messages (see the Generalised Incentives repository for more information).
Aside from the npm packages specified within package.json
, the Generalised Relayer relies on the following dependencies:
- Redis
ℹ️ There is no need to manually install/run any dependencies when running the Relayer with Docker.
The Relayer configuration is split into 2 distinct files.
⚠️ The Relayer will not run without the following configuration files.
Most of the Relayer configuration is specified within a .yaml
file located at the project's root directory. The configuration file must be named using the config.{$NODE_ENV}.yaml
format according to the environment variable NODE_ENV
of the runtime (e.g. on a production machine where NODE_ENV=production
, the configuration file must be named config.production.yaml
).
The
NODE_ENV
variable should ideally be set on the shell configuration file (i.e..bashrc
or equivalent), but may also be set by prepending it to the launch command, e.g.NODE_ENV=production docker compose up
. For more information see the Node documentation.
The .yaml
configuration file is divided into the following sections:
global
: Defines the global relayer configuration.- The
privateKey
of the account that will submit the relay transactions on all chains must be defined at this point. - Default configuration for the
monitor
,getter
,pricing
,evaluator
,submitter
andwallet
can also be specified at this point.
- The
ambs
: The AMBs configuration.- Every AMB configuration must have at least the address of the Generalised Incentives contract that implements the AMB (
incentivesAddress
) at this point, or otherwise within the chain-specific AMB configuration (underchains -> $ambName -> incentivesAddress
).
- Every AMB configuration must have at least the address of the Generalised Incentives contract that implements the AMB (
chains
: Defines the configuration for each of the chains to be supported by the relayer.- This includes the
chainId
and therpc
to be used for the chain. - Each chain may override the global
monitor
,getter
,pricing
,evaluator
,submitter
andwallet
configurations (those defined under theglobal
configuration), andamb
configurations.
- This includes the
ℹ️ For a full reference of the configuration file, see
config.example.yaml
.
Ports and docker specific configuration is set on a .env
file within the project's root directory. This includes the COMPOSE_PROFILES
environment variable which defines the docker services to enable (e.g. to enable the docker-compose.yaml
services tagged with the wormhole
profile set COMPOSE_PROFILES=wormhole
).
ℹ️ See
.env.example
for the required environment variables.
The simplest way to run the relayer is via docker compose
(refer to the Docker documentation for installation instructions). Run the Relayer with:
docker compose up [-d]
The -d
option detaches the process to the background.
Install the required dependencies with:
pnpm install
- NOTE: The
devDependencies
are required to build the project. If running on a production machine whereNODE_ENV=production
, usepnpm install --prod=false
Initiate a Redis database with:
docker container run -p 6379:6379 redis
- This command sets
6379
as the port for Redis communication. Make sure this port is correctly set on the.env
configuration file.
Build and start the Relayer with:
pnpm start
For further insight into the requirements for running the Relayer see the docker-compose.yaml
file.
The Relayer is devided into 6 main services: Monitor
, Getter
, Evaluator
, Collector
, Submitter
and Wallet
. These services work together to get the GeneralisedIncentives message bounties, evaluate their value, collect the message proofs, and submit them on the destination chain. The services are run in parallel and communicate using Redis. Wherever it makes sense, chains are allocated seperate workers to ensure a chain fault doesn't propagate and impact the performance on other chains.
The Monitor service keeps track of the latest block information for each supported chain by polling the RPCs at the configured intervals. Other services may listen for the block changes that are observed by this Monitor service (this is mainly done by the Getter service). Additionally, external services may subscribe to the observed block changes via a websocket connection as exposed on the Monitor controller.
The Getter service is responsible for fetching on-chain bounties and messages. It works by searching for relevant EVM events triggered by the GeneralisedIncentives contract:
BountyPlaced
: Signals that a message has been sent and contains the associated relaying incentives.MessageDelivered
: Signals that a message has been relayed from the source chain to the destination chain (event published on the destination chain).BountyClaimed
: Signals that a message has been relayed from the destination chain to the source chain, and the bounty has been distributed.BountyIncreased
: Signals that the associated relaying incentive has been updated.
The incentive information gathered with these events is sent to the common Redis database for later use by the other services.
The Evaluator service determines the profitability of delivering/acking a message using a provided gas estimation for the transaction in question together with the Bounty information gathered by the Getter. It is used by the Submitter to dettermine whether to relay a message or not, and can be used by external services via exposed API endopints defined on the Evaluator controller.
The Collector service collects the information to relay the cross-chain messages directly from the various AMB's, as for example the AMB's proofs. Every proof collected is sent to the Submitter via Redis to request the relay of the packet.
The Submitter service gets packets that need relaying from Redis. For every packet received, the submitter:
- Gets the associated bounty information (i.e. the relaying incentive) from Redis.
- Simulates the transaction to get a gas estimate.
- Evaluates whether the relaying bounty covers the gas cost of the packet submission by querying the Evaluator service.
- Request the packet submission if the evaluation is successful using the processPacket method of the IncentivizedMessageEscrow contract via the Wallet service.
To make the Submitter as resilitent as possible to RPC failures/connection errors, each evaluation and submission step is tried up to maxTries
times with a retryInterval
delay between tries (these default to 3
and 2000
ms, but can be modified on the Relayer config).
The Submitter additionally limits the maximum number of transactions within the 'submission' pipeline (i.e. transactions that have been started to be processed and are not completed), and will not accept any further relay orders once reached.
The Wallet service is used to submit transactions requested by the other services of the Relayer (only the Submitter at the time of writing). For every transaction request:
-
The transaction fee values are dynamically determined according to the following configuration:
- The
maxFeePerGas
configuration sets the transactionmaxFeePerGas
property. This defines the maximum fee to be paid per gas for a transaction (including both the base fee and the miner fee). If not set, nomaxFeePerGas
is set on the transaction. - The
maxPriorityFeeAdjustmentFactor
determines the amount by which to modify the queried recommendedmaxPriorityFee
from the rpc. If not set, nomaxPriorityFee
is set on the transaction. - The
maxAllowedPriorityFeePerGas
sets the maximum value thatmaxPriorityFee
may be set to (after applying themaxPriorityFeeAdjustmentFactor
).
- The
gasPriceAdjustmentFactor
determines the amount by which to modify the queried recommendedgasPrice
from the rpc. If not set, nogasPrice
is set on the transaction. - The
maxAllowedGasPrice
sets the maximum value thatgasPrice
may be set to (after applying thegasPriceAdjustmentFactor
).
⚠️ If the above gas configurations are not specified, the transactions will be submitted using theethers
/rpc defaults. - The
-
The transaction is submitted.
-
The transaction confirmation is awaited.
-
If the transaction fails to be mined after a configurable time interval, the transaction is repriced.
- If a transaction does not mine in time (
maxTries * (confirmationTimeout + retryInterval)
approximately), the Relayer will attempt to reprice the transaction by resubmitting the transaction with higher gas price values. The gas prices are adjusted according to thepriorityAdjustmentFactor
configuration. If not set, it defaults to1.1
(i.e +10%).
- If a transaction does not mine in time (
-
If the transaction still fails to be mined, the wallet will attempt at cancelling the transaction.
⚠️ If the Wallet fails to cancel a transaction, the Submitter pipeline will stall and no further orders will be processed until the stuck transaction is resolved.
A Pricing Service is implemented to be able to determine the value of gas/tokens handled by the Relayer. This service is mainly used by the Evaluator service to determine the profitability of relays, but can also be queried by external services using the exposed API endpoints specified on the Pricing controller.
The Relayer includes 2 pricing providers:
fixed
: Can be used to specify a 'fixed' value for each token.coin-gecko
: Provides real-time pricing information for each token. Using this provider requires the additionalgasCoinId
configuration to be specified (see the providedconfig.example.yaml
file).
The base Pricing controller provided includes price caching at a configurable cacheDuration
interval (set to 0 to disable caching).
ℹ️ Further custom pricing providers can be implemented following the example of the existing providers.
To take into consideration the different behaviours and characteristics of different chains, a custom Resolvers can be specified for each chain. At the time of writing, the Resolvers can:
- Map the rpc block number to the one observed by the transactions itself (for chains like Arbitrum).
- Estimate gas parameters for transactions, including estimating the gas usage as observed by the transactions (for chains like Arbitrum) and additional L1 fees (for op-stack chains).
ℹ️ Resolvers have to be specified on the configuration file for each desired chain. See
src/resolvers
for the available resolvers.
The Relayer keeps an estimate of the Relayer account gas balance for each chain. A warning is emitted to the logs if the gas balance falls below a configurable threshold lowBalanceWarning
(in Wei).
The distinct services of the Relayer communicate with each other using a Redis database. To abstract the Redis implementation away, a helper library, store.lib.ts
, is provided.
The Relayer makes available a getAMBs
endpoint with which an external service may query the AMB messages corresponding to a transaction hash.
The Persister service can be enabled to save the information gathered by the Relayer to a persistent PostgreSQL database.
In order to add support for a new AMB, a new service folder under collector/
named after the AMB must be added, within which the service file, also named after the AMB, must define the service that collects the AMB proofs. The collector service must send the collected AMB data to Redis using the submitProof
helper of the store/store.lib.ts
library.
⚠️ The Collector service must not block the main event loop in any manner (e.g. make use of worker threads). See the Mock collector implementation for further reference.
The AMB configuration must be added to the .yaml
configuration file under the AMB name. This configuration will be automatically passed to the service once it is instantiated by the main Collector controller (see here).
It is recommended to update the docker-compose.yaml
file with any image/service that is required by the AMB to have a completely standalone implementation when running the Relayer with Docker.
ℹ️ It is also recommended to add a new Docker Compose profile for any image/service added to the
docker-compose.yaml
file so that it can be disabled at the user's will.
ℹ️ Update
config.example.yaml
with the new AMB details for future reference.
The mock implementation is proof-of-authentication (PoA) scheme which works well for developing and testing. To use it, deploy a Mock Generalised Incentive implementation using a known key. Then set the key in the Mock AMB config and run the Relayer.
The Relayer uses ethers
types for the contracts that it interacts with (e.g. the Generalised Incentives contract). These types are generated with the typechain
package using the contract abis (under the abis/
folder) upon installation of the npm
packages. If the contract abis change the types must be regenerated (see the postinstall
script on package.json
).