Rapid Relayer is a fast, scalable, stateful IBC Relayer optimized for interwoven rollups.
Rapid Relayer does not use the tx_search
query of Hermes to handle packets from several blocks at once. Initia Labs has developed this IBC Relayer to replace Hermes, only using the necessary functions for packet handling.
- Minitia L2s generate blocks extremely quick at 500ms per block.
- Due to the interwoven nature of Initia, often many IBC packets are generated within blocks. Hermes can handle batches of packets but on a single block basis.
- Hermes handles these IBC packets sequentially leading to unprocessed packets accumulating very quickly when having fast blocktimes.
- If Hermes stops, unprocessed packets will continue to pile up.
- When Hermes misses a packet, it finds them using
tx_search
query on every sequence, this can take minutes for just a few hundred packets. - We need something more rapid.
- We removed the
tx_search
query, and handle packets in parallel across several blocks at once. - Keep track of
synced_height
andlatest_height
. - Multi-threaded workers: packet handler and event feeder. The event feeder feeds the packet from new blocks to a cache and the packet handler fetches packets from it. This way, even if the packet handler stops, the event feeder will continue to operate.
- We remove the slow call of
tx_search
.
git clone https://github.com/initia-labs/rapid-relayer.git
npm install
You can configure Rapid Relayer using either a JSON configuration file or environment variables.
The config.json
file is optional, the program can run with only environment variables.
When both are provided, environment variables have higher priority than the JSON configuration.
Create a config.json
file with the following structure:
{
"$schema": "./config.schema.json",
"port": 7010,
"metricPort": 70001,
"logLevel": "info",
"rpcRequestTimeout": 5000,
"chains": [
{
"bech32Prefix": "init",
"chainId": "chain-1",
"gasPrice": "0.15gas",
"restUri": "https://rest.chain-1.com",
"rpcUri": ["https://rpc.chain-1.com", "https://rpc.chain-1.com/fallback"],
"wallets": [
{
"key": {
"type": "raw",
"privateKey": "123..."
},
"maxHandlePacket": 10,
"startHeight": 0 // if empty start from the latest height
},
{
"key": {
"type": "mnemonic",
"privateKey": "repair family apology column ..."
},
"maxHandlePacket": 10,
"packetFilter": {
"connections": [{ "connectionId": "conneciton-1" }]
}
}
],
"feeFilter": {
"recvFee": [{ "denom": "gas", "amount": 100 }],
"timeoutFee": [{ "denom": "gas", "amount": 200 }],
"ackFee": [{ "denom": "gas", "amount": 300 }]
}
},
{
"bech32Prefix": "init",
"chainId": "chain-2",
"gasPrice": "0umin",
"restUri": "https://rest.chain-2.com",
"rpcUri": ["https://rpc.chain-2.com", "https://rpc.chain-2.com/fallback"],
"wallets": [
{
"key": {
"type": "raw",
"privateKey": "123..."
},
"maxHandlePacket": 10
}
]
}
]
}
You can also configure Rapid Relayer using environment variables. The following environment variables are supported:
Top-level configuration:
PORT
: Port number for the API serverMETRIC_PORT
: Port number for the metrics serverLOG_LEVEL
: Log levelRPC_REQUEST_TIMEOUT
: Timeout for RPC requests in millisecondsDB_PATH
: Path to the database directory
Chain configuration: You can configure chains in two ways:
-
Using a single environment variable:
CHAINS
: JSON string containing the chains configuration array
-
Using individual environment variables for each chain:
CHAIN_<index>_CHAIN_ID
: Chain ID for the chain at index<index>
CHAIN_<index>_BECH32_PREFIX
: Bech32 prefix for the chainCHAIN_<index>_GAS_PRICE
: Gas price for the chainCHAIN_<index>_REST_URI
: REST URI for the chainCHAIN_<index>_RPC_URI
: RPC URI for the chain (can be a single URI string or array of URI strings)CHAIN_<index>_FEE_FILTER
: JSON string containing the fee filter configuration
Wallet configuration: You can configure wallets in two ways:
-
Using a single environment variable:
CHAIN_<index>_WALLETS
: JSON string containing the wallets configuration array
-
Using individual environment variables for each wallet:
CHAIN_<index>_WALLET_<wallet_index>_KEY_TYPE
: Key type ("raw", "mnemonic", "env_raw", or "env_mnemonic")CHAIN_<index>_WALLET_<wallet_index>_KEY_PRIVATE_KEY
: Private keyCHAIN_<index>_WALLET_<wallet_index>_KEY_OPTIONS
: JSON string containing key optionsCHAIN_<index>_WALLET_<wallet_index>_MAX_HANDLE_PACKET
: Maximum number of packets to handleCHAIN_<index>_WALLET_<wallet_index>_START_HEIGHT
: Start height for the walletCHAIN_<index>_WALLET_<wallet_index>_PACKET_FILTER
: JSON string containing packet filter configuration
Example:
# Top-level configuration
export PORT=7010
export METRIC_PORT=70001
export LOG_LEVEL=info
# Chain 1 configuration
export CHAIN_0_CHAIN_ID=chain-1
export CHAIN_0_BECH32_PREFIX=init
export CHAIN_0_GAS_PRICE=0.15gas
export CHAIN_0_REST_URI=https://rest.chain-1.com
export CHAIN_0_RPC_URI=https://rpc.chain-1.com # Single URI string
# Chain 1 wallet 1 configuration
export CHAIN_0_WALLET_0_KEY_TYPE=raw
export CHAIN_0_WALLET_0_KEY_PRIVATE_KEY=123...
export CHAIN_0_WALLET_0_MAX_HANDLE_PACKET=10
export CHAIN_0_WALLET_0_START_HEIGHT=0
# Chain 1 wallet 2 configuration
export CHAIN_0_WALLET_1_KEY_TYPE=mnemonic
export CHAIN_0_WALLET_1_KEY_PRIVATE_KEY="repair family apology column ..."
export CHAIN_0_WALLET_1_MAX_HANDLE_PACKET=10
export CHAIN_0_WALLET_1_PACKET_FILTER='{"connections":[{"connectionId":"conneciton-1"}]}'
# Chain 1 fee filter
export CHAIN_0_FEE_FILTER='{"recvFee":[{"denom":"gas","amount":100}],"timeoutFee":[{"denom":"gas","amount":200}],"ackFee":[{"denom":"gas","amount":300}]}'
# Chain 2 configuration
export CHAIN_1_CHAIN_ID=chain-2
export CHAIN_1_BECH32_PREFIX=init
export CHAIN_1_GAS_PRICE=0umin
export CHAIN_1_REST_URI=https://rest.chain-2.com
export CHAIN_1_RPC_URI='["https://rpc.chain-2.com", "https://rpc-fallback.chain-2.com"]' # JSON array of URI strings for fallback
# Chain 2 wallet 1 configuration
export CHAIN_1_WALLET_0_KEY_TYPE=raw
export CHAIN_1_WALLET_0_KEY_PRIVATE_KEY=123...
export CHAIN_1_WALLET_0_MAX_HANDLE_PACKET=10
Note: Environment variables have higher priority than the JSON configuration. If both are provided, the environment variables will override the corresponding values in the JSON configuration.
### 2. Run relayer
```bash
npm start
docker build -t your-tag .
Mount a volume called '/config' which contains your config.json and a /syncInfo volume which will contain the state:
docker run -it -v/tmp/rr/config:/config -v/tmp/rr/syncInfo:/syncInfo -d rapid-relayer:latest
This will start the relayer in a docker container using your config, and placing the state in a separate volume.
You can also use environment variables with Docker:
docker run -it \
-e PORT=7010 \
-e METRIC_PORT=70001 \
-e LOG_LEVEL=info \
-e CHAIN_0_CHAIN_ID=chain-1 \
-e CHAIN_0_BECH32_PREFIX=init \
-e CHAIN_0_GAS_PRICE=0.15gas \
-e CHAIN_0_REST_URI=https://rest.chain-1.com \
-e CHAIN_0_RPC_URI=https://rpc.chain-1.com \
-e CHAIN_0_WALLET_0_KEY_TYPE=raw \
-e CHAIN_0_WALLET_0_KEY_PRIVATE_KEY=123... \
-v/tmp/rr/syncInfo:/syncInfo \
-d rapid-relayer:latest
You can also combine both approaches, with environment variables having the higher priority.
Rapid Relayer supports running in cluster mode using the RAFT consensus algorithm for high availability and leader election. This allows multiple relayer nodes to coordinate, ensuring only one leader node executes transactions, while all nodes stay in sync.
- Cluster mode uses RAFT for automatic leader election and failover.
- Only the leader node executes transactions; followers stay in sync and can take over if the leader fails.
- Supports both single-node and multi-node clusters.
Add a raft
section to your config (example for single-node and multi-node):
"raft": {
"nodeId": "node1",
"host": "127.0.0.1",
"port": 4001,
"peers": []
}
Each node must have a unique id
, its own host
/port
, and list all other nodes in peers
:
"raft": {
"nodeId": "node1",
"host": "10.0.0.1",
"port": 4001,
"peers": [
{ "id": "node2", "host": "10.0.0.2", "port": 4002 },
{ "id": "node3", "host": "10.0.0.3", "port": 4003 }
]
}
- Repeat for each node, changing
id
,host
, andport
accordingly.
- Start each node with its own config (with correct
raft
section). - Nodes will automatically discover each other, elect a leader, and synchronize.
- You can run a single node for development, or multiple nodes for production/high-availability.
npm start
# On node1
npm start -- --config config-node1.json
# On node2
npm start -- --config config-node2.json
# On node3
npm start -- --config config-node3.json
- Single node: The node will elect itself as leader and process transactions normally.
- Multi-node:
- Start all nodes. Check logs for messages like
became LEADER
andheartbeat
. - Stop the leader node; another node should be elected as leader automatically.
- All nodes should log their state and election/heartbeat events.
- Start all nodes. Check logs for messages like
- You can check cluster status and leadership in the logs.