16 KiB
Snowbridge Execution Relay
Overview
The Execution Relay processes Ethereum execution layer events and delivers cross-chain messages to DataHaven. It monitors the Gateway contract on Ethereum and relays messages to the corresponding pallets on DataHaven.
Purpose
- Relay Ethereum execution layer messages to DataHaven
- Monitor Gateway contract for outbound messages
- Submit message proofs to DataHaven for verification
- Enable cross-chain token transfers and message passing from Ethereum
Direction
Ethereum Execution Layer → DataHaven
Prerequisites
- Docker with
linux/amd64platform support - Access to Ethereum execution layer WebSocket endpoint
- Access to Ethereum consensus layer (beacon) HTTP endpoint
- Access to DataHaven node WebSocket endpoint
- Substrate account with balance for transaction fees
- Deployed Gateway contract on Ethereum
- Persistent storage for relay datastore
Hardware Requirements
Specifications
| Component | Requirement |
|---|---|
| CPU | 4 cores |
| RAM | 8 GB |
| Storage (Datastore) | 10 GB SSD |
| Network | 100 Mbit/s symmetric |
Important Considerations
- Persistent storage: The relay maintains a local datastore to track processed messages; use persistent volumes in containerized deployments
- Message throughput: Storage requirements may increase with high message volumes
- Network connectivity: Requires connections to both Ethereum (execution + beacon) and DataHaven nodes
- Reliable RPC endpoints: Use enterprise-grade or self-hosted nodes for production deployments
RPC Endpoint Requirements
Ethereum Execution Layer
The relay requires access to a stable, reliable Ethereum WebSocket endpoint. Endpoint instability or downtime will prevent the relay from functioning correctly.
Recommended providers:
- Self-hosted execution node (Geth, Nethermind, Besu, Erigon)
- Dwellir
- Chainstack
- QuickNode
- Alchemy
Requirements:
- WebSocket support (WSS for production)
- Full event log access for Gateway contract monitoring
- Low latency (< 100ms recommended)
- High availability (99.9%+ uptime)
Beacon Node API
The relay also requires access to the Ethereum Beacon API for constructing message proofs.
Recommended providers:
- Self-hosted beacon node (Lighthouse, Prysm, Teku, Nimbus, Lodestar)
- Same providers as execution layer (with beacon API support)
Requirements:
- Full beacon API support (
/eth/v1/beacon/*endpoints) - State endpoint access for proof construction
- Low latency (< 100ms recommended)
Relay Redundancy
Why Redundancy Matters
Running multiple relay instances provides fault tolerance and ensures continuous bridge operation even if one relay fails. The on-chain pallets have built-in deduplication, so only the first valid submission is accepted—redundant relays simply provide backup coverage.
Configuring Redundant Relays
Deploy multiple relay instances pointing to different RPC providers for maximum fault tolerance:
Instance 1 (Primary):
{
"source": {
"ethereum": {
"endpoint": "wss://eth-provider-a.example.com"
},
"beacon": {
"endpoint": "https://beacon-provider-a.example.com"
}
},
"sink": {
"parachain": {
"endpoint": "wss://datahaven-rpc-1.example.com"
}
}
}
Instance 2 (Backup):
{
"source": {
"ethereum": {
"endpoint": "wss://eth-provider-b.example.com"
},
"beacon": {
"endpoint": "https://beacon-provider-b.example.com"
}
},
"sink": {
"parachain": {
"endpoint": "wss://datahaven-rpc-2.example.com"
}
}
}
Best Practices for Redundancy
- Use different RPC providers: Avoid single points of failure by using different Ethereum node providers for each relay instance
- Geographic distribution: Deploy relays in different regions/data centers
- Independent infrastructure: Run relays on separate machines or Kubernetes nodes
- Separate funding accounts: Use different relay accounts to avoid nonce conflicts
- Monitor all instances: Set up alerting for each relay independently
Key Requirements
Substrate Private Key
The Execution Relay requires a Substrate private key to sign and submit extrinsics to DataHaven.
| Key Type | Purpose |
|---|---|
| Substrate (sr25519/ecdsa) | Sign message delivery extrinsics on DataHaven |
Account Funding
The relay account must be funded with HAVE tokens to pay for transaction fees.
Recommended Balance: 100+ HAVE for continuous operations
For detailed operating cost estimates and optimization strategies, see the Relay Operating Costs guide.
CLI Flags
Required Flags
| Flag | Description |
|---|---|
--config <PATH> |
Path to the JSON configuration file |
Private Key Flags (One Required)
| Flag | Description |
|---|---|
--substrate.private-key <KEY> |
Substrate private key URI directly |
--substrate.private-key-file <PATH> |
Path to file containing the private key |
--substrate.private-key-id <ID> |
AWS Secrets Manager secret ID for the private key |
Configuration File
Structure
{
"source": {
"ethereum": {
"endpoint": "ws://ethereum-node:8546"
},
"contracts": {
"Gateway": "0x8f86403A4DE0BB5791fa46B8e795C547942fE4Cf"
},
"beacon": {
"endpoint": "http://beacon-node:4000",
"stateEndpoint": "http://beacon-node:4000",
"spec": {
"syncCommitteeSize": 512,
"slotsInEpoch": 32,
"epochsPerSyncCommitteePeriod": 256,
"forkVersions": {
"deneb": 0,
"electra": 0
}
},
"datastore": {
"location": "/relay-data",
"maxEntries": 100
}
}
},
"sink": {
"parachain": {
"endpoint": "ws://datahaven-node:9944",
"maxWatchedExtrinsics": 8,
"headerRedundancy": 20
}
},
"instantVerification": false,
"schedule": {
"id": null,
"totalRelayerCount": 1,
"sleepInterval": 1
}
}
Configuration Parameters
Source (Ethereum)
| Parameter | Description | Example |
|---|---|---|
source.ethereum.endpoint |
Ethereum execution layer WebSocket | ws://ethereum-node:8546 |
source.contracts.Gateway |
Gateway contract address | 0x... |
Source (Beacon Chain)
| Parameter | Description | Example |
|---|---|---|
source.beacon.endpoint |
Beacon chain HTTP API endpoint | http://beacon-node:4000 |
source.beacon.stateEndpoint |
Beacon chain state endpoint | http://beacon-node:4000 |
source.beacon.spec.* |
Beacon chain specification | See beacon spec parameters |
source.beacon.datastore.location |
Path to persistent datastore | /relay-data |
source.beacon.datastore.maxEntries |
Maximum datastore entries | 100 |
Sink (DataHaven)
| Parameter | Description | Example |
|---|---|---|
sink.parachain.endpoint |
DataHaven WebSocket endpoint | ws://datahaven-node:9944 |
sink.parachain.maxWatchedExtrinsics |
Max concurrent watched extrinsics | 8 |
sink.parachain.headerRedundancy |
Header redundancy factor | 20 |
Relay Settings
| Parameter | Description | Example |
|---|---|---|
instantVerification |
Enable instant verification mode | false |
schedule.id |
Relayer instance ID (for multi-instance) | null or 0 |
schedule.totalRelayerCount |
Total number of relayer instances | 1 |
schedule.sleepInterval |
Seconds between message checks | 1 |
Multi-Instance Deployment
For high-availability or load distribution, multiple Execution Relayers can be deployed using the schedule configuration to coordinate between instances.
Schedule Configuration Parameters
| Parameter | Type | Description |
|---|---|---|
schedule.id |
number or null |
Unique identifier for this relay instance (0-indexed). Set to null for single-instance deployments. |
schedule.totalRelayerCount |
number |
Total number of relay instances in the deployment. All instances must use the same value. |
schedule.sleepInterval |
number |
Seconds to wait between polling for new messages. Lower values = faster detection, higher resource usage. |
How Multi-Instance Scheduling Works
When multiple relayers are deployed, the schedule.id and totalRelayerCount parameters work together to distribute message processing:
- Message assignment: Messages are assigned to relayers based on
message_nonce % totalRelayerCount == schedule.id - Staggered processing: Each relayer only processes messages assigned to its ID, preventing duplicate submissions
- Failover: If a relayer fails, its messages will eventually be picked up by other relayers after timeout
Example with 3 relayers:
- Instance 0 processes messages where
nonce % 3 == 0(nonces: 0, 3, 6, 9, ...) - Instance 1 processes messages where
nonce % 3 == 1(nonces: 1, 4, 7, 10, ...) - Instance 2 processes messages where
nonce % 3 == 2(nonces: 2, 5, 8, 11, ...)
Configuration Examples
Single Instance (default):
{
"schedule": {
"id": null,
"totalRelayerCount": 1,
"sleepInterval": 1
}
}
Three-Instance Deployment:
Instance 0:
{
"schedule": {
"id": 0,
"totalRelayerCount": 3,
"sleepInterval": 1
}
}
Instance 1:
{
"schedule": {
"id": 1,
"totalRelayerCount": 3,
"sleepInterval": 1
}
}
Instance 2:
{
"schedule": {
"id": 2,
"totalRelayerCount": 3,
"sleepInterval": 1
}
}
Sleep Interval Tuning
The sleepInterval parameter controls how frequently the relay polls for new messages:
| Value | Use Case | Trade-offs |
|---|---|---|
1 |
Low latency required | Higher RPC usage, faster message detection |
5 |
Balanced | Good balance of latency and resource usage |
10 |
Cost-sensitive | Lower RPC costs, slower message detection |
30 |
Minimal activity | Very low resource usage, higher latency |
Recommendation: Start with sleepInterval: 1 for production deployments where message latency is important. Increase if RPC rate limits become an issue.
Deployment Checklist
- Unique IDs: Each instance must have a unique
schedule.id(0 tototalRelayerCount - 1) - Consistent count: All instances must use the same
totalRelayerCountvalue - Separate accounts: Use different Substrate accounts to avoid nonce conflicts
- Independent storage: Each instance needs its own persistent datastore volume
- Different RPC endpoints: Point instances to different RPC providers for fault tolerance
Running the Relay
Docker Run
docker run -d \
--name snowbridge-execution-relay \
--platform linux/amd64 \
--add-host host.docker.internal:host-gateway \
--network datahaven-network \
-v $(pwd)/execution-relay.json:/configs/execution-relay.json:ro \
-v $(pwd)/relay-data:/relay-data \
--pull always \
datahavenxyz/snowbridge-relay:latest \
run execution \
--config /configs/execution-relay.json \
--substrate.private-key "0x..."
Docker Compose
version: '3.8'
services:
execution-relay:
image: datahavenxyz/snowbridge-relay:latest
container_name: snowbridge-execution-relay
platform: linux/amd64
restart: unless-stopped
volumes:
- ./configs/execution-relay.json:/configs/execution-relay.json:ro
- execution-relay-data:/relay-data
command:
- "run"
- "execution"
- "--config"
- "/configs/execution-relay.json"
- "--substrate.private-key-file"
- "/secrets/substrate-key"
secrets:
- substrate-key
volumes:
execution-relay-data:
secrets:
substrate-key:
file: ./secrets/execution-relay-substrate-key
Kubernetes Deployment
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: dh-execution-relay
spec:
serviceName: dh-execution-relay
replicas: 1
selector:
matchLabels:
app: dh-execution-relay
template:
metadata:
labels:
app: dh-execution-relay
spec:
containers:
- name: execution-relay
image: datahavenxyz/snowbridge-relay:latest
imagePullPolicy: Always
args:
- "run"
- "execution"
- "--config"
- "/configs/execution-relay.json"
- "--substrate.private-key-file"
- "/secrets/dh-execution-relay-substrate-key"
volumeMounts:
- name: config
mountPath: /configs
readOnly: true
- name: secrets
mountPath: /secrets
readOnly: true
- name: relay-data
mountPath: /relay-data
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
volumes:
- name: config
configMap:
name: execution-relay-config
- name: secrets
secret:
secretName: dh-execution-relay-substrate-key
volumeClaimTemplates:
- metadata:
name: relay-data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 10Gi
Message Flow
Ethereum → DataHaven Message Flow
- User calls Gateway contract on Ethereum
- Gateway emits
OutboundMessageAcceptedevent - Execution Relay monitors for Gateway events
- Relay constructs message proof using beacon chain state
- Relay submits proof to DataHaven via
EthereumInboundQueuepallet - DataHaven verifies proof against beacon client state
- Message is dispatched to target pallet
Supported Message Types
- Token transfers (ERC-20 tokens to DataHaven)
- Arbitrary cross-chain messages
- Smart contract calls
Monitoring
Health Checks
# View relay logs
docker logs -f snowbridge-execution-relay
# Check for message processing
docker logs snowbridge-execution-relay 2>&1 | grep -i "message\|submit\|proof"
Key Metrics to Monitor
- Message queue depth
- Message delivery success rate
- Ethereum event processing lag
- Account balance (for fees)
- Beacon chain sync status
Troubleshooting
Issue: Messages Not Being Delivered
Check:
- Gateway contract address is correct
- Ethereum endpoint is accessible
- Beacon chain is synced
- DataHaven node is accessible
# Check Ethereum connectivity
curl -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
http://ethereum-node:8545
Issue: Proof Verification Failures
Check:
- Beacon Relay is running and synced
EthereumBeaconClientpallet has recent updates- Beacon chain spec matches configuration
Issue: High Latency
Solutions:
- Reduce
sleepIntervalfor faster message detection - Deploy multiple relay instances
- Ensure low-latency connections to endpoints
Security Considerations
- Private Key Protection: Store Substrate private keys securely
- Network Security: Use secure connections (WSS) when possible
- Access Control: Use dedicated accounts with minimal permissions
- Monitoring: Set up alerts for message delivery failures
Economics
Transaction Costs
- Message delivery: ~0.012 DOT equivalent per message (varies with message size)
- Relayers earn incentives for successful deliveries
Incentive Structure
Relayers can claim incentives from the protocol for successful message deliveries. See Snowbridge documentation for details.