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:
Currently, the Relayer supports the following AMBs:
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:
ℹ️ There is no need to manually install/run any dependencies when running the Relayer with Docker.
Relayer Configuration
The Relayer configuration is split into 2 distinct files.
⚠️ The Relayer will not run without the following configuration files.
.yaml
fileMost 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.
privateKey
of the account that will submit the relay transactions on all chains must be defined at this point. monitor
, getter
, pricing
, evaluator
, submitter
and wallet
can also be specified at this point.ambs
: The AMBs configuration.
incentivesAddress
) at this point, or otherwise within the chain-specific AMB configuration (under chains -> $ambName -> incentivesAddress
).chains
: Defines the configuration for each of the chains to be supported by the relayer.
chainId
and the rpc
to be used for the chain.monitor
, getter
, pricing
, evaluator
, submitter
and wallet
configurations (those defined under the global
configuration), and amb
configurations.ℹ️ For a full reference of the configuration file, see
config.example.yaml
.
.env
filePorts 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
devDependencies
are required to build the project. If running on a production machine where NODE_ENV=production
, use pnpm install --prod=false
Initiate a Redis database with:
docker container run -p 6379:6379 redis
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:
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:
maxFeePerGas
configuration sets the transaction maxFeePerGas
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, no maxFeePerGas
is set on the transaction.maxPriorityFeeAdjustmentFactor
determines the amount by which to modify the queried recommended maxPriorityFee
from the rpc. If not set, no maxPriorityFee
is set on the transaction.maxAllowedPriorityFeePerGas
sets the maximum value that maxPriorityFee
may be set to (after applying the maxPriorityFeeAdjustmentFactor
).gasPriceAdjustmentFactor
determines the amount by which to modify the queried recommended gasPrice
from the rpc. If not set, no gasPrice
is set on the transaction.maxAllowedGasPrice
sets the maximum value that gasPrice
may be set to (after applying the gasPriceAdjustmentFactor
).⚠️ If the above gas configurations are not specified, the transactions will be submitted using the
ethers
/rpc defaults.
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 the priorityAdjustmentFactor
configuration. If not set, it defaults to 1.1
(i.e +10%).⚠️ 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 additional gasCoinId
configuration to be specified (see the provided config.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:
ℹ️ 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).
Store
libraryThe 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
).