diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index d76929a9..e3bc4d3f 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,3 +1,3 @@
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-syntax
-* @michaelkaplan13 @cam-schultz @minghinmatthewlam @gwen917 @geoff-vball @bernard-avalabs
+* @ava-labs/interop
\ No newline at end of file
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index 1b89a884..1a17199a 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -21,17 +21,16 @@ jobs:
uses: actions/checkout@v4
with:
submodules: recursive
-
- - name: Set Go version
- run: |
- source ./scripts/versions.sh
- echo GO_VERSION=$GO_VERSION >> $GITHUB_ENV
- echo SUBNET_EVM_VERSION=$SUBNET_EVM_VERSION >> $GITHUB_ENV
- name: Setup Go
uses: actions/setup-go@v5
with:
- go-version: ${{ env.GO_VERSION }}
+ go-version-file: 'go.mod'
+
+ - name: Set subnet-evm version
+ run: |
+ source ./scripts/versions.sh
+ echo SUBNET_EVM_VERSION=$SUBNET_EVM_VERSION >> $GITHUB_ENV
- name: Checkout subnet-evm repository
uses: actions/checkout@v4
diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
index 9c99f558..089aa183 100644
--- a/.github/workflows/linter.yml
+++ b/.github/workflows/linter.yml
@@ -22,15 +22,10 @@ jobs:
with:
submodules: recursive
- - name: Set Go version
- run: |
- source ./scripts/versions.sh
- echo GO_VERSION=$GO_VERSION >> $GITHUB_ENV
-
- name: Setup Go
uses: actions/setup-go@v5
with:
- go-version: ${{ env.GO_VERSION }}
+ go-version-file: 'go.mod'
- name: Install buf
uses: bufbuild/buf-setup-action@v1.31.0
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 6272fe92..97c08a1e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -15,19 +15,18 @@ jobs:
- name: Git checkout
uses: actions/checkout@v4
with:
- fetch-depth: 0
- path: awm-relayer
submodules: recursive
+ # The GO_VERSION must be set explicitly to be used in the Dockerfile.
- name: Set Go version
run: |
- source ./awm-relayer/scripts/versions.sh
+ source ./scripts/versions.sh
echo GO_VERSION=$GO_VERSION >> $GITHUB_ENV
- name: Set up Go
uses: actions/setup-go@v5
with:
- go-version: ${{ env.GO_VERSION }}
+ go-version-file: 'go.mod'
- name: Set up arm64 cross compiler
run: |
@@ -70,8 +69,6 @@ jobs:
distribution: goreleaser
version: latest
args: release --clean
- workdir: ./awm-relayer/
env:
# https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- GO_VERSION: ${{ env.GO_VERSION }}
diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml
index a2a8284f..8934ccc2 100644
--- a/.github/workflows/snyk.yml
+++ b/.github/workflows/snyk.yml
@@ -17,7 +17,6 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
with:
- path: awm-relayer
submodules: recursive
- name: Run Snyk
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 46b7ead2..a7de23a1 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -21,16 +21,11 @@ jobs:
uses: actions/checkout@v4
with:
submodules: recursive
-
- - name: Set Go version
- run: |
- source ./scripts/versions.sh
- echo GO_VERSION=$GO_VERSION >> $GITHUB_ENV
- name: Setup Go
uses: actions/setup-go@v5
with:
- go-version: ${{ env.GO_VERSION }}
+ go-version-file: 'go.mod'
- name: Run Relayer Unit Tests
run: ./scripts/test.sh
diff --git a/.gitignore b/.gitignore
index 40211110..f820db91 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,6 @@ server.log
# Foundry outputs
cache/
out/
+
+# Release build outputs
+osxcross/
\ No newline at end of file
diff --git a/.golangci.yml b/.golangci.yml
index 3a7ec484..3a55826f 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -23,6 +23,7 @@ linters:
- unconvert
- unused
- whitespace
+ - lll
linters-settings:
gofmt:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index eeed2395..37357164 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,7 +2,7 @@
## Setup
-To start developing on AWM Relayer, you'll need Golang >= v1.21.7.
+To start developing on AWM Relayer, you'll need Golang v1.21.12.
## Issues
diff --git a/README.md b/README.md
index 5d04bdc3..e3f9e33b 100644
--- a/README.md
+++ b/README.md
@@ -199,11 +199,11 @@ The relayer is configured via a JSON file, the path to which is passed in via th
`"source-blockchain-id": string`
- - cb58-encoded blockchain ID of the source blockchain.
+ - cb58-encoded or "0x" prefixed hex-encoded blockchain ID of the source blockchain.
`"destination-blockchain-id": string`
- - cb58-encoded blockchain ID of the destination blockchain.
+ - cb58-encoded or "0x" prefixed hex-encoded blockchain ID of the destination blockchain.
`"source-address": string`
@@ -219,11 +219,11 @@ The relayer is configured via a JSON file, the path to which is passed in via th
`"subnet-id": string`
- - cb58-encoded Subnet ID.
+ - cb58-encoded or "0x" prefixed hex-encoded Subnet ID.
`"blockchain-id": string`
- - cb58-encoded blockchain ID.
+ - cb58-encoded or "0x" prefixed hex-encoded blockchain ID.
`"vm": string`
@@ -263,11 +263,11 @@ The relayer is configured via a JSON file, the path to which is passed in via th
`"subnet-id": string`
- - cb58-encoded Subnet ID.
+ - cb58-encoded or "0x" prefixed hex-encoded Subnet ID.
`"blockchain-id": string`
- - cb58-encoded blockchain ID.
+ - cb58-encoded or "0x" prefixed hex-encoded blockchain ID.
`"vm": string`
@@ -316,6 +316,54 @@ The relayer consists of the following components:
+### API
+
+#### `/relay`
+- Used to manually relay a Warp message. The body of the request must contain the following JSON:
+```json
+{
+ "blockchain-id": "",
+ "message-id": "",
+ "block-num": ""
+}
+```
+- If successful, the endpoint will return the following JSON:
+```json
+{
+ "transaction-hash": ""
+}
+```
+
+#### `/relay/message`
+- Used to manually relay a warp message. The body of the request must contain the following JSON:
+```json
+{
+ "unsigned-message-bytes": "",
+ "source-address": ""
+}
+```
+- If successful, the endpoint will return the following JSON:
+```json
+{
+ "transaction-hash": "",
+}
+```
+
+#### `/health`
+- Takes no arguments. Returns a `200` status code if all Application Relayers are healthy. Returns a `503` status if any of the Application Relayers have experienced an unrecoverable error. Here is an example return body:
+```json
+{
+ "status": "down",
+ "details": {
+ "relayers-all": {
+ "status": "down",
+ "timestamp": "2024-06-01T05:06:07.685522Z",
+ "error": ""
+ }
+ }
+}
+```
+
## Testing
### Unit Tests
diff --git a/api/health_check.go b/api/health_check.go
new file mode 100644
index 00000000..1fafe3d8
--- /dev/null
+++ b/api/health_check.go
@@ -0,0 +1,42 @@
+package api
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/alexliesenfeld/health"
+ "github.com/ava-labs/avalanchego/ids"
+ "github.com/ava-labs/avalanchego/utils/logging"
+ "go.uber.org/atomic"
+ "go.uber.org/zap"
+)
+
+const HealthAPIPath = "/health"
+
+func HandleHealthCheck(logger logging.Logger, relayerHealth map[ids.ID]*atomic.Bool) {
+ http.Handle(HealthAPIPath, healthCheckHandler(logger, relayerHealth))
+}
+
+func healthCheckHandler(logger logging.Logger, relayerHealth map[ids.ID]*atomic.Bool) http.Handler {
+ return health.NewHandler(health.NewChecker(
+ health.WithCheck(health.Check{
+ Name: "relayers-all",
+ Check: func(context.Context) error {
+ // Store the IDs as the cb58 encoding
+ var unhealthyRelayers []string
+ for id, health := range relayerHealth {
+ if !health.Load() {
+ unhealthyRelayers = append(unhealthyRelayers, id.String())
+ }
+ }
+
+ if len(unhealthyRelayers) > 0 {
+ logger.Fatal("Relayers are unhealthy for blockchains", zap.Strings("blockchains", unhealthyRelayers))
+ return fmt.Errorf("relayers are unhealthy for blockchains %v", unhealthyRelayers)
+ }
+ return nil
+ },
+ }),
+ ))
+}
diff --git a/api/relay_message.go b/api/relay_message.go
new file mode 100644
index 00000000..8b0d479a
--- /dev/null
+++ b/api/relay_message.go
@@ -0,0 +1,142 @@
+package api
+
+import (
+ "encoding/json"
+ "math/big"
+ "net/http"
+
+ "github.com/ava-labs/avalanchego/utils/logging"
+ "github.com/ava-labs/awm-relayer/relayer"
+ "github.com/ava-labs/awm-relayer/types"
+ "github.com/ava-labs/awm-relayer/utils"
+ "github.com/ethereum/go-ethereum/common"
+ "go.uber.org/zap"
+)
+
+const (
+ RelayAPIPath = "/relay"
+ RelayMessageAPIPath = RelayAPIPath + "/message"
+)
+
+type RelayMessageRequest struct {
+ // Required. cb58-encoded or "0x" prefixed hex-encoded source blockchain ID for the message
+ BlockchainID string `json:"blockchain-id"`
+ // Required. cb58-encoded or "0x" prefixed hex-encoded warp message ID
+ MessageID string `json:"message-id"`
+ // Required. Block number that the message was sent in
+ BlockNum uint64 `json:"block-num"`
+}
+
+type RelayMessageResponse struct {
+ // hex encoding of the transaction hash containing the processed message
+ TransactionHash string `json:"transaction-hash"`
+}
+
+// Defines a manual warp message to be sent from the relayer through the API.
+type ManualWarpMessageRequest struct {
+ UnsignedMessageBytes []byte `json:"unsigned-message-bytes"`
+ SourceAddress string `json:"source-address"`
+}
+
+func HandleRelayMessage(logger logging.Logger, messageCoordinator *relayer.MessageCoordinator) {
+ http.Handle(RelayAPIPath, relayAPIHandler(logger, messageCoordinator))
+}
+
+func HandleRelay(logger logging.Logger, messageCoordinator *relayer.MessageCoordinator) {
+ http.Handle(RelayMessageAPIPath, relayMessageAPIHandler(logger, messageCoordinator))
+}
+
+func relayMessageAPIHandler(logger logging.Logger, messageCoordinator *relayer.MessageCoordinator) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ var req ManualWarpMessageRequest
+ err := json.NewDecoder(r.Body).Decode(&req)
+ if err != nil {
+ logger.Warn("Could not decode request body")
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ unsignedMessage, err := types.UnpackWarpMessage(req.UnsignedMessageBytes)
+ if err != nil {
+ logger.Warn("Error unpacking warp message", zap.Error(err))
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ warpMessageInfo := &types.WarpMessageInfo{
+ SourceAddress: common.HexToAddress(req.SourceAddress),
+ UnsignedMessage: unsignedMessage,
+ }
+
+ txHash, err := messageCoordinator.ProcessWarpMessage(warpMessageInfo)
+ if err != nil {
+ logger.Error("Error processing message", zap.Error(err))
+ http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ resp, err := json.Marshal(
+ RelayMessageResponse{
+ TransactionHash: txHash.Hex(),
+ },
+ )
+ if err != nil {
+ logger.Error("Error marshaling response", zap.Error(err))
+ http.Error(w, "error marshaling response: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ _, err = w.Write(resp)
+ if err != nil {
+ logger.Error("Error writing response", zap.Error(err))
+ }
+ })
+}
+
+func relayAPIHandler(logger logging.Logger, messageCoordinator *relayer.MessageCoordinator) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ var req RelayMessageRequest
+ err := json.NewDecoder(r.Body).Decode(&req)
+ if err != nil {
+ logger.Warn("Could not decode request body")
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ blockchainID, err := utils.HexOrCB58ToID(req.BlockchainID)
+ if err != nil {
+ logger.Warn("Invalid blockchainID", zap.String("blockchainID", req.BlockchainID))
+ http.Error(w, "invalid blockchainID: "+err.Error(), http.StatusBadRequest)
+ return
+ }
+ messageID, err := utils.HexOrCB58ToID(req.MessageID)
+ if err != nil {
+ logger.Warn("Invalid messageID", zap.String("messageID", req.MessageID))
+ http.Error(w, "invalid messageID: "+err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ txHash, err := messageCoordinator.ProcessMessageID(blockchainID, messageID, new(big.Int).SetUint64(req.BlockNum))
+ if err != nil {
+ logger.Error("Error processing message", zap.Error(err))
+ http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ resp, err := json.Marshal(
+ RelayMessageResponse{
+ TransactionHash: txHash.Hex(),
+ },
+ )
+ if err != nil {
+ logger.Error("Error marshalling response", zap.Error(err))
+ http.Error(w, "error marshalling response: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ _, err = w.Write(resp)
+ if err != nil {
+ logger.Error("Error writing response", zap.Error(err))
+ }
+ })
+}
diff --git a/config/config.go b/config/config.go
index c9e65108..6aceaca8 100644
--- a/config/config.go
+++ b/config/config.go
@@ -52,13 +52,12 @@ type Config struct {
RedisURL string `mapstructure:"redis-url" json:"redis-url"`
APIPort uint16 `mapstructure:"api-port" json:"api-port"`
MetricsPort uint16 `mapstructure:"metrics-port" json:"metrics-port"`
- DBWriteIntervalSeconds uint64 `mapstructure:"db-write-interval-seconds" json:"db-write-interval-seconds"`
+ DBWriteIntervalSeconds uint64 `mapstructure:"db-write-interval-seconds" json:"db-write-interval-seconds"` //nolint:lll
PChainAPI *APIConfig `mapstructure:"p-chain-api" json:"p-chain-api"`
InfoAPI *APIConfig `mapstructure:"info-api" json:"info-api"`
SourceBlockchains []*SourceBlockchain `mapstructure:"source-blockchains" json:"source-blockchains"`
DestinationBlockchains []*DestinationBlockchain `mapstructure:"destination-blockchains" json:"destination-blockchains"`
ProcessMissedBlocks bool `mapstructure:"process-missed-blocks" json:"process-missed-blocks"`
- ManualWarpMessages []*ManualWarpMessage `mapstructure:"manual-warp-messages" json:"manual-warp-messages"`
DeciderHost string `mapstructure:"decider-host" json:"decider-host"`
DeciderPort *uint16 `mapstructure:"decider-port" json:"decider-port"`
@@ -73,13 +72,13 @@ func DisplayUsageText() {
// Validates the configuration
// Does not modify the public fields as derived from the configuration passed to the application,
-// but does initialize private fields available through getters
+// but does initialize private fields available through getters.
func (c *Config) Validate() error {
if len(c.SourceBlockchains) == 0 {
- return errors.New("relayer not configured to relay from any subnets. A list of source subnets must be provided in the configuration file")
+ return errors.New("relayer not configured to relay from any subnets. A list of source subnets must be provided in the configuration file") //nolint:lll
}
if len(c.DestinationBlockchains) == 0 {
- return errors.New("relayer not configured to relay to any subnets. A list of destination subnets must be provided in the configuration file")
+ return errors.New("relayer not configured to relay to any subnets. A list of destination subnets must be provided in the configuration file") //nolint:lll
}
if err := c.PChainAPI.Validate(); err != nil {
return err
@@ -122,13 +121,6 @@ func (c *Config) Validate() error {
}
c.blockchainIDToSubnetID = blockchainIDToSubnetID
- // Validate the manual warp messages
- for i, msg := range c.ManualWarpMessages {
- if err := msg.Validate(); err != nil {
- return fmt.Errorf("invalid manual warp message at index %d: %w", i, err)
- }
- }
-
return nil
}
@@ -205,7 +197,11 @@ func (c *Config) InitializeWarpQuorums() error {
for _, destinationSubnet := range c.DestinationBlockchains {
err := destinationSubnet.initializeWarpQuorum()
if err != nil {
- return fmt.Errorf("failed to initialize Warp quorum for destination subnet %s: %w", destinationSubnet.SubnetID, err)
+ return fmt.Errorf(
+ "failed to initialize Warp quorum for destination subnet %s: %w",
+ destinationSubnet.SubnetID,
+ err,
+ )
}
}
diff --git a/config/config_test.go b/config/config_test.go
index a2221617..cb924bc1 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -78,8 +78,14 @@ func TestGetRelayerAccountPrivateKey_set_pk_in_config(t *testing.T) {
resultVerifier: func(c Config) bool {
// All destination subnets should have the default private key
for i, subnet := range c.DestinationBlockchains {
- if subnet.AccountPrivateKey != utils.SanitizeHexString(TestValidConfig.DestinationBlockchains[i].AccountPrivateKey) {
- fmt.Printf("expected: %s, got: %s\n", utils.SanitizeHexString(TestValidConfig.DestinationBlockchains[i].AccountPrivateKey), subnet.AccountPrivateKey)
+ if subnet.AccountPrivateKey != utils.SanitizeHexString(
+ TestValidConfig.DestinationBlockchains[i].AccountPrivateKey,
+ ) {
+ fmt.Printf(
+ "expected: %s, got: %s\n",
+ utils.SanitizeHexString(TestValidConfig.DestinationBlockchains[i].AccountPrivateKey),
+ subnet.AccountPrivateKey,
+ )
return false
}
}
@@ -102,18 +108,30 @@ func TestGetRelayerAccountPrivateKey_set_pk_with_subnet_env(t *testing.T) {
},
envSetter: func() {
// Overwrite the PK for the first subnet using an env var
- varName := fmt.Sprintf("%s_%s", accountPrivateKeyEnvVarName, TestValidConfig.DestinationBlockchains[0].BlockchainID)
+ varName := fmt.Sprintf(
+ "%s_%s",
+ accountPrivateKeyEnvVarName,
+ TestValidConfig.DestinationBlockchains[0].BlockchainID,
+ )
t.Setenv(varName, testPk2)
},
expectedOverwritten: true,
resultVerifier: func(c Config) bool {
// All destination subnets should have testPk1
if c.DestinationBlockchains[0].AccountPrivateKey != utils.SanitizeHexString(testPk2) {
- fmt.Printf("expected: %s, got: %s\n", utils.SanitizeHexString(testPk2), c.DestinationBlockchains[0].AccountPrivateKey)
+ fmt.Printf(
+ "expected: %s, got: %s\n",
+ utils.SanitizeHexString(testPk2),
+ c.DestinationBlockchains[0].AccountPrivateKey,
+ )
return false
}
if c.DestinationBlockchains[1].AccountPrivateKey != utils.SanitizeHexString(testPk1) {
- fmt.Printf("expected: %s, got: %s\n", utils.SanitizeHexString(testPk1), c.DestinationBlockchains[1].AccountPrivateKey)
+ fmt.Printf(
+ "expected: %s, got: %s\n",
+ utils.SanitizeHexString(testPk1),
+ c.DestinationBlockchains[1].AccountPrivateKey,
+ )
return false
}
return true
@@ -339,7 +357,10 @@ func TestGetWarpQuorum(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) {
client := mock_ethclient.NewMockClient(gomock.NewController(t))
gomock.InOrder(
- client.EXPECT().ChainConfig(gomock.Any()).Return(&testCase.chainConfig, nil).Times(testCase.getChainConfigCalls),
+ client.EXPECT().ChainConfig(gomock.Any()).Return(
+ &testCase.chainConfig,
+ nil,
+ ).Times(testCase.getChainConfigCalls),
)
quorum, err := getWarpQuorum(testCase.subnetID, testCase.blockchainID, client)
diff --git a/config/destination_blockchain.go b/config/destination_blockchain.go
index 80cd9ed0..7146f7ce 100644
--- a/config/destination_blockchain.go
+++ b/config/destination_blockchain.go
@@ -10,7 +10,8 @@ import (
"github.com/ethereum/go-ethereum/crypto"
)
-// Destination blockchain configuration. Specifies how to connect to and issue transactions on the desination blockchain.
+// Destination blockchain configuration. Specifies how to connect to and issue
+// transactions on the desination blockchain.
type DestinationBlockchain struct {
SubnetID string `mapstructure:"subnet-id" json:"subnet-id"`
BlockchainID string `mapstructure:"blockchain-id" json:"blockchain-id"`
@@ -28,14 +29,8 @@ type DestinationBlockchain struct {
blockchainID ids.ID
}
-// Validatees the destination subnet configuration
+// Validates the destination subnet configuration
func (s *DestinationBlockchain) Validate() error {
- if _, err := ids.FromString(s.SubnetID); err != nil {
- return fmt.Errorf("invalid subnetID in destination subnet configuration. Provided ID: %s", s.SubnetID)
- }
- if _, err := ids.FromString(s.BlockchainID); err != nil {
- return fmt.Errorf("invalid blockchainID in destination subnet configuration. Provided ID: %s", s.BlockchainID)
- }
if err := s.RPCEndpoint.Validate(); err != nil {
return fmt.Errorf("invalid rpc-endpoint in destination subnet configuration: %w", err)
}
@@ -59,14 +54,14 @@ func (s *DestinationBlockchain) Validate() error {
}
// Validate and store the subnet and blockchain IDs for future use
- blockchainID, err := ids.FromString(s.BlockchainID)
+ blockchainID, err := utils.HexOrCB58ToID(s.BlockchainID)
if err != nil {
- return fmt.Errorf("invalid blockchainID in configuration. error: %w", err)
+ return fmt.Errorf("invalid blockchainID '%s' in configuration. error: %w", s.BlockchainID, err)
}
s.blockchainID = blockchainID
- subnetID, err := ids.FromString(s.SubnetID)
+ subnetID, err := utils.HexOrCB58ToID(s.SubnetID)
if err != nil {
- return fmt.Errorf("invalid subnetID in configuration. error: %w", err)
+ return fmt.Errorf("invalid subnetID '%s' in configuration. error: %w", s.SubnetID, err)
}
s.subnetID = subnetID
@@ -91,7 +86,12 @@ func (s *DestinationBlockchain) initializeWarpQuorum() error {
return fmt.Errorf("invalid subnetID in configuration. error: %w", err)
}
- client, err := utils.NewEthClientWithConfig(context.Background(), s.RPCEndpoint.BaseURL, s.RPCEndpoint.HTTPHeaders, s.RPCEndpoint.QueryParams)
+ client, err := utils.NewEthClientWithConfig(
+ context.Background(),
+ s.RPCEndpoint.BaseURL,
+ s.RPCEndpoint.HTTPHeaders,
+ s.RPCEndpoint.QueryParams,
+ )
if err != nil {
return fmt.Errorf("failed to dial destination blockchain %s: %w", blockchainID, err)
}
diff --git a/config/manual_warp_message.go b/config/manual_warp_message.go
deleted file mode 100644
index db5b26b0..00000000
--- a/config/manual_warp_message.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package config
-
-import (
- "encoding/hex"
- "errors"
-
- "github.com/ava-labs/avalanchego/ids"
- "github.com/ava-labs/awm-relayer/utils"
- "github.com/ethereum/go-ethereum/common"
-)
-
-// Defines a manual warp message to be sent from the relayer on startup.
-type ManualWarpMessage struct {
- UnsignedMessageBytes string `mapstructure:"unsigned-message-bytes" json:"unsigned-message-bytes"`
- SourceBlockchainID string `mapstructure:"source-blockchain-id" json:"source-blockchain-id"`
- DestinationBlockchainID string `mapstructure:"destination-blockchain-id" json:"destination-blockchain-id"`
- SourceAddress string `mapstructure:"source-address" json:"source-address"`
- DestinationAddress string `mapstructure:"destination-address" json:"destination-address"`
-
- // convenience fields to access the values after initialization
- unsignedMessageBytes []byte
- sourceBlockchainID ids.ID
- destinationBlockchainID ids.ID
- sourceAddress common.Address
- destinationAddress common.Address
-}
-
-// Validates the manual Warp message configuration.
-// Does not modify the public fields as derived from the configuration passed to the application,
-// but does initialize private fields available through getters
-func (m *ManualWarpMessage) Validate() error {
- unsignedMsg, err := hex.DecodeString(utils.SanitizeHexString(m.UnsignedMessageBytes))
- if err != nil {
- return err
- }
- sourceBlockchainID, err := ids.FromString(m.SourceBlockchainID)
- if err != nil {
- return err
- }
- if !common.IsHexAddress(m.SourceAddress) {
- return errors.New("invalid source address in manual warp message configuration")
- }
- destinationBlockchainID, err := ids.FromString(m.DestinationBlockchainID)
- if err != nil {
- return err
- }
- if !common.IsHexAddress(m.DestinationAddress) {
- return errors.New("invalid destination address in manual warp message configuration")
- }
- m.unsignedMessageBytes = unsignedMsg
- m.sourceBlockchainID = sourceBlockchainID
- m.sourceAddress = common.HexToAddress(m.SourceAddress)
- m.destinationBlockchainID = destinationBlockchainID
- m.destinationAddress = common.HexToAddress(m.DestinationAddress)
- return nil
-}
-
-func (m *ManualWarpMessage) GetUnsignedMessageBytes() []byte {
- return m.unsignedMessageBytes
-}
-
-func (m *ManualWarpMessage) GetSourceBlockchainID() ids.ID {
- return m.sourceBlockchainID
-}
-
-func (m *ManualWarpMessage) GetSourceAddress() common.Address {
- return m.sourceAddress
-}
-
-func (m *ManualWarpMessage) GetDestinationBlockchainID() ids.ID {
- return m.destinationBlockchainID
-}
-
-func (m *ManualWarpMessage) GetDestinationAddress() common.Address {
- return m.destinationAddress
-}
diff --git a/config/source_blockchain.go b/config/source_blockchain.go
index d3e6f035..1786d280 100644
--- a/config/source_blockchain.go
+++ b/config/source_blockchain.go
@@ -16,15 +16,15 @@ import (
// Specifies the height from which to start processing historical blocks.
type SourceBlockchain struct {
SubnetID string `mapstructure:"subnet-id" json:"subnet-id"`
- BlockchainID string `mapstructure:"blockchain-id" json:"blockchain-id"`
+ BlockchainID string `mapstructure:"blockchain-id" json:"blockchain-id"` //nolint:lll
VM string `mapstructure:"vm" json:"vm"`
- RPCEndpoint APIConfig `mapstructure:"rpc-endpoint" json:"rpc-endpoint"`
- WSEndpoint APIConfig `mapstructure:"ws-endpoint" json:"ws-endpoint"`
- MessageContracts map[string]MessageProtocolConfig `mapstructure:"message-contracts" json:"message-contracts"`
- SupportedDestinations []*SupportedDestination `mapstructure:"supported-destinations" json:"supported-destinations"`
- ProcessHistoricalBlocksFromHeight uint64 `mapstructure:"process-historical-blocks-from-height" json:"process-historical-blocks-from-height"`
- AllowedOriginSenderAddresses []string `mapstructure:"allowed-origin-sender-addresses" json:"allowed-origin-sender-addresses"`
- WarpAPIEndpoint APIConfig `mapstructure:"warp-api-endpoint" json:"warp-api-endpoint"`
+ RPCEndpoint APIConfig `mapstructure:"rpc-endpoint" json:"rpc-endpoint"` //nolint:lll
+ WSEndpoint APIConfig `mapstructure:"ws-endpoint" json:"ws-endpoint"` //nolint:lll
+ MessageContracts map[string]MessageProtocolConfig `mapstructure:"message-contracts" json:"message-contracts"` //nolint:lll
+ SupportedDestinations []*SupportedDestination `mapstructure:"supported-destinations" json:"supported-destinations"` //nolint:lll
+ ProcessHistoricalBlocksFromHeight uint64 `mapstructure:"process-historical-blocks-from-height" json:"process-historical-blocks-from-height"` //nolint:lll
+ AllowedOriginSenderAddresses []string `mapstructure:"allowed-origin-sender-addresses" json:"allowed-origin-sender-addresses"` //nolint:lll
+ WarpAPIEndpoint APIConfig `mapstructure:"warp-api-endpoint" json:"warp-api-endpoint"` //nolint:lll
// convenience fields to access parsed data after initialization
subnetID ids.ID
@@ -33,16 +33,10 @@ type SourceBlockchain struct {
useAppRequestNetwork bool
}
-// Validates the source subnet configuration, including verifying that the supported destinations are present in destinationBlockchainIDs
-// Does not modify the public fields as derived from the configuration passed to the application,
-// but does initialize private fields available through getters
+// Validates the source subnet configuration, including verifying that the supported destinations are present in
+// destinationBlockchainIDs. Does not modify the public fields as derived from the configuration passed to the
+// application, but does initialize private fields available through getters.
func (s *SourceBlockchain) Validate(destinationBlockchainIDs *set.Set[string]) error {
- if _, err := ids.FromString(s.SubnetID); err != nil {
- return fmt.Errorf("invalid subnetID in source subnet configuration. Provided ID: %s", s.SubnetID)
- }
- if _, err := ids.FromString(s.BlockchainID); err != nil {
- return fmt.Errorf("invalid blockchainID in source subnet configuration. Provided ID: %s", s.BlockchainID)
- }
if err := s.RPCEndpoint.Validate(); err != nil {
return fmt.Errorf("invalid rpc-endpoint in source subnet configuration: %w", err)
}
@@ -79,14 +73,14 @@ func (s *SourceBlockchain) Validate(destinationBlockchainIDs *set.Set[string]) e
}
// Validate and store the subnet and blockchain IDs for future use
- blockchainID, err := ids.FromString(s.BlockchainID)
+ blockchainID, err := utils.HexOrCB58ToID(s.BlockchainID)
if err != nil {
- return fmt.Errorf("invalid blockchainID in configuration. error: %w", err)
+ return fmt.Errorf("invalid blockchainID '%s' in configuration. error: %w", s.BlockchainID, err)
}
s.blockchainID = blockchainID
- subnetID, err := ids.FromString(s.SubnetID)
+ subnetID, err := utils.HexOrCB58ToID(s.SubnetID)
if err != nil {
- return fmt.Errorf("invalid subnetID in configuration. error: %w", err)
+ return fmt.Errorf("invalid subnetID '%s' in configuration. error: %w", s.SubnetID, err)
}
s.subnetID = subnetID
@@ -99,23 +93,31 @@ func (s *SourceBlockchain) Validate(destinationBlockchainIDs *set.Set[string]) e
}
}
for _, dest := range s.SupportedDestinations {
- blockchainID, err := ids.FromString(dest.BlockchainID)
+ blockchainID, err := utils.HexOrCB58ToID(dest.BlockchainID)
if err != nil {
return fmt.Errorf("invalid blockchainID in configuration. error: %w", err)
}
if !destinationBlockchainIDs.Contains(dest.BlockchainID) {
- return fmt.Errorf("configured source subnet %s has a supported destination blockchain ID %s that is not configured as a destination blockchain",
+ return fmt.Errorf(
+ "configured source subnet %s has a supported destination blockchain ID %s that is not configured as a destination blockchain", //nolint:lll
s.SubnetID,
- blockchainID)
+ blockchainID,
+ )
}
dest.blockchainID = blockchainID
for _, addressStr := range dest.Addresses {
if !common.IsHexAddress(addressStr) {
- return fmt.Errorf("invalid allowed destination address in source blockchain configuration: %s", addressStr)
+ return fmt.Errorf(
+ "invalid allowed destination address in source blockchain configuration: %s",
+ addressStr,
+ )
}
address := common.HexToAddress(addressStr)
if address == utils.ZeroAddress {
- return fmt.Errorf("invalid allowed destination address in source blockchain configuration: %s", addressStr)
+ return fmt.Errorf(
+ "invalid allowed destination address in source blockchain configuration: %s",
+ addressStr,
+ )
}
dest.addresses = append(dest.addresses, address)
}
@@ -125,11 +127,17 @@ func (s *SourceBlockchain) Validate(destinationBlockchainIDs *set.Set[string]) e
allowedOriginSenderAddresses := make([]common.Address, len(s.AllowedOriginSenderAddresses))
for i, addressStr := range s.AllowedOriginSenderAddresses {
if !common.IsHexAddress(addressStr) {
- return fmt.Errorf("invalid allowed origin sender address in source blockchain configuration: %s", addressStr)
+ return fmt.Errorf(
+ "invalid allowed origin sender address in source blockchain configuration: %s",
+ addressStr,
+ )
}
address := common.HexToAddress(addressStr)
if address == utils.ZeroAddress {
- return fmt.Errorf("invalid allowed origin sender address in source blockchain configuration: %s", addressStr)
+ return fmt.Errorf(
+ "invalid allowed origin sender address in source blockchain configuration: %s",
+ addressStr,
+ )
}
allowedOriginSenderAddresses[i] = address
}
diff --git a/config/viper.go b/config/viper.go
index 0273ea96..7ea590c4 100644
--- a/config/viper.go
+++ b/config/viper.go
@@ -89,12 +89,22 @@ func BuildConfig(v *viper.Viper) (Config, error) {
privateKey := subnet.AccountPrivateKey
if accountPrivateKey != "" {
privateKey = accountPrivateKey
- cfg.overwrittenOptions = append(cfg.overwrittenOptions, fmt.Sprintf("destination-blockchain(%s).account-private-key", subnet.blockchainID))
+ cfg.overwrittenOptions = append(
+ cfg.overwrittenOptions,
+ fmt.Sprintf("destination-blockchain(%s).account-private-key", subnet.blockchainID),
+ )
// Otherwise, check for private keys suffixed with the chain ID and set it for that subnet
// Since the key is dynamic, this is only possible through environment variables
- } else if privateKeyFromEnv := os.Getenv(fmt.Sprintf("%s_%s", accountPrivateKeyEnvVarName, subnet.BlockchainID)); privateKeyFromEnv != "" {
+ } else if privateKeyFromEnv := os.Getenv(fmt.Sprintf(
+ "%s_%s",
+ accountPrivateKeyEnvVarName,
+ subnet.BlockchainID,
+ )); privateKeyFromEnv != "" {
privateKey = privateKeyFromEnv
- cfg.overwrittenOptions = append(cfg.overwrittenOptions, fmt.Sprintf("destination-blockchain(%s).account-private-key", subnet.blockchainID))
+ cfg.overwrittenOptions = append(cfg.overwrittenOptions, fmt.Sprintf(
+ "destination-blockchain(%s).account-private-key",
+ subnet.blockchainID),
+ )
}
cfg.DestinationBlockchains[i].AccountPrivateKey = utils.SanitizeHexString(privateKey)
}
diff --git a/database/json_file_storage_test.go b/database/json_file_storage_test.go
index 7e524714..9361a730 100644
--- a/database/json_file_storage_test.go
+++ b/database/json_file_storage_test.go
@@ -70,10 +70,16 @@ func TestConcurrentWriteReadSingleChain(t *testing.T) {
if !success {
t.Fatalf("failed to convert latest block to big.Int. err: %v", err)
}
- assert.Equal(t, finalTargetValue, latestProcessedBlock.Uint64(), "latest processed block height is not correct.")
+ assert.Equal(
+ t,
+ finalTargetValue,
+ latestProcessedBlock.Uint64(),
+ "latest processed block height is not correct.",
+ )
}
-// Test that the JSON database can write and read from multiple chains concurrently. Write to any given chain are not concurrent.
+// Test that the JSON database can write and read from multiple chains concurrently.
+// Writes to any given chain are not concurrent.
func TestConcurrentWriteReadMultipleChains(t *testing.T) {
relayerIDs := createRelayerIDs(
[]ids.ID{
@@ -111,7 +117,12 @@ func TestConcurrentWriteReadMultipleChains(t *testing.T) {
if !success {
t.Fatalf("failed to convert latest block to big.Int. err: %v", err)
}
- assert.Equal(t, finalTargetValue, latestProcessedBlock.Uint64(), fmt.Sprintf("latest processed block height is not correct. networkID: %d", i))
+ assert.Equal(
+ t,
+ finalTargetValue,
+ latestProcessedBlock.Uint64(),
+ fmt.Sprintf("latest processed block height is not correct. networkID: %d", i),
+ )
}
}
diff --git a/database/utils.go b/database/utils.go
index 00d1f741..b0a513a4 100644
--- a/database/utils.go
+++ b/database/utils.go
@@ -18,7 +18,8 @@ func IsKeyNotFoundError(err error) bool {
// Determines the height to process from. There are three cases:
// 1) The database contains the latest processed block data for the chain
-// - In this case, we return the maximum of the latest processed block and the configured processHistoricalBlocksFromHeight
+// - In this case, we return the maximum of the latest processed block and the
+// configured processHistoricalBlocksFromHeight.
//
// 2) The database has been configured for the chain, but does not contain the latest processed block data
// - In this case, we return the configured processHistoricalBlocksFromHeight
diff --git a/go.mod b/go.mod
index 12d511ed..b073ab52 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/ava-labs/awm-relayer
-go 1.21.11
+go 1.21.12
require (
github.com/ava-labs/avalanche-network-runner v1.7.6
diff --git a/main/main.go b/main/main.go
index 40a0db56..1ef462aa 100644
--- a/main/main.go
+++ b/main/main.go
@@ -5,7 +5,6 @@ package main
import (
"context"
- "encoding/hex"
"fmt"
"log"
"net/http"
@@ -14,18 +13,19 @@ import (
"strconv"
"strings"
- "github.com/alexliesenfeld/health"
"github.com/ava-labs/avalanchego/api/metrics"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/message"
"github.com/ava-labs/avalanchego/utils/constants"
"github.com/ava-labs/avalanchego/utils/logging"
+ "github.com/ava-labs/awm-relayer/api"
"github.com/ava-labs/awm-relayer/config"
"github.com/ava-labs/awm-relayer/database"
+ "github.com/ava-labs/awm-relayer/messages"
+ offchainregistry "github.com/ava-labs/awm-relayer/messages/off-chain-registry"
+ "github.com/ava-labs/awm-relayer/messages/teleporter"
"github.com/ava-labs/awm-relayer/peers"
"github.com/ava-labs/awm-relayer/relayer"
- "github.com/ava-labs/awm-relayer/types"
- relayerTypes "github.com/ava-labs/awm-relayer/types"
"github.com/ava-labs/awm-relayer/utils"
"github.com/ava-labs/awm-relayer/vms"
"github.com/ava-labs/subnet-evm/ethclient"
@@ -111,24 +111,27 @@ func main() {
logger.Info("Initializing destination clients")
destinationClients, err := vms.CreateDestinationClients(logger, cfg)
if err != nil {
- logger.Error(
- "Failed to create destination clients",
- zap.Error(err),
- )
+ logger.Fatal("Failed to create destination clients", zap.Error(err))
+ panic(err)
+ }
+
+ // Initialize all source clients
+ logger.Info("Initializing source clients")
+ sourceClients, err := createSourceClients(context.Background(), logger, &cfg)
+ if err != nil {
+ logger.Fatal("Failed to create source clients", zap.Error(err))
panic(err)
}
// Initialize metrics gathered through prometheus
gatherer, registerer, err := initializeMetrics()
if err != nil {
- logger.Fatal("Failed to set up prometheus metrics",
- zap.Error(err))
+ logger.Fatal("Failed to set up prometheus metrics", zap.Error(err))
panic(err)
}
// Initialize the global app request network
logger.Info("Initializing app request network")
-
// The app request network generates P2P networking logs that are verbose at the info level.
// Unless the log level is debug or lower, set the network log level to error to avoid spamming the logs.
// We do not collect metrics for the network.
@@ -142,51 +145,15 @@ func main() {
&cfg,
)
if err != nil {
- logger.Error(
- "Failed to create app request network",
- zap.Error(err),
- )
+ logger.Fatal("Failed to create app request network", zap.Error(err))
panic(err)
}
- // Each goroutine will have an atomic bool that it can set to false if it ever disconnects from its subscription.
- relayerHealth := make(map[ids.ID]*atomic.Bool)
-
- checker := health.NewChecker(
- health.WithCheck(health.Check{
- Name: "relayers-all",
- Check: func(context.Context) error {
- // Store the IDs as the cb58 encoding
- var unhealthyRelayers []string
- for id, health := range relayerHealth {
- if !health.Load() {
- unhealthyRelayers = append(unhealthyRelayers, id.String())
- }
- }
-
- if len(unhealthyRelayers) > 0 {
- return fmt.Errorf("relayers are unhealthy for blockchains %v", unhealthyRelayers)
- }
- return nil
- },
- }),
- )
-
- http.Handle("/health", health.NewHandler(checker))
-
- // start the health check server
- go func() {
- log.Fatalln(http.ListenAndServe(fmt.Sprintf(":%d", cfg.APIPort), nil))
- }()
-
startMetricsServer(logger, gatherer, cfg.MetricsPort)
- metrics, err := relayer.NewApplicationRelayerMetrics(registerer)
+ relayerMetrics, err := relayer.NewApplicationRelayerMetrics(registerer)
if err != nil {
- logger.Error(
- "Failed to create application relayer metrics",
- zap.Error(err),
- )
+ logger.Fatal("Failed to create application relayer metrics", zap.Error(err))
panic(err)
}
@@ -200,20 +167,14 @@ func main() {
constants.DefaultNetworkMaximumInboundTimeout,
)
if err != nil {
- logger.Error(
- "Failed to create message creator",
- zap.Error(err),
- )
+ logger.Fatal("Failed to create message creator", zap.Error(err))
panic(err)
}
// Initialize the database
db, err := database.NewDatabase(logger, &cfg)
if err != nil {
- logger.Error(
- "Failed to create database",
- zap.Error(err),
- )
+ logger.Fatal("Failed to create database", zap.Error(err))
panic(err)
}
@@ -221,25 +182,46 @@ func main() {
ticker := utils.NewTicker(cfg.DBWriteIntervalSeconds)
go ticker.Run()
- // Gather manual Warp messages specified in the configuration
- manualWarpMessages := make(map[ids.ID][]*relayerTypes.WarpMessageInfo)
- for _, msg := range cfg.ManualWarpMessages {
- sourceBlockchainID := msg.GetSourceBlockchainID()
- unsignedMsg, err := types.UnpackWarpMessage(msg.GetUnsignedMessageBytes())
- if err != nil {
- logger.Error(
- "Failed to unpack manual Warp message",
- zap.String("warpMessageBytes", hex.EncodeToString(msg.GetUnsignedMessageBytes())),
- zap.Error(err),
- )
- panic(err)
- }
- warpLogInfo := relayerTypes.WarpMessageInfo{
- SourceAddress: msg.GetSourceAddress(),
- UnsignedMessage: unsignedMsg,
- }
- manualWarpMessages[sourceBlockchainID] = append(manualWarpMessages[sourceBlockchainID], &warpLogInfo)
+ relayerHealth := createHealthTrackers(&cfg)
+
+ messageHandlerFactories, err := createMessageHandlerFactories(logger, &cfg)
+ if err != nil {
+ logger.Fatal("Failed to create message handler factories", zap.Error(err))
+ panic(err)
+ }
+
+ applicationRelayers, minHeights, err := createApplicationRelayers(
+ context.Background(),
+ logger,
+ relayerMetrics,
+ db,
+ ticker,
+ network,
+ messageCreator,
+ &cfg,
+ sourceClients,
+ destinationClients,
+ )
+ if err != nil {
+ logger.Fatal("Failed to create application relayers", zap.Error(err))
+ panic(err)
}
+ messageCoordinator := relayer.NewMessageCoordinator(
+ logger,
+ messageHandlerFactories,
+ applicationRelayers,
+ sourceClients,
+ )
+
+ // Each Listener goroutine will have an atomic bool that it can set to false to indicate an unrecoverable error
+ api.HandleHealthCheck(logger, relayerHealth)
+ api.HandleRelay(logger, messageCoordinator)
+ api.HandleRelayMessage(logger, messageCoordinator)
+
+ // start the health check server
+ go func() {
+ log.Fatalln(http.ListenAndServe(fmt.Sprintf(":%d", cfg.APIPort), nil))
+ }()
errGroup, ctx := errgroup.WithContext(context.Background())
@@ -270,139 +252,157 @@ func main() {
// Create listeners for each of the subnets configured as a source
for _, s := range cfg.SourceBlockchains {
- blockchainID, err := ids.FromString(s.BlockchainID)
- if err != nil {
- logger.Error(
- "Invalid subnetID in configuration",
- zap.Error(err),
- )
- panic(err)
- }
sourceBlockchain := s
- health := atomic.NewBool(true)
- relayerHealth[blockchainID] = health
-
// errgroup will cancel the context when the first goroutine returns an error
errGroup.Go(func() error {
- // Dial the eth client
- ethClient, err := utils.NewEthClientWithConfig(
- context.Background(),
- sourceBlockchain.RPCEndpoint.BaseURL,
- sourceBlockchain.RPCEndpoint.HTTPHeaders,
- sourceBlockchain.RPCEndpoint.QueryParams,
- )
- if err != nil {
- logger.Error(
- "Failed to connect to node via RPC",
- zap.String("blockchainID", sourceBlockchain.BlockchainID),
- zap.Error(err),
- )
- return err
- }
-
- // Create the ApplicationRelayers
- applicationRelayers, minHeight, err := createApplicationRelayers(
- ctx,
- logger,
- metrics,
- db,
- ticker,
- *sourceBlockchain,
- network,
- messageCreator,
- &cfg,
- ethClient,
- destinationClients,
- )
- if err != nil {
- logger.Error(
- "Failed to create application relayers",
- zap.String("blockchainID", sourceBlockchain.BlockchainID),
- zap.Error(err),
- )
- return err
- }
- logger.Info(
- "Created application relayers",
- zap.String("blockchainID", sourceBlockchain.BlockchainID),
- )
-
// runListener runs until it errors or the context is cancelled by another goroutine
- return runListener(
+ return relayer.RunListener(
ctx,
logger,
*sourceBlockchain,
- health,
- manualWarpMessages[blockchainID],
- &cfg,
- ethClient,
- grpcClient,
- applicationRelayers,
- minHeight,
+ sourceClients[sourceBlockchain.GetBlockchainID()],
+ relayerHealth[sourceBlockchain.GetBlockchainID()],
+ cfg.ProcessMissedBlocks,
+ minHeights[sourceBlockchain.GetBlockchainID()],
+ messageCoordinator,
+ grpcClient,
)
})
}
err = errGroup.Wait()
- logger.Error(
- "Relayer exiting.",
- zap.Error(err),
- )
+ logger.Error("Relayer exiting.", zap.Error(err))
}
-// runListener creates a Listener instance and the ApplicationRelayers for a subnet.
-// The Listener listens for warp messages on that subnet, and the ApplicationRelayers handle delivery to the destination
-func runListener(
- ctx context.Context,
+func createMessageHandlerFactories(
logger logging.Logger,
- sourceBlockchain config.SourceBlockchain,
- relayerHealth *atomic.Bool,
- manualWarpMessages []*relayerTypes.WarpMessageInfo,
globalConfig *config.Config,
- ethClient ethclient.Client,
grpcClient *grpc.ClientConn,
- applicationRelayers map[common.Hash]*relayer.ApplicationRelayer,
- minHeight uint64,
-) error {
- // Create the Listener
- listener, err := relayer.NewListener(
- logger,
- sourceBlockchain,
- relayerHealth,
- globalConfig,
- applicationRelayers,
- minHeight,
- ethClient,
- grpcClient,
- )
- if err != nil {
- return fmt.Errorf("failed to create listener instance: %w", err)
+) (map[ids.ID]map[common.Address]messages.MessageHandlerFactory, error) {
+ messageHandlerFactories := make(map[ids.ID]map[common.Address]messages.MessageHandlerFactory)
+ for _, sourceBlockchain := range globalConfig.SourceBlockchains {
+ messageHandlerFactoriesForSource := make(map[common.Address]messages.MessageHandlerFactory)
+ // Create message handler factories for each supported message protocol
+ for addressStr, cfg := range sourceBlockchain.MessageContracts {
+ address := common.HexToAddress(addressStr)
+ format := cfg.MessageFormat
+ var (
+ m messages.MessageHandlerFactory
+ err error
+ )
+ switch config.ParseMessageProtocol(format) {
+ case config.TELEPORTER:
+ m, err = teleporter.NewMessageHandlerFactory(
+ logger,
+ address,
+ cfg,
+ )
+ case config.OFF_CHAIN_REGISTRY:
+ m, err = offchainregistry.NewMessageHandlerFactory(
+ logger,
+ cfg,
+ )
+ default:
+ m, err = nil, fmt.Errorf("invalid message format %s", format)
+ }
+ if err != nil {
+ logger.Error("Failed to create message handler factory", zap.Error(err))
+ return nil, err
+ }
+ messageHandlerFactoriesForSource[address] = m
+ }
+ messageHandlerFactories[sourceBlockchain.GetBlockchainID()] = messageHandlerFactoriesForSource
}
- logger.Info(
- "Created listener",
- zap.String("blockchainID", sourceBlockchain.BlockchainID),
- )
- err = listener.ProcessManualWarpMessages(logger, manualWarpMessages, sourceBlockchain)
- if err != nil {
- logger.Error(
- "Failed to process manual Warp messages",
- zap.String("blockchainID", sourceBlockchain.BlockchainID),
- zap.Error(err),
+ return messageHandlerFactories, nil
+}
+
+func createSourceClients(
+ ctx context.Context,
+ logger logging.Logger,
+ cfg *config.Config,
+) (map[ids.ID]ethclient.Client, error) {
+ var err error
+ clients := make(map[ids.ID]ethclient.Client)
+
+ for _, sourceBlockchain := range cfg.SourceBlockchains {
+ clients[sourceBlockchain.GetBlockchainID()], err = utils.NewEthClientWithConfig(
+ ctx,
+ sourceBlockchain.RPCEndpoint.BaseURL,
+ sourceBlockchain.RPCEndpoint.HTTPHeaders,
+ sourceBlockchain.RPCEndpoint.QueryParams,
)
+ if err != nil {
+ logger.Error(
+ "Failed to connect to node via RPC",
+ zap.String("blockchainID", sourceBlockchain.BlockchainID),
+ zap.Error(err),
+ )
+ return nil, err
+ }
}
+ return clients, nil
+}
- logger.Info(
- "Listener initialized. Listening for messages to relay.",
- zap.String("originBlockchainID", sourceBlockchain.BlockchainID),
- )
+// Returns a map of application relayers, as well as a map of source blockchain IDs to starting heights.
+func createApplicationRelayers(
+ ctx context.Context,
+ logger logging.Logger,
+ relayerMetrics *relayer.ApplicationRelayerMetrics,
+ db database.RelayerDatabase,
+ ticker *utils.Ticker,
+ network *peers.AppRequestNetwork,
+ messageCreator message.Creator,
+ cfg *config.Config,
+ sourceClients map[ids.ID]ethclient.Client,
+ destinationClients map[ids.ID]vms.DestinationClient,
+) (map[common.Hash]*relayer.ApplicationRelayer, map[ids.ID]uint64, error) {
+ applicationRelayers := make(map[common.Hash]*relayer.ApplicationRelayer)
+ minHeights := make(map[ids.ID]uint64)
+ for _, sourceBlockchain := range cfg.SourceBlockchains {
+ currentHeight, err := sourceClients[sourceBlockchain.GetBlockchainID()].BlockNumber(ctx)
+ if err != nil {
+ logger.Error("Failed to get current block height", zap.Error(err))
+ return nil, nil, err
+ }
+
+ // Create the ApplicationRelayers
+ applicationRelayersForSource, minHeight, err := createApplicationRelayersForSourceChain(
+ ctx,
+ logger,
+ relayerMetrics,
+ db,
+ ticker,
+ *sourceBlockchain,
+ network,
+ messageCreator,
+ cfg,
+ currentHeight,
+ destinationClients,
+ )
+ if err != nil {
+ logger.Error(
+ "Failed to create application relayers",
+ zap.String("blockchainID", sourceBlockchain.BlockchainID),
+ zap.Error(err),
+ )
+ return nil, nil, err
+ }
- // Wait for logs from the subscribed node
- // Will only return on error or context cancellation
- return listener.ProcessLogs(ctx)
+ for relayerID, applicationRelayer := range applicationRelayersForSource {
+ applicationRelayers[relayerID] = applicationRelayer
+ }
+ minHeights[sourceBlockchain.GetBlockchainID()] = minHeight
+
+ logger.Info(
+ "Created application relayers",
+ zap.String("blockchainID", sourceBlockchain.BlockchainID),
+ )
+ }
+ return applicationRelayers, minHeights, nil
}
// createApplicationRelayers creates Application Relayers for a given source blockchain.
-func createApplicationRelayers(
+func createApplicationRelayersForSourceChain(
ctx context.Context,
logger logging.Logger,
metrics *relayer.ApplicationRelayerMetrics,
@@ -412,7 +412,7 @@ func createApplicationRelayers(
network *peers.AppRequestNetwork,
messageCreator message.Creator,
cfg *config.Config,
- srcEthClient ethclient.Client,
+ currentHeight uint64,
destinationClients map[ids.ID]vms.DestinationClient,
) (map[common.Hash]*relayer.ApplicationRelayer, uint64, error) {
// Create the ApplicationRelayers
@@ -422,17 +422,8 @@ func createApplicationRelayers(
)
applicationRelayers := make(map[common.Hash]*relayer.ApplicationRelayer)
- currentHeight, err := srcEthClient.BlockNumber(context.Background())
- if err != nil {
- logger.Error(
- "Failed to get current block height",
- zap.Error(err),
- )
- return nil, 0, err
- }
-
// Each ApplicationRelayer determines its starting height based on the database state.
- // The Listener begins processing messages starting from the minimum height across all of the ApplicationRelayers
+ // The Listener begins processing messages starting from the minimum height across all the ApplicationRelayers
minHeight := uint64(0)
for _, relayerID := range database.GetSourceBlockchainRelayerIDs(&sourceBlockchain) {
height, err := database.CalculateStartingBlockHeight(
@@ -479,6 +470,14 @@ func createApplicationRelayers(
return applicationRelayers, minHeight, nil
}
+func createHealthTrackers(cfg *config.Config) map[ids.ID]*atomic.Bool {
+ healthTrackers := make(map[ids.ID]*atomic.Bool, len(cfg.SourceBlockchains))
+ for _, sourceBlockchain := range cfg.SourceBlockchains {
+ healthTrackers[sourceBlockchain.GetBlockchainID()] = atomic.NewBool(true)
+ }
+ return healthTrackers
+}
+
func startMetricsServer(logger logging.Logger, gatherer prometheus.Gatherer, port uint16) {
http.Handle("/metrics", promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{}))
diff --git a/messages/message_handler.go b/messages/message_handler.go
index 1ebfc04c..8a7419bc 100644
--- a/messages/message_handler.go
+++ b/messages/message_handler.go
@@ -27,9 +27,11 @@ type MessageHandler interface {
// SendMessage sends the signed message to the destination chain. The payload parsed according to
// the VM rules is also passed in, since MessageManager does not assume any particular VM
- SendMessage(signedMessage *warp.Message, destinationClient vms.DestinationClient) error
+ // returns the transaction hash if the transaction is successful.
+ SendMessage(signedMessage *warp.Message, destinationClient vms.DestinationClient) (common.Hash, error)
- // GetMessageRoutingInfo returns the source chain ID, origin sender address, destination chain ID, and destination address
+ // GetMessageRoutingInfo returns the source chain ID, origin sender address,
+ // destination chain ID, and destination address.
GetMessageRoutingInfo() (
ids.ID,
common.Address,
diff --git a/messages/off-chain-registry/message_handler.go b/messages/off-chain-registry/message_handler.go
index 8abd17e4..75f2c3cf 100644
--- a/messages/off-chain-registry/message_handler.go
+++ b/messages/off-chain-registry/message_handler.go
@@ -81,8 +81,9 @@ func (m *messageHandler) GetUnsignedMessage() *warp.UnsignedMessage {
return m.unsignedMessage
}
-// ShouldSendMessage returns false if any contract is already registered as the specified version in the TeleporterRegistry contract.
-// This is because a single contract address can be registered to multiple versions, but each version may only map to a single contract address.
+// ShouldSendMessage returns false if any contract is already registered as the specified version
+// in the TeleporterRegistry contract. This is because a single contract address can be registered
+// to multiple versions, but each version may only map to a single contract address.
func (m *messageHandler) ShouldSendMessage(destinationClient vms.DestinationClient) (bool, error) {
addressedPayload, err := warpPayload.ParseAddressedCall(m.unsignedMessage.Payload)
if err != nil {
@@ -92,7 +93,9 @@ func (m *messageHandler) ShouldSendMessage(destinationClient vms.DestinationClie
)
return false, err
}
- entry, destination, err := teleporterregistry.UnpackTeleporterRegistryWarpPayload(addressedPayload.Payload)
+ entry, destination, err := teleporterregistry.UnpackTeleporterRegistryWarpPayload(
+ addressedPayload.Payload,
+ )
if err != nil {
m.logger.Error(
"Failed unpacking teleporter registry warp payload",
@@ -112,7 +115,10 @@ func (m *messageHandler) ShouldSendMessage(destinationClient vms.DestinationClie
// Get the correct destination client from the global map
client, ok := destinationClient.Client().(ethclient.Client)
if !ok {
- panic(fmt.Sprintf("Destination client for chain %s is not an Ethereum client", destinationClient.DestinationBlockchainID().String()))
+ panic(fmt.Sprintf(
+ "Destination client for chain %s is not an Ethereum client",
+ destinationClient.DestinationBlockchainID().String()),
+ )
}
// Check if the version is already registered in the TeleporterRegistry contract.
@@ -144,35 +150,49 @@ func (m *messageHandler) ShouldSendMessage(destinationClient vms.DestinationClie
return false, nil
}
-func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationClient vms.DestinationClient) error {
+func (m *messageHandler) SendMessage(
+ signedMessage *warp.Message,
+ destinationClient vms.DestinationClient,
+) (common.Hash, error) {
// Construct the transaction call data to call the TeleporterRegistry contract.
// Only one off-chain registry Warp message is sent at a time, so we hardcode the index to 0 in the call.
callData, err := teleporterregistry.PackAddProtocolVersion(0)
if err != nil {
m.logger.Error(
"Failed packing receiveCrossChainMessage call data",
- zap.String("destinationBlockchainID", destinationClient.DestinationBlockchainID().String()),
+ zap.String(
+ "destinationBlockchainID",
+ destinationClient.DestinationBlockchainID().String(),
+ ),
zap.String("warpMessageID", signedMessage.ID().String()),
)
- return err
+ return common.Hash{}, err
}
- _, err = destinationClient.SendTx(signedMessage, m.factory.registryAddress.Hex(), addProtocolVersionGasLimit, callData)
+ txHash, err := destinationClient.SendTx(
+ signedMessage,
+ m.factory.registryAddress.Hex(),
+ addProtocolVersionGasLimit,
+ callData,
+ )
if err != nil {
m.logger.Error(
"Failed to send tx.",
- zap.String("destinationBlockchainID", destinationClient.DestinationBlockchainID().String()),
+ zap.String(
+ "destinationBlockchainID",
+ destinationClient.DestinationBlockchainID().String(),
+ ),
zap.String("warpMessageID", signedMessage.ID().String()),
zap.Error(err),
)
- return err
+ return common.Hash{}, err
}
m.logger.Info(
"Sent message to destination chain",
zap.String("destinationBlockchainID", destinationClient.DestinationBlockchainID().String()),
zap.String("warpMessageID", signedMessage.ID().String()),
)
- return nil
+ return txHash, nil
}
func (m *messageHandler) GetMessageRoutingInfo() (
diff --git a/messages/off-chain-registry/message_handler_test.go b/messages/off-chain-registry/message_handler_test.go
index a71b895f..550dff55 100644
--- a/messages/off-chain-registry/message_handler_test.go
+++ b/messages/off-chain-registry/message_handler_test.go
@@ -152,9 +152,19 @@ func TestShouldSendMessage(t *testing.T) {
// construct the signed message
var unsignedMessage *warp.UnsignedMessage
if test.isMessageInvalid {
- unsignedMessage = createInvalidRegistryUnsignedWarpMessage(t, test.entry, teleporterRegistryAddress, test.destinationBlockchainID)
+ unsignedMessage = createInvalidRegistryUnsignedWarpMessage(
+ t,
+ test.entry,
+ teleporterRegistryAddress,
+ test.destinationBlockchainID,
+ )
} else {
- unsignedMessage = createRegistryUnsignedWarpMessage(t, test.entry, teleporterRegistryAddress, test.destinationBlockchainID)
+ unsignedMessage = createRegistryUnsignedWarpMessage(
+ t,
+ test.entry,
+ teleporterRegistryAddress,
+ test.destinationBlockchainID,
+ )
}
messageHandler, err := factory.NewMessageHandler(unsignedMessage)
require.NoError(t, err)
@@ -199,7 +209,10 @@ func createInvalidRegistryUnsignedWarpMessage(
payloadBytes, err := teleporterregistry.PackTeleporterRegistryWarpPayload(entry, teleporterRegistryAddress)
require.NoError(t, err)
- invalidAddressedPayload, err := payload.NewAddressedCall(messageProtocolAddress[:], append(payloadBytes, []byte{1, 2, 3, 4}...))
+ invalidAddressedPayload, err := payload.NewAddressedCall(
+ messageProtocolAddress[:],
+ append(payloadBytes, []byte{1, 2, 3, 4}...),
+ )
require.NoError(t, err)
invalidUnsignedMessage, err := warp.NewUnsignedMessage(
diff --git a/messages/teleporter/message_handler.go b/messages/teleporter/message_handler.go
index 812e7eff..d124eeee 100644
--- a/messages/teleporter/message_handler.go
+++ b/messages/teleporter/message_handler.go
@@ -226,9 +226,13 @@ func (m *messageHandler) ShouldSendMessage(destinationClient vms.DestinationClie
return decision, nil
}
-// SendMessage extracts the gasLimit and packs the call data to call the receiveCrossChainMessage method of the Teleporter contract,
-// and dispatches transaction construction and broadcast to the destination client
-func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationClient vms.DestinationClient) error {
+// SendMessage extracts the gasLimit and packs the call data to call the receiveCrossChainMessage
+// method of the Teleporter contract, and dispatches transaction construction and broadcast to the
+// destination client.
+func (m *messageHandler) SendMessage(
+ signedMessage *warp.Message,
+ destinationClient vms.DestinationClient,
+) (common.Hash, error) {
destinationBlockchainID := destinationClient.DestinationBlockchainID()
teleporterMessageID, err := teleporterUtils.CalculateMessageID(
m.factory.protocolAddress,
@@ -237,7 +241,7 @@ func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationCli
m.teleporterMessage.MessageNonce,
)
if err != nil {
- return fmt.Errorf("failed to calculate Teleporter message ID: %w", err)
+ return common.Hash{}, fmt.Errorf("failed to calculate Teleporter message ID: %w", err)
}
m.logger.Info(
@@ -254,7 +258,7 @@ func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationCli
zap.String("warpMessageID", signedMessage.ID().String()),
zap.String("teleporterMessageID", teleporterMessageID.String()),
)
- return err
+ return common.Hash{}, err
}
gasLimit, err := gasUtils.CalculateReceiveMessageGasLimit(
@@ -271,10 +275,13 @@ func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationCli
zap.String("warpMessageID", signedMessage.ID().String()),
zap.String("teleporterMessageID", teleporterMessageID.String()),
)
- return err
+ return common.Hash{}, err
}
// Construct the transaction call data to call the receive cross chain message method of the receiver precompile.
- callData, err := teleportermessenger.PackReceiveCrossChainMessage(0, common.HexToAddress(m.factory.messageConfig.RewardAddress))
+ callData, err := teleportermessenger.PackReceiveCrossChainMessage(
+ 0,
+ common.HexToAddress(m.factory.messageConfig.RewardAddress),
+ )
if err != nil {
m.logger.Error(
"Failed packing receiveCrossChainMessage call data",
@@ -282,10 +289,15 @@ func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationCli
zap.String("warpMessageID", signedMessage.ID().String()),
zap.String("teleporterMessageID", teleporterMessageID.String()),
)
- return err
+ return common.Hash{}, err
}
- txHash, err := destinationClient.SendTx(signedMessage, m.factory.protocolAddress.Hex(), gasLimit, callData)
+ txHash, err := destinationClient.SendTx(
+ signedMessage,
+ m.factory.protocolAddress.Hex(),
+ gasLimit,
+ callData,
+ )
if err != nil {
m.logger.Error(
"Failed to send tx.",
@@ -294,13 +306,13 @@ func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationCli
zap.String("teleporterMessageID", teleporterMessageID.String()),
zap.Error(err),
)
- return err
+ return common.Hash{}, err
}
// Wait for the message to be included in a block before returning
err = m.waitForReceipt(signedMessage, destinationClient, txHash, teleporterMessageID)
if err != nil {
- return err
+ return common.Hash{}, err
}
m.logger.Info(
@@ -310,10 +322,15 @@ func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationCli
zap.String("teleporterMessageID", teleporterMessageID.String()),
zap.String("txHash", txHash.String()),
)
- return nil
+ return txHash, nil
}
-func (m *messageHandler) waitForReceipt(signedMessage *warp.Message, destinationClient vms.DestinationClient, txHash common.Hash, teleporterMessageID ids.ID) error {
+func (m *messageHandler) waitForReceipt(
+ signedMessage *warp.Message,
+ destinationClient vms.DestinationClient,
+ txHash common.Hash,
+ teleporterMessageID ids.ID,
+) error {
destinationBlockchainID := destinationClient.DestinationBlockchainID()
callCtx, callCtxCancel := context.WithTimeout(context.Background(), 30*time.Second)
defer callCtxCancel()
@@ -348,7 +365,9 @@ func (m *messageHandler) waitForReceipt(signedMessage *warp.Message, destination
// parseTeleporterMessage returns the Warp message's corresponding Teleporter message from the cache if it exists.
// Otherwise parses the Warp message payload.
-func (f *factory) parseTeleporterMessage(unsignedMessage *warp.UnsignedMessage) (*teleportermessenger.TeleporterMessage, error) {
+func (f *factory) parseTeleporterMessage(
+ unsignedMessage *warp.UnsignedMessage,
+) (*teleportermessenger.TeleporterMessage, error) {
addressedPayload, err := warpPayload.ParseAddressedCall(unsignedMessage.Payload)
if err != nil {
f.logger.Error(
@@ -372,10 +391,15 @@ func (f *factory) parseTeleporterMessage(unsignedMessage *warp.UnsignedMessage)
// getTeleporterMessenger returns the Teleporter messenger instance for the destination chain.
// Panic instead of returning errors because this should never happen, and if it does, we do not
// want to log and swallow the error, since operations after this will fail too.
-func (f *factory) getTeleporterMessenger(destinationClient vms.DestinationClient) *teleportermessenger.TeleporterMessenger {
+func (f *factory) getTeleporterMessenger(
+ destinationClient vms.DestinationClient,
+) *teleportermessenger.TeleporterMessenger {
client, ok := destinationClient.Client().(ethclient.Client)
if !ok {
- panic(fmt.Sprintf("Destination client for chain %s is not an Ethereum client", destinationClient.DestinationBlockchainID().String()))
+ panic(fmt.Sprintf(
+ "Destination client for chain %s is not an Ethereum client",
+ destinationClient.DestinationBlockchainID().String()),
+ )
}
// Get the teleporter messenger contract
diff --git a/messages/teleporter/message_handler_test.go b/messages/teleporter/message_handler_test.go
index 73ae035d..e74bff62 100644
--- a/messages/teleporter/message_handler_test.go
+++ b/messages/teleporter/message_handler_test.go
@@ -73,14 +73,26 @@ func TestShouldSendMessage(t *testing.T) {
validMessageBytes, err := teleportermessenger.PackTeleporterMessage(validTeleporterMessage)
require.NoError(t, err)
- validAddressedCall, err := warpPayload.NewAddressedCall(messageProtocolAddress.Bytes(), validMessageBytes)
+ validAddressedCall, err := warpPayload.NewAddressedCall(
+ messageProtocolAddress.Bytes(),
+ validMessageBytes,
+ )
require.NoError(t, err)
sourceBlockchainID := ids.Empty
- warpUnsignedMessage, err := warp.NewUnsignedMessage(0, sourceBlockchainID, validAddressedCall.Bytes())
+ warpUnsignedMessage, err := warp.NewUnsignedMessage(
+ 0,
+ sourceBlockchainID,
+ validAddressedCall.Bytes(),
+ )
require.NoError(t, err)
- messageID, err := teleporterUtils.CalculateMessageID(messageProtocolAddress, sourceBlockchainID, destinationBlockchainID, validTeleporterMessage.MessageNonce)
+ messageID, err := teleporterUtils.CalculateMessageID(
+ messageProtocolAddress,
+ sourceBlockchainID,
+ destinationBlockchainID,
+ validTeleporterMessage.MessageNonce,
+ )
require.NoError(t, err)
messageReceivedInput, err := teleportermessenger.PackMessageReceived(messageID)
@@ -92,9 +104,16 @@ func TestShouldSendMessage(t *testing.T) {
messageDelivered, err := teleportermessenger.PackMessageReceivedOutput(true)
require.NoError(t, err)
- invalidAddressedCall, err := warpPayload.NewAddressedCall(messageProtocolAddress.Bytes(), validMessageBytes)
+ invalidAddressedCall, err := warpPayload.NewAddressedCall(
+ messageProtocolAddress.Bytes(),
+ validMessageBytes,
+ )
require.NoError(t, err)
- invalidWarpUnsignedMessage, err := warp.NewUnsignedMessage(0, sourceBlockchainID, append(invalidAddressedCall.Bytes(), []byte{1, 2, 3, 4}...))
+ invalidWarpUnsignedMessage, err := warp.NewUnsignedMessage(
+ 0,
+ sourceBlockchainID,
+ append(invalidAddressedCall.Bytes(), []byte{1, 2, 3, 4}...),
+ )
require.NoError(t, err)
testCases := []struct {
diff --git a/peers/app_request_network.go b/peers/app_request_network.go
index 8a915cde..736154a6 100644
--- a/peers/app_request_network.go
+++ b/peers/app_request_network.go
@@ -263,8 +263,12 @@ func (n *AppRequestNetwork) ConnectToCanonicalValidators(subnetID ids.ID) (*Conn
// Private helpers
-// Connect to the validators of the source blockchain. For each destination blockchain, verify that we have connected to a threshold of stake.
-func (n *AppRequestNetwork) connectToNonPrimaryNetworkPeers(cfg *config.Config, sourceBlockchain *config.SourceBlockchain) error {
+// Connect to the validators of the source blockchain. For each destination blockchain,
+// verify that we have connected to a threshold of stake.
+func (n *AppRequestNetwork) connectToNonPrimaryNetworkPeers(
+ cfg *config.Config,
+ sourceBlockchain *config.SourceBlockchain,
+) error {
subnetID := sourceBlockchain.GetSubnetID()
connectedValidators, err := n.ConnectToCanonicalValidators(subnetID)
if err != nil {
@@ -291,8 +295,12 @@ func (n *AppRequestNetwork) connectToNonPrimaryNetworkPeers(cfg *config.Config,
return nil
}
-// Connect to the validators of the destination blockchains. Verify that we have connected to a threshold of stake for each blockchain.
-func (n *AppRequestNetwork) connectToPrimaryNetworkPeers(cfg *config.Config, sourceBlockchain *config.SourceBlockchain) error {
+// Connect to the validators of the destination blockchains. Verify that we have connected
+// to a threshold of stake for each blockchain.
+func (n *AppRequestNetwork) connectToPrimaryNetworkPeers(
+ cfg *config.Config,
+ sourceBlockchain *config.SourceBlockchain,
+) error {
for _, destination := range sourceBlockchain.SupportedDestinations {
blockchainID := destination.GetBlockchainID()
subnetID := cfg.GetSubnetID(blockchainID)
diff --git a/peers/external_handler.go b/peers/external_handler.go
index 544597a9..7c925e1a 100644
--- a/peers/external_handler.go
+++ b/peers/external_handler.go
@@ -37,7 +37,8 @@ type expectedResponses struct {
expected, received int
}
-// Create a new RelayerExternalHandler to forward relevant inbound app messages to the respective Teleporter application relayer, as well as handle timeouts.
+// Create a new RelayerExternalHandler to forward relevant inbound app messages to the respective
+// Teleporter application relayer, as well as handle timeouts.
func NewRelayerExternalHandler(
logger logging.Logger,
registerer prometheus.Registerer,
@@ -76,9 +77,10 @@ func NewRelayerExternalHandler(
// For each inboundMessage, OnFinishedHandling must be called exactly once. However, since we handle relayer messages
// async, we must call OnFinishedHandling manually across all code paths.
//
-// This diagram illustrates how HandleInbound forwards relevant AppResponses to the corresponding Teleporter application relayer.
-// On startup, one Relayer goroutine is created per source subnet, which listens to the subscriber for cross-chain messages
-// When a cross-chain message is picked up by a Relayer, HandleInbound routes AppResponses traffic to the appropriate Relayer
+// This diagram illustrates how HandleInbound forwards relevant AppResponses to the corresponding
+// Teleporter application relayer. On startup, one Relayer goroutine is created per source subnet,
+// which listens to the subscriber for cross-chain messages. When a cross-chain message is picked
+// up by a Relayer, HandleInbound routes AppResponses traffic to the appropriate Relayer.
func (h *RelayerExternalHandler) HandleInbound(_ context.Context, inboundMessage message.InboundMessage) {
h.log.Debug(
"Handling app response",
@@ -109,9 +111,13 @@ func (h *RelayerExternalHandler) Disconnected(nodeID ids.NodeID) {
)
}
-// RegisterRequestID registers an AppRequest by requestID, and marks the number of expected responses, equivalent to the number of nodes requested.
-// requestID should be globally unique for the lifetime of the AppRequest. This is upper bounded by the timeout duration.
-func (h *RelayerExternalHandler) RegisterRequestID(requestID uint32, numExpectedResponses int) chan message.InboundMessage {
+// RegisterRequestID registers an AppRequest by requestID, and marks the number of
+// expected responses, equivalent to the number of nodes requested. requestID should
+// be globally unique for the lifetime of the AppRequest. This is upper bounded by the timeout duration.
+func (h *RelayerExternalHandler) RegisterRequestID(
+ requestID uint32,
+ numExpectedResponses int,
+) chan message.InboundMessage {
// Create a channel to receive the response
h.lock.Lock()
defer h.lock.Unlock()
diff --git a/peers/validators/canonical_validator_client.go b/peers/validators/canonical_validator_client.go
index 225638bc..c3d01dc7 100644
--- a/peers/validators/canonical_validator_client.go
+++ b/peers/validators/canonical_validator_client.go
@@ -36,7 +36,9 @@ func NewCanonicalValidatorClient(logger logging.Logger, apiConfig *config.APICon
}
}
-func (v *CanonicalValidatorClient) GetCurrentCanonicalValidatorSet(subnetID ids.ID) ([]*avalancheWarp.Validator, uint64, error) {
+func (v *CanonicalValidatorClient) GetCurrentCanonicalValidatorSet(
+ subnetID ids.ID,
+) ([]*avalancheWarp.Validator, uint64, error) {
height, err := v.GetCurrentHeight(context.Background())
if err != nil {
v.logger.Error(
@@ -109,7 +111,8 @@ func (v *CanonicalValidatorClient) GetValidatorSet(
// as well as their BLS public keys.
func (v *CanonicalValidatorClient) getCurrentValidatorSet(
ctx context.Context,
- subnetID ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) {
+ subnetID ids.ID,
+) (map[ids.NodeID]*validators.GetValidatorOutput, error) {
// Get the current subnet validators. These validators are not expected to include
// BLS signing information given that addPermissionlessValidatorTx is only used to
// add primary network validators.
diff --git a/relayer/application_relayer.go b/relayer/application_relayer.go
index c1f59600..e2bcfde9 100644
--- a/relayer/application_relayer.go
+++ b/relayer/application_relayer.go
@@ -31,6 +31,7 @@ import (
coreEthMsg "github.com/ava-labs/coreth/plugin/evm/message"
msg "github.com/ava-labs/subnet-evm/plugin/evm/message"
"github.com/ava-labs/subnet-evm/rpc"
+ "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"golang.org/x/sync/errgroup"
@@ -42,7 +43,8 @@ type blsSignatureBuf [bls.SignatureLen]byte
const (
// Number of retries to collect signatures from validators
maxRelayerQueryAttempts = 5
- // Maximum amount of time to spend waiting (in addition to network round trip time per attempt) during relayer signature query routine
+ // Maximum amount of time to spend waiting (in addition to network round trip time per attempt)
+ // during relayer signature query routine
signatureRequestRetryWaitPeriodMs = 10_000
)
@@ -98,7 +100,8 @@ func NewApplicationRelayer(
}
var signingSubnet ids.ID
if sourceBlockchain.GetSubnetID() == constants.PrimaryNetworkID {
- // If the message originates from the primary subnet, then we instead "self sign" the message using the validators of the destination subnet.
+ // If the message originates from the primary subnet, then we instead "self sign"
+ // the message using the validators of the destination subnet.
signingSubnet = cfg.GetSubnetID(relayerID.DestinationBlockchainID)
} else {
// Otherwise, the source subnet signs the message.
@@ -107,12 +110,19 @@ func NewApplicationRelayer(
sub := ticker.Subscribe()
- checkpointManager := checkpoint.NewCheckpointManager(logger, db, sub, relayerID, startingHeight)
+ checkpointManager := checkpoint.NewCheckpointManager(
+ logger,
+ db,
+ sub,
+ relayerID,
+ startingHeight,
+ )
checkpointManager.Run()
var warpClient *rpc.Client
if !sourceBlockchain.UseAppRequestNetwork() {
- // The subnet-evm Warp API client does not support query parameters or HTTP headers, and expects the URI to be in a specific form.
+ // The subnet-evm Warp API client does not support query parameters or HTTP headers
+ // and expects the URI to be in a specific form.
// Instead, we invoke the Warp API directly via the RPC client.
warpClient, err = utils.DialWithConfig(
context.Background(),
@@ -150,15 +160,21 @@ func NewApplicationRelayer(
// Process [msgs] at height [height] by relaying each message to the destination chain.
// Checkpoints the height with the checkpoint manager when all messages are relayed.
-// ProcessHeight is expected to be called for every block greater than or equal to the [startingHeight] provided in the constructor
-func (r *ApplicationRelayer) ProcessHeight(height uint64, handlers []messages.MessageHandler, errChan chan error) {
+// ProcessHeight is expected to be called for every block greater than or equal to the
+// [startingHeight] provided in the constructor.
+func (r *ApplicationRelayer) ProcessHeight(
+ height uint64,
+ handlers []messages.MessageHandler,
+ errChan chan error,
+) {
var eg errgroup.Group
for _, handler := range handlers {
- // Copy the loop variable to a local variable to avoid the loop variable being captured by the goroutine
- // Once we upgrade to Go 1.22, we can use the loop variable directly in the goroutine
+ // Copy the loop variable to a local variable to avoid the loop variable being captured by the
+ // goroutine. Once we upgrade to Go 1.22, we can use the loop variable directly in the goroutine.
h := handler
eg.Go(func() error {
- return r.ProcessMessage(h)
+ _, err := r.ProcessMessage(h)
+ return err
})
}
if err := eg.Wait(); err != nil {
@@ -182,29 +198,26 @@ func (r *ApplicationRelayer) ProcessHeight(height uint64, handlers []messages.Me
}
// Relays a message to the destination chain. Does not checkpoint the height.
-func (r *ApplicationRelayer) ProcessMessage(handler messages.MessageHandler) error {
+// returns the transaction hash if the message is successfully relayed.
+func (r *ApplicationRelayer) ProcessMessage(handler messages.MessageHandler) (common.Hash, error) {
// Increment the request ID. Make sure we don't hold the lock while we relay the message.
r.lock.Lock()
r.currentRequestID++
reqID := r.currentRequestID
r.lock.Unlock()
- err := r.relayMessage(
- reqID,
- handler,
- )
-
- return err
+ return r.relayMessage(reqID, handler)
}
func (r *ApplicationRelayer) RelayerID() database.RelayerID {
return r.relayerID
}
+// returns the transaction hash if the message is successfully relayed.
func (r *ApplicationRelayer) relayMessage(
requestID uint32,
handler messages.MessageHandler,
-) error {
+) (common.Hash, error) {
r.logger.Debug(
"Relaying message",
zap.Uint32("requestID", requestID),
@@ -218,11 +231,11 @@ func (r *ApplicationRelayer) relayMessage(
zap.Error(err),
)
r.incFailedRelayMessageCount("failed to check if message should be sent")
- return err
+ return common.Hash{}, err
}
if !shouldSend {
r.logger.Info("Message should not be sent")
- return nil
+ return common.Hash{}, nil
}
unsignedMessage := handler.GetUnsignedMessage()
@@ -240,7 +253,7 @@ func (r *ApplicationRelayer) relayMessage(
zap.Error(err),
)
r.incFailedRelayMessageCount("failed to create signed warp message via AppRequest network")
- return err
+ return common.Hash{}, err
}
} else {
r.incFetchSignatureRPCCount()
@@ -251,35 +264,38 @@ func (r *ApplicationRelayer) relayMessage(
zap.Error(err),
)
r.incFailedRelayMessageCount("failed to create signed warp message via RPC")
- return err
+ return common.Hash{}, err
}
}
// create signed message latency (ms)
r.setCreateSignedMessageLatencyMS(float64(time.Since(startCreateSignedMessageTime).Milliseconds()))
- err = handler.SendMessage(signedMessage, r.destinationClient)
+ txHash, err := handler.SendMessage(signedMessage, r.destinationClient)
if err != nil {
r.logger.Error(
"Failed to send warp message",
zap.Error(err),
)
r.incFailedRelayMessageCount("failed to send warp message")
- return err
+ return common.Hash{}, err
}
r.logger.Info(
"Finished relaying message to destination chain",
zap.String("destinationBlockchainID", r.relayerID.DestinationBlockchainID.String()),
+ zap.String("txHash", txHash.Hex()),
)
r.incSuccessfulRelayMessageCount()
- return nil
+ return txHash, nil
}
// createSignedMessage fetches the signed Warp message from the source chain via RPC.
// Each VM may implement their own RPC method to construct the aggregate signature, which
// will need to be accounted for here.
-func (r *ApplicationRelayer) createSignedMessage(unsignedMessage *avalancheWarp.UnsignedMessage) (*avalancheWarp.Message, error) {
+func (r *ApplicationRelayer) createSignedMessage(
+ unsignedMessage *avalancheWarp.UnsignedMessage,
+) (*avalancheWarp.Message, error) {
r.logger.Info("Fetching aggregate signature from the source chain validators via API")
var (
@@ -335,8 +351,12 @@ func (r *ApplicationRelayer) createSignedMessage(unsignedMessage *avalancheWarp.
return nil, errFailedToGetAggSig
}
-// createSignedMessageAppRequest collects signatures from nodes by directly querying them via AppRequest, then aggregates the signatures, and constructs the signed warp message.
-func (r *ApplicationRelayer) createSignedMessageAppRequest(unsignedMessage *avalancheWarp.UnsignedMessage, requestID uint32) (*avalancheWarp.Message, error) {
+// createSignedMessageAppRequest collects signatures from nodes by directly querying them
+// via AppRequest, then aggregates the signatures, and constructs the signed warp message.
+func (r *ApplicationRelayer) createSignedMessageAppRequest(
+ unsignedMessage *avalancheWarp.UnsignedMessage,
+ requestID uint32,
+) (*avalancheWarp.Message, error) {
r.logger.Info(
"Fetching aggregate signature from the source chain validators via AppRequest",
zap.String("warpMessageID", unsignedMessage.ID().String()),
@@ -390,7 +410,12 @@ func (r *ApplicationRelayer) createSignedMessageAppRequest(unsignedMessage *aval
}
// Construct the AppRequest
- outMsg, err := r.messageCreator.AppRequest(unsignedMessage.SourceChainID, requestID, peers.DefaultAppRequestTimeout, reqBytes)
+ outMsg, err := r.messageCreator.AppRequest(
+ unsignedMessage.SourceChainID,
+ requestID,
+ peers.DefaultAppRequestTimeout,
+ reqBytes,
+ )
if err != nil {
r.logger.Error(
"Failed to create app request message",
@@ -529,8 +554,9 @@ func (r *ApplicationRelayer) createSignedMessageAppRequest(unsignedMessage *aval
// Attempts to create a signed warp message from the accumulated responses.
// Returns a non-nil Warp message if [accumulatedSignatureWeight] exceeds the signature verification threshold.
-// Returns false in the second return parameter if the app response is not relevant to the current signature aggregation request.
-// Returns an error only if a non-recoverable error occurs, otherwise returns a nil error to continue processing responses.
+// Returns false in the second return parameter if the app response is not relevant to the current signature
+// aggregation request. Returns an error only if a non-recoverable error occurs, otherwise returns a nil error
+// to continue processing responses.
func (r *ApplicationRelayer) handleResponse(
response message.InboundMessage,
sentTo set.Set[ids.NodeID],
@@ -608,10 +634,13 @@ func (r *ApplicationRelayer) handleResponse(
return nil, true, err
}
- signedMsg, err := avalancheWarp.NewMessage(unsignedMessage, &avalancheWarp.BitSetSignature{
- Signers: vdrBitSet.Bytes(),
- Signature: *(*[bls.SignatureLen]byte)(bls.SignatureToBytes(aggSig)),
- })
+ signedMsg, err := avalancheWarp.NewMessage(
+ unsignedMessage,
+ &avalancheWarp.BitSetSignature{
+ Signers: vdrBitSet.Bytes(),
+ Signature: *(*[bls.SignatureLen]byte)(bls.SignatureToBytes(aggSig)),
+ },
+ )
if err != nil {
r.logger.Error(
"Failed to create new signed message",
@@ -628,8 +657,9 @@ func (r *ApplicationRelayer) handleResponse(
return nil, true, nil
}
-// isValidSignatureResponse tries to generate a signature from the peer.AsyncResponse, then verifies the signature against the node's public key.
-// If we are unable to generate the signature or verify correctly, false will be returned to indicate no valid signature was found in response.
+// isValidSignatureResponse tries to generate a signature from the peer.AsyncResponse, then verifies
+// the signature against the node's public key. If we are unable to generate the signature or verify
+// correctly, false will be returned to indicate no valid signature was found in response.
func (r *ApplicationRelayer) isValidSignatureResponse(
unsignedMessage *avalancheWarp.UnsignedMessage,
response message.InboundMessage,
@@ -693,9 +723,12 @@ func (r *ApplicationRelayer) isValidSignatureResponse(
return signature, true
}
-// aggregateSignatures constructs a BLS aggregate signature from the collected validator signatures. Also returns a bit set representing the
-// validators that are represented in the aggregate signature. The bit set is in canonical validator order.
-func (r *ApplicationRelayer) aggregateSignatures(signatureMap map[int]blsSignatureBuf) (*bls.Signature, set.Bits, error) {
+// aggregateSignatures constructs a BLS aggregate signature from the collected validator signatures. Also
+// returns a bit set representing the validators that are represented in the aggregate signature. The bit
+// set is in canonical validator order.
+func (r *ApplicationRelayer) aggregateSignatures(
+ signatureMap map[int]blsSignatureBuf,
+) (*bls.Signature, set.Bits, error) {
// Aggregate the signatures
signatures := make([]*bls.Signature, 0, len(signatureMap))
vdrBitSet := set.NewBits()
diff --git a/relayer/checkpoint/checkpoint.go b/relayer/checkpoint/checkpoint.go
index 6d0e0e08..6df59338 100644
--- a/relayer/checkpoint/checkpoint.go
+++ b/relayer/checkpoint/checkpoint.go
@@ -81,7 +81,11 @@ func (cm *CheckpointManager) writeToDatabase() {
zap.Uint64("height", cm.committedHeight),
zap.String("relayerID", cm.relayerID.ID.String()),
)
- err = cm.database.Put(cm.relayerID.ID, database.LatestProcessedBlockKey, []byte(strconv.FormatUint(cm.committedHeight, 10)))
+ err = cm.database.Put(
+ cm.relayerID.ID,
+ database.LatestProcessedBlockKey,
+ []byte(strconv.FormatUint(cm.committedHeight, 10)),
+ )
if err != nil {
cm.logger.Error(
"Failed to write latest processed block height",
diff --git a/relayer/listener.go b/relayer/listener.go
index 8cb3f594..42a832c3 100644
--- a/relayer/listener.go
+++ b/relayer/listener.go
@@ -8,20 +8,13 @@ import (
"fmt"
"math/big"
"math/rand"
- "sync"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/logging"
"github.com/ava-labs/awm-relayer/config"
- "github.com/ava-labs/awm-relayer/database"
- "github.com/ava-labs/awm-relayer/messages"
- offchainregistry "github.com/ava-labs/awm-relayer/messages/off-chain-registry"
- "github.com/ava-labs/awm-relayer/messages/teleporter"
- relayerTypes "github.com/ava-labs/awm-relayer/types"
"github.com/ava-labs/awm-relayer/utils"
- vms "github.com/ava-labs/awm-relayer/vms"
+ "github.com/ava-labs/awm-relayer/vms"
"github.com/ava-labs/subnet-evm/ethclient"
- "github.com/ethereum/go-ethereum/common"
"go.uber.org/atomic"
"go.uber.org/zap"
"google.golang.org/grpc"
@@ -36,28 +29,63 @@ const (
// Listener handles all messages sent from a given source chain
type Listener struct {
- Subscriber vms.Subscriber
- requestIDLock *sync.Mutex
- currentRequestID uint32
- contractMessage vms.ContractMessage
- messageHandlerFactories map[common.Address]messages.MessageHandlerFactory
- logger logging.Logger
- sourceBlockchain config.SourceBlockchain
- catchUpResultChan chan bool
- healthStatus *atomic.Bool
- globalConfig *config.Config
- applicationRelayers map[common.Hash]*ApplicationRelayer
- ethClient ethclient.Client
+ Subscriber vms.Subscriber
+ currentRequestID uint32
+ contractMessage vms.ContractMessage
+ logger logging.Logger
+ sourceBlockchain config.SourceBlockchain
+ catchUpResultChan chan bool
+ healthStatus *atomic.Bool
+ ethClient ethclient.Client
+ messageCoordinator *MessageCoordinator
}
-func NewListener(
+// runListener creates a Listener instance and the ApplicationRelayers for a subnet.
+// The Listener listens for warp messages on that subnet, and the ApplicationRelayers handle delivery to the destination
+func RunListener(
+ ctx context.Context,
logger logging.Logger,
sourceBlockchain config.SourceBlockchain,
+ ethRPCClient ethclient.Client,
relayerHealth *atomic.Bool,
- globalConfig *config.Config,
- applicationRelayers map[common.Hash]*ApplicationRelayer,
+ processMissedBlocks bool,
+ minHeight uint64,
+ messageCoordinator *MessageCoordinator,
+) error {
+ // Create the Listener
+ listener, err := newListener(
+ ctx,
+ logger,
+ sourceBlockchain,
+ ethRPCClient,
+ relayerHealth,
+ processMissedBlocks,
+ minHeight,
+ messageCoordinator,
+ )
+ if err != nil {
+ return fmt.Errorf("failed to create listener instance: %w", err)
+ }
+
+ logger.Info(
+ "Listener initialized. Listening for messages to relay.",
+ zap.String("originBlockchainID", sourceBlockchain.BlockchainID),
+ )
+
+ // Wait for logs from the subscribed node
+ // Will only return on error or context cancellation
+ return listener.processLogs(ctx)
+}
+
+func newListener(
+ ctx context.Context,
+ logger logging.Logger,
+ sourceBlockchain config.SourceBlockchain,
+ ethRPCClient ethclient.Client,
+ relayerHealth *atomic.Bool,
+ processMissedBlocks bool,
startingHeight uint64,
- ethClient ethclient.Client,
+ messageCoordinator *MessageCoordinator,
grpcClient *grpc.ClientConn,
) (*Listener, error) {
blockchainID, err := ids.FromString(sourceBlockchain.BlockchainID)
@@ -68,8 +96,9 @@ func NewListener(
)
return nil, err
}
+
ethWSClient, err := utils.NewEthClientWithConfig(
- context.Background(),
+ ctx,
sourceBlockchain.WSEndpoint.BaseURL,
sourceBlockchain.WSEndpoint.HTTPHeaders,
sourceBlockchain.WSEndpoint.QueryParams,
@@ -84,41 +113,6 @@ func NewListener(
}
sub := vms.NewSubscriber(logger, config.ParseVM(sourceBlockchain.VM), blockchainID, ethWSClient)
- // Create message managers for each supported message protocol
- messageHandlerFactories := make(map[common.Address]messages.MessageHandlerFactory)
- for addressStr, cfg := range sourceBlockchain.MessageContracts {
- address := common.HexToAddress(addressStr)
- format := cfg.MessageFormat
- var (
- m messages.MessageHandlerFactory
- err error
- )
- switch config.ParseMessageProtocol(format) {
- case config.TELEPORTER:
- m, err = teleporter.NewMessageHandlerFactory(
- logger,
- address,
- cfg,
- grpcClient,
- )
- case config.OFF_CHAIN_REGISTRY:
- m, err = offchainregistry.NewMessageHandlerFactory(
- logger,
- cfg,
- )
- default:
- m, err = nil, fmt.Errorf("invalid message format %s", format)
- }
- if err != nil {
- logger.Error(
- "Failed to create message manager",
- zap.Error(err),
- )
- return nil, err
- }
- messageHandlerFactories[address] = m
- }
-
// Marks when the listener has finished the catch-up process on startup.
// Until that time, we do not know the order in which messages are processed,
// since the catch-up process occurs concurrently with normal message processing
@@ -135,22 +129,19 @@ func NewListener(
zap.String("blockchainIDHex", sourceBlockchain.GetBlockchainID().Hex()),
)
lstnr := Listener{
- Subscriber: sub,
- requestIDLock: &sync.Mutex{},
- currentRequestID: rand.Uint32(), // Initialize to a random value to mitigate requestID collision
- contractMessage: vms.NewContractMessage(logger, sourceBlockchain),
- messageHandlerFactories: messageHandlerFactories,
- logger: logger,
- sourceBlockchain: sourceBlockchain,
- catchUpResultChan: catchUpResultChan,
- healthStatus: relayerHealth,
- globalConfig: globalConfig,
- applicationRelayers: applicationRelayers,
- ethClient: ethClient,
- }
-
- // Open the subscription. We must do this before processing any missed messages, otherwise we may miss an incoming message
- // in between fetching the latest block and subscribing.
+ Subscriber: sub,
+ currentRequestID: rand.Uint32(), // Initialize to a random value to mitigate requestID collision
+ contractMessage: vms.NewContractMessage(logger, sourceBlockchain),
+ logger: logger,
+ sourceBlockchain: sourceBlockchain,
+ catchUpResultChan: catchUpResultChan,
+ healthStatus: relayerHealth,
+ ethClient: ethRPCClient,
+ messageCoordinator: messageCoordinator,
+ }
+
+ // Open the subscription. We must do this before processing any missed messages, otherwise we may
+ // miss an incoming message in between fetching the latest block and subscribing.
err = lstnr.Subscriber.Subscribe(maxSubscribeAttempts)
if err != nil {
logger.Error(
@@ -160,7 +151,7 @@ func NewListener(
return nil, err
}
- if lstnr.globalConfig.ProcessMissedBlocks {
+ if processMissedBlocks {
// Process historical blocks in a separate goroutine so that the main processing loop can
// start processing new blocks as soon as possible. Otherwise, it's possible for
// ProcessFromHeight to overload the message queue and cause a deadlock.
@@ -179,7 +170,7 @@ func NewListener(
// Listens to the Subscriber logs channel to process them.
// On subscriber error, attempts to reconnect and errors if unable.
// Exits if context is cancelled by another goroutine.
-func (lstnr *Listener) ProcessLogs(ctx context.Context) error {
+func (lstnr *Listener) processLogs(ctx context.Context) error {
// Error channel for application relayer errors
errChan := make(chan error)
for {
@@ -214,52 +205,7 @@ func (lstnr *Listener) ProcessLogs(ctx context.Context) error {
return fmt.Errorf("failed to catch up on historical blocks")
}
case blockHeader := <-lstnr.Subscriber.Headers():
- // Parse the logs in the block, and group by application relayer
-
- block, err := relayerTypes.NewWarpBlockInfo(blockHeader, lstnr.ethClient)
- if err != nil {
- lstnr.logger.Error(
- "Failed to create Warp block info",
- zap.Error(err),
- )
- continue
- }
-
- // Relay the messages in the block to the destination chains. Continue on failure.
- lstnr.logger.Debug(
- "Processing block",
- zap.String("sourceBlockchainID", lstnr.sourceBlockchain.GetBlockchainID().String()),
- zap.Uint64("blockNumber", block.BlockNumber),
- )
-
- // Register each message in the block with the appropriate application relayer
- messageHandlers := make(map[common.Hash][]messages.MessageHandler)
- for _, warpLogInfo := range block.Messages {
- appRelayer, handler, err := lstnr.GetAppRelayerMessageHandler(warpLogInfo)
- if err != nil {
- lstnr.logger.Error(
- "Failed to parse message",
- zap.String("blockchainID", lstnr.sourceBlockchain.GetBlockchainID().String()),
- zap.Error(err),
- )
- continue
- }
- if appRelayer == nil {
- lstnr.logger.Debug("Application relayer not found. Skipping message relay")
- continue
- }
- messageHandlers[appRelayer.relayerID.ID] = append(messageHandlers[appRelayer.relayerID.ID], handler)
- }
- // Initiate message relay of all registered messages
- for _, appRelayer := range lstnr.applicationRelayers {
- // Dispatch all messages in the block to the appropriate application relayer.
- // An empty slice is still a valid argument to ProcessHeight; in this case the height is immediately committed.
- handlers := messageHandlers[appRelayer.relayerID.ID]
-
- // Process the height async. This is safe because the ApplicationRelayer maintains the threadsafe
- // invariant that heights are committed to the database one at a time, in order, with no gaps.
- go appRelayer.ProcessHeight(block.BlockNumber, handlers, errChan)
- }
+ go lstnr.messageCoordinator.ProcessBlock(blockHeader, lstnr.ethClient, errChan)
case err := <-lstnr.Subscriber.Err():
lstnr.healthStatus.Store(false)
lstnr.logger.Error(
@@ -301,162 +247,3 @@ func (lstnr *Listener) reconnectToSubscriber() error {
lstnr.healthStatus.Store(true)
return nil
}
-
-// Unpacks the Warp message and fetches the appropriate application relayer
-// Checks for the following registered keys. At most one of these keys should be registered.
-// 1. An exact match on sourceBlockchainID, destinationBlockchainID, originSenderAddress, and destinationAddress
-// 2. A match on sourceBlockchainID and destinationBlockchainID, with a specific originSenderAddress and any destinationAddress
-// 3. A match on sourceBlockchainID and destinationBlockchainID, with any originSenderAddress and a specific destinationAddress
-// 4. A match on sourceBlockchainID and destinationBlockchainID, with any originSenderAddress and any destinationAddress
-func (lstnr *Listener) getApplicationRelayer(
- sourceBlockchainID ids.ID,
- originSenderAddress common.Address,
- destinationBlockchainID ids.ID,
- destinationAddress common.Address,
-) *ApplicationRelayer {
- // Check for an exact match
- applicationRelayerID := database.CalculateRelayerID(
- sourceBlockchainID,
- destinationBlockchainID,
- originSenderAddress,
- destinationAddress,
- )
- if applicationRelayer, ok := lstnr.applicationRelayers[applicationRelayerID]; ok {
- return applicationRelayer
- }
-
- // Check for a match on sourceBlockchainID and destinationBlockchainID, with a specific originSenderAddress and any destinationAddress
- applicationRelayerID = database.CalculateRelayerID(
- sourceBlockchainID,
- destinationBlockchainID,
- originSenderAddress,
- database.AllAllowedAddress,
- )
- if applicationRelayer, ok := lstnr.applicationRelayers[applicationRelayerID]; ok {
- return applicationRelayer
- }
-
- // Check for a match on sourceBlockchainID and destinationBlockchainID, with any originSenderAddress and a specific destinationAddress
- applicationRelayerID = database.CalculateRelayerID(
- sourceBlockchainID,
- destinationBlockchainID,
- database.AllAllowedAddress,
- destinationAddress,
- )
- if applicationRelayer, ok := lstnr.applicationRelayers[applicationRelayerID]; ok {
- return applicationRelayer
- }
-
- // Check for a match on sourceBlockchainID and destinationBlockchainID, with any originSenderAddress and any destinationAddress
- applicationRelayerID = database.CalculateRelayerID(
- sourceBlockchainID,
- destinationBlockchainID,
- database.AllAllowedAddress,
- database.AllAllowedAddress,
- )
- if applicationRelayer, ok := lstnr.applicationRelayers[applicationRelayerID]; ok {
- return applicationRelayer
- }
- lstnr.logger.Debug(
- "Application relayer not found. Skipping message relay.",
- zap.String("blockchainID", lstnr.sourceBlockchain.GetBlockchainID().String()),
- zap.String("destinationBlockchainID", destinationBlockchainID.String()),
- zap.String("originSenderAddress", originSenderAddress.String()),
- zap.String("destinationAddress", destinationAddress.String()),
- )
- return nil
-}
-
-// Returns the ApplicationRelayer that is configured to handle this message, as well as a one-time MessageHandler
-// instance that the ApplicationRelayer uses to relay this specific message.
-// The MessageHandler and ApplicationRelayer are decoupled to support batch workflows in which a single ApplicationRelayer
-// processes multiple messages (using their corresponding MessageHandlers) in a single shot.
-func (lstnr *Listener) GetAppRelayerMessageHandler(warpMessageInfo *relayerTypes.WarpMessageInfo) (
- *ApplicationRelayer,
- messages.MessageHandler,
- error,
-) {
- // Check that the warp message is from a supported message protocol contract address.
- messageHandlerFactory, supportedMessageProtocol := lstnr.messageHandlerFactories[warpMessageInfo.SourceAddress]
- if !supportedMessageProtocol {
- // Do not return an error here because it is expected for there to be messages from other contracts
- // than just the ones supported by a single listener instance.
- lstnr.logger.Debug(
- "Warp message from unsupported message protocol address. Not relaying.",
- zap.String("protocolAddress", warpMessageInfo.SourceAddress.Hex()),
- )
- return nil, nil, nil
- }
- messageHandler, err := messageHandlerFactory.NewMessageHandler(warpMessageInfo.UnsignedMessage)
- if err != nil {
- lstnr.logger.Error(
- "Failed to create message handler",
- zap.Error(err),
- )
- return nil, nil, err
- }
-
- // Fetch the message delivery data
- sourceBlockchainID, originSenderAddress, destinationBlockchainID, destinationAddress, err := messageHandler.GetMessageRoutingInfo()
- if err != nil {
- lstnr.logger.Error(
- "Failed to get message routing information",
- zap.Error(err),
- )
- return nil, nil, err
- }
-
- lstnr.logger.Info(
- "Unpacked warp message",
- zap.String("sourceBlockchainID", sourceBlockchainID.String()),
- zap.String("originSenderAddress", originSenderAddress.String()),
- zap.String("destinationBlockchainID", destinationBlockchainID.String()),
- zap.String("destinationAddress", destinationAddress.String()),
- zap.String("warpMessageID", warpMessageInfo.UnsignedMessage.ID().String()),
- )
-
- appRelayer := lstnr.getApplicationRelayer(
- sourceBlockchainID,
- originSenderAddress,
- destinationBlockchainID,
- destinationAddress,
- )
- if appRelayer == nil {
- return nil, nil, nil
- }
- return appRelayer, messageHandler, nil
-}
-
-func (lstnr *Listener) ProcessManualWarpMessages(
- logger logging.Logger,
- manualWarpMessages []*relayerTypes.WarpMessageInfo,
- sourceBlockchain config.SourceBlockchain,
-) error {
- // Send any messages that were specified in the configuration
- for _, warpMessage := range manualWarpMessages {
- logger.Info(
- "Relaying manual Warp message",
- zap.String("blockchainID", sourceBlockchain.BlockchainID),
- zap.String("warpMessageID", warpMessage.UnsignedMessage.ID().String()),
- )
- appRelayer, handler, err := lstnr.GetAppRelayerMessageHandler(warpMessage)
- if err != nil {
- logger.Error(
- "Failed to parse manual Warp message.",
- zap.Error(err),
- zap.String("warpMessageID", warpMessage.UnsignedMessage.ID().String()),
- )
- return err
- }
- err = appRelayer.ProcessMessage(handler)
- if err != nil {
- logger.Error(
- "Failed to process manual Warp message",
- zap.String("blockchainID", sourceBlockchain.BlockchainID),
- zap.String("warpMessageID", warpMessage.UnsignedMessage.ID().String()),
- )
- return err
- }
- }
- return nil
-}
diff --git a/relayer/message_coordinator.go b/relayer/message_coordinator.go
new file mode 100644
index 00000000..f9de1b1c
--- /dev/null
+++ b/relayer/message_coordinator.go
@@ -0,0 +1,286 @@
+// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package relayer
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "math/big"
+
+ "github.com/ava-labs/avalanchego/ids"
+ "github.com/ava-labs/avalanchego/utils/logging"
+ "github.com/ava-labs/awm-relayer/database"
+ "github.com/ava-labs/awm-relayer/messages"
+ relayerTypes "github.com/ava-labs/awm-relayer/types"
+ "github.com/ava-labs/subnet-evm/core/types"
+ "github.com/ava-labs/subnet-evm/ethclient"
+ "github.com/ava-labs/subnet-evm/interfaces"
+ "github.com/ava-labs/subnet-evm/precompile/contracts/warp"
+ "github.com/ethereum/go-ethereum/common"
+ "go.uber.org/zap"
+)
+
+// MessageCoordinator contains all the logic required to process messages in the relayer.
+// Other components such as the listeners or the API should pass messages to the MessageCoordinator
+// so that it can parse the message(s) and pass them the the proper ApplicationRelayer.
+type MessageCoordinator struct {
+ logger logging.Logger
+ // Maps Source blockchain ID and protocol address to a Message Handler Factory
+ messageHandlerFactories map[ids.ID]map[common.Address]messages.MessageHandlerFactory
+ applicationRelayers map[common.Hash]*ApplicationRelayer
+ sourceClients map[ids.ID]ethclient.Client
+}
+
+func NewMessageCoordinator(
+ logger logging.Logger,
+ messageHandlerFactories map[ids.ID]map[common.Address]messages.MessageHandlerFactory,
+ applicationRelayers map[common.Hash]*ApplicationRelayer,
+ sourceClients map[ids.ID]ethclient.Client,
+) *MessageCoordinator {
+ return &MessageCoordinator{
+ logger: logger,
+ messageHandlerFactories: messageHandlerFactories,
+ applicationRelayers: applicationRelayers,
+ sourceClients: sourceClients,
+ }
+}
+
+// getAppRelayerMessageHandler returns the ApplicationRelayer that is configured to handle this message,
+// as well as a one-time MessageHandler instance that the ApplicationRelayer uses to relay this specific message.
+// The MessageHandler and ApplicationRelayer are decoupled to support batch workflows in which a single
+// ApplicationRelayer processes multiple messages (using their corresponding MessageHandlers) in a single shot.
+func (mc *MessageCoordinator) getAppRelayerMessageHandler(
+ warpMessageInfo *relayerTypes.WarpMessageInfo,
+) (
+ *ApplicationRelayer,
+ messages.MessageHandler,
+ error,
+) {
+ // Check that the warp message is from a supported message protocol contract address.
+ //nolint:lll
+ messageHandlerFactory, supportedMessageProtocol := mc.messageHandlerFactories[warpMessageInfo.UnsignedMessage.SourceChainID][warpMessageInfo.SourceAddress]
+ if !supportedMessageProtocol {
+ // Do not return an error here because it is expected for there to be messages from other contracts
+ // than just the ones supported by a single listener instance.
+ mc.logger.Debug(
+ "Warp message from unsupported message protocol address. Not relaying.",
+ zap.String("protocolAddress", warpMessageInfo.SourceAddress.Hex()),
+ )
+ return nil, nil, nil
+ }
+ messageHandler, err := messageHandlerFactory.NewMessageHandler(warpMessageInfo.UnsignedMessage)
+ if err != nil {
+ mc.logger.Error("Failed to create message handler", zap.Error(err))
+ return nil, nil, err
+ }
+
+ // Fetch the message delivery data
+ //nolint:lll
+ sourceBlockchainID, originSenderAddress, destinationBlockchainID, destinationAddress, err := messageHandler.GetMessageRoutingInfo()
+ if err != nil {
+ mc.logger.Error("Failed to get message routing information", zap.Error(err))
+ return nil, nil, err
+ }
+
+ mc.logger.Info(
+ "Unpacked warp message",
+ zap.String("sourceBlockchainID", sourceBlockchainID.String()),
+ zap.String("originSenderAddress", originSenderAddress.String()),
+ zap.String("destinationBlockchainID", destinationBlockchainID.String()),
+ zap.String("destinationAddress", destinationAddress.String()),
+ zap.String("warpMessageID", warpMessageInfo.UnsignedMessage.ID().String()),
+ )
+
+ appRelayer := mc.getApplicationRelayer(
+ sourceBlockchainID,
+ originSenderAddress,
+ destinationBlockchainID,
+ destinationAddress,
+ )
+ if appRelayer == nil {
+ return nil, nil, nil
+ }
+ return appRelayer, messageHandler, nil
+}
+
+// Unpacks the Warp message and fetches the appropriate application relayer
+// Checks for the following registered keys. At most one of these keys should be registered.
+// 1. An exact match on sourceBlockchainID, destinationBlockchainID, originSenderAddress, and destinationAddress
+// 2. A match on sourceBlockchainID and destinationBlockchainID, with a specific originSenderAddress and
+// any destinationAddress
+// 3. A match on sourceBlockchainID and destinationBlockchainID, with any originSenderAddress and a
+// specific destinationAddress
+// 4. A match on sourceBlockchainID and destinationBlockchainID, with any originSenderAddress and any
+// destinationAddress
+func (mc *MessageCoordinator) getApplicationRelayer(
+ sourceBlockchainID ids.ID,
+ originSenderAddress common.Address,
+ destinationBlockchainID ids.ID,
+ destinationAddress common.Address,
+) *ApplicationRelayer {
+ // Check for an exact match
+ applicationRelayerID := database.CalculateRelayerID(
+ sourceBlockchainID,
+ destinationBlockchainID,
+ originSenderAddress,
+ destinationAddress,
+ )
+ if applicationRelayer, ok := mc.applicationRelayers[applicationRelayerID]; ok {
+ return applicationRelayer
+ }
+
+ // Check for a match on sourceBlockchainID and destinationBlockchainID, with a specific
+ // originSenderAddress and any destinationAddress.
+ applicationRelayerID = database.CalculateRelayerID(
+ sourceBlockchainID,
+ destinationBlockchainID,
+ originSenderAddress,
+ database.AllAllowedAddress,
+ )
+ if applicationRelayer, ok := mc.applicationRelayers[applicationRelayerID]; ok {
+ return applicationRelayer
+ }
+
+ // Check for a match on sourceBlockchainID and destinationBlockchainID, with any originSenderAddress
+ // and a specific destinationAddress.
+ applicationRelayerID = database.CalculateRelayerID(
+ sourceBlockchainID,
+ destinationBlockchainID,
+ database.AllAllowedAddress,
+ destinationAddress,
+ )
+ if applicationRelayer, ok := mc.applicationRelayers[applicationRelayerID]; ok {
+ return applicationRelayer
+ }
+
+ // Check for a match on sourceBlockchainID and destinationBlockchainID, with any originSenderAddress
+ // and any destinationAddress.
+ applicationRelayerID = database.CalculateRelayerID(
+ sourceBlockchainID,
+ destinationBlockchainID,
+ database.AllAllowedAddress,
+ database.AllAllowedAddress,
+ )
+ if applicationRelayer, ok := mc.applicationRelayers[applicationRelayerID]; ok {
+ return applicationRelayer
+ }
+ mc.logger.Debug(
+ "Application relayer not found. Skipping message relay.",
+ zap.String("blockchainID", sourceBlockchainID.String()),
+ zap.String("destinationBlockchainID", destinationBlockchainID.String()),
+ zap.String("originSenderAddress", originSenderAddress.String()),
+ zap.String("destinationAddress", destinationAddress.String()),
+ )
+ return nil
+}
+
+func (mc *MessageCoordinator) ProcessWarpMessage(warpMessage *relayerTypes.WarpMessageInfo) (common.Hash, error) {
+ appRelayer, handler, err := mc.getAppRelayerMessageHandler(warpMessage)
+ if err != nil {
+ mc.logger.Error(
+ "Failed to parse Warp message.",
+ zap.Error(err),
+ zap.String("warpMessageID", warpMessage.UnsignedMessage.ID().String()),
+ )
+ return common.Hash{}, err
+ }
+ if appRelayer == nil {
+ mc.logger.Error("Application relayer not found")
+ return common.Hash{}, errors.New("application relayer not found")
+ }
+
+ return appRelayer.ProcessMessage(handler)
+}
+
+func (mc *MessageCoordinator) ProcessMessageID(
+ blockchainID ids.ID,
+ messageID ids.ID,
+ blockNum *big.Int,
+) (common.Hash, error) {
+ ethClient, ok := mc.sourceClients[blockchainID]
+ if !ok {
+ mc.logger.Error(
+ "Source client not found",
+ zap.String("blockchainID", blockchainID.String()),
+ )
+ return common.Hash{}, fmt.Errorf("source client not set for blockchain: %s", blockchainID.String())
+ }
+
+ warpMessage, err := FetchWarpMessage(ethClient, messageID, blockNum)
+ if err != nil {
+ mc.logger.Error(
+ "Failed to fetch warp from blockchain",
+ zap.String("blockchainID", blockchainID.String()),
+ zap.Error(err),
+ )
+ return common.Hash{}, fmt.Errorf("could not fetch warp message from ID: %w", err)
+ }
+
+ return mc.ProcessWarpMessage(warpMessage)
+}
+
+// Meant to be ran asynchronously. Errors should be sent to errChan.
+func (mc *MessageCoordinator) ProcessBlock(
+ blockHeader *types.Header,
+ ethClient ethclient.Client,
+ errChan chan error,
+) {
+ // Parse the logs in the block, and group by application relayer
+ block, err := relayerTypes.NewWarpBlockInfo(blockHeader, ethClient)
+ if err != nil {
+ mc.logger.Error("Failed to create Warp block info", zap.Error(err))
+ errChan <- err
+ return
+ }
+
+ // Register each message in the block with the appropriate application relayer
+ messageHandlers := make(map[common.Hash][]messages.MessageHandler)
+ for _, warpLogInfo := range block.Messages {
+ appRelayer, handler, err := mc.getAppRelayerMessageHandler(warpLogInfo)
+ if err != nil {
+ mc.logger.Error(
+ "Failed to parse message",
+ zap.String("blockchainID", warpLogInfo.UnsignedMessage.SourceChainID.String()),
+ zap.String("protocolAddress", warpLogInfo.SourceAddress.String()),
+ zap.Error(err),
+ )
+ continue
+ }
+ if appRelayer == nil {
+ mc.logger.Debug("Application relayer not found. Skipping message relay")
+ continue
+ }
+ messageHandlers[appRelayer.relayerID.ID] = append(messageHandlers[appRelayer.relayerID.ID], handler)
+ }
+ // Initiate message relay of all registered messages
+ for _, appRelayer := range mc.applicationRelayers {
+ // Dispatch all messages in the block to the appropriate application relayer.
+ // An empty slice is still a valid argument to ProcessHeight; in this case the height is immediately committed.
+ handlers := messageHandlers[appRelayer.relayerID.ID]
+
+ go appRelayer.ProcessHeight(block.BlockNumber, handlers, errChan)
+ }
+}
+
+func FetchWarpMessage(
+ ethClient ethclient.Client,
+ warpID ids.ID,
+ blockNum *big.Int,
+) (*relayerTypes.WarpMessageInfo, error) {
+ logs, err := ethClient.FilterLogs(context.Background(), interfaces.FilterQuery{
+ Topics: [][]common.Hash{{relayerTypes.WarpPrecompileLogFilter}, nil, {common.Hash(warpID)}},
+ Addresses: []common.Address{warp.ContractAddress},
+ FromBlock: blockNum,
+ ToBlock: blockNum,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("could not fetch logs: %w", err)
+ }
+ if len(logs) != 1 {
+ return nil, fmt.Errorf("found more than 1 log: %d", len(logs))
+ }
+
+ return relayerTypes.NewWarpMessageInfo(logs[0])
+}
diff --git a/tests/allowed_addresses.go b/tests/allowed_addresses.go
index 4f5f2ca8..dbaa809a 100644
--- a/tests/allowed_addresses.go
+++ b/tests/allowed_addresses.go
@@ -28,12 +28,14 @@ const relayerCfgFname4 = "relayer-config-4.json"
const numKeys = 4
// Tests allowed source and destination address functionality.
-// First, relays messages using distinct relayer instances that all write to the same database. The instances are configured to:
+// First, relays messages using distinct relayer instances that all write to the same database.
+// The instances are configured to:
// - Deliver from any source address to any destination address
// - Deliver from a specific source address to any destination address
// - Deliver from any source address to a specific destination address
// - Deliver from a specific source address to a specific destination address
-// Then, checks that each relayer instance is able to properly catch up on missed messages that match its particular configuration
+// Then, checks that each relayer instance is able to properly catch up on missed messages that
+// match its particular configuration.
func AllowedAddresses(network interfaces.LocalNetwork) {
subnetAInfo := network.GetPrimaryNetworkInfo()
subnetBInfo, _ := utils.GetTwoSubnets(network)
diff --git a/tests/basic_relay.go b/tests/basic_relay.go
index 1c2a52df..713aef5c 100644
--- a/tests/basic_relay.go
+++ b/tests/basic_relay.go
@@ -52,6 +52,8 @@ func BasicRelay(network interfaces.LocalNetwork) {
fundedAddress,
relayerKey,
)
+ // The config needs to be validated in order to be passed to database.GetConfigRelayerIDs
+ relayerConfig.Validate()
relayerConfigPath := testUtils.WriteRelayerConfig(relayerConfig, testUtils.DefaultRelayerCfgFname)
@@ -107,15 +109,31 @@ func BasicRelay(network interfaces.LocalNetwork) {
logging.JSON.ConsoleEncoder(),
),
)
- jsonDB, err := database.NewJSONFileStorage(logger, relayerConfig.StorageLocation, database.GetConfigRelayerIDs(&relayerConfig))
+ jsonDB, err := database.NewJSONFileStorage(
+ logger,
+ relayerConfig.StorageLocation,
+ database.GetConfigRelayerIDs(&relayerConfig),
+ )
Expect(err).Should(BeNil())
// Create relayer keys that allow all source and destination addresses
- relayerIDA := database.CalculateRelayerID(subnetAInfo.BlockchainID, subnetBInfo.BlockchainID, database.AllAllowedAddress, database.AllAllowedAddress)
- relayerIDB := database.CalculateRelayerID(subnetBInfo.BlockchainID, subnetAInfo.BlockchainID, database.AllAllowedAddress, database.AllAllowedAddress)
+ relayerIDA := database.CalculateRelayerID(
+ subnetAInfo.BlockchainID,
+ subnetBInfo.BlockchainID,
+ database.AllAllowedAddress,
+ database.AllAllowedAddress,
+ )
+ relayerIDB := database.CalculateRelayerID(
+ subnetBInfo.BlockchainID,
+ subnetAInfo.BlockchainID,
+ database.AllAllowedAddress,
+ database.AllAllowedAddress,
+ )
// Modify the JSON database to force the relayer to re-process old blocks
- jsonDB.Put(relayerIDA, database.LatestProcessedBlockKey, []byte("0"))
- jsonDB.Put(relayerIDB, database.LatestProcessedBlockKey, []byte("0"))
+ err = jsonDB.Put(relayerIDA, database.LatestProcessedBlockKey, []byte("0"))
+ Expect(err).Should(BeNil())
+ err = jsonDB.Put(relayerIDB, database.LatestProcessedBlockKey, []byte("0"))
+ Expect(err).Should(BeNil())
// Subscribe to the destination chain
newHeadsB := make(chan *types.Header, 10)
@@ -128,7 +146,8 @@ func BasicRelay(network interfaces.LocalNetwork) {
relayerCleanup = testUtils.BuildAndRunRelayerExecutable(ctx, relayerConfigPath)
defer relayerCleanup()
- // We should not receive a new block on subnet B, since the relayer should have seen the Teleporter message was already delivered
+ // We should not receive a new block on subnet B, since the relayer should have
+ // seen the Teleporter message was already delivered.
log.Info("Waiting for 10s to ensure no new block confirmations on destination chain")
Consistently(newHeadsB, 10*time.Second, 500*time.Millisecond).ShouldNot(Receive())
diff --git a/tests/batch_relay.go b/tests/batch_relay.go
index 808eb436..aebde3bb 100644
--- a/tests/batch_relay.go
+++ b/tests/batch_relay.go
@@ -119,7 +119,13 @@ func BatchRelay(network interfaces.LocalNetwork) {
}
currWait++
if currWait == maxWait {
- Expect(false).Should(BeTrue(), fmt.Sprintf("did not receive all sent messages in time. received %d/%d", numMessages-sentMessages.Len(), numMessages))
+ Expect(false).Should(BeTrue(),
+ fmt.Sprintf(
+ "did not receive all sent messages in time. received %d/%d",
+ numMessages-sentMessages.Len(),
+ numMessages,
+ ),
+ )
}
time.Sleep(1 * time.Second)
}
diff --git a/tests/e2e_test.go b/tests/e2e_test.go
index 8c1bd68f..c26e78fa 100644
--- a/tests/e2e_test.go
+++ b/tests/e2e_test.go
@@ -46,10 +46,18 @@ var _ = ginkgo.BeforeSuite(func() {
localNetworkInstance = local.NewLocalNetwork(warpGenesisFile)
// Generate the Teleporter deployment values
- teleporterContractAddress := common.HexToAddress(testUtils.ReadHexTextFile("./tests/utils/UniversalTeleporterMessengerContractAddress.txt"))
- teleporterDeployerAddress := common.HexToAddress(testUtils.ReadHexTextFile("./tests/utils/UniversalTeleporterDeployerAddress.txt"))
- teleporterDeployerTransactionStr := testUtils.ReadHexTextFile("./tests/utils/UniversalTeleporterDeployerTransaction.txt")
- teleporterDeployerTransaction, err := hex.DecodeString(utils.SanitizeHexString(teleporterDeployerTransactionStr))
+ teleporterContractAddress := common.HexToAddress(
+ testUtils.ReadHexTextFile("./tests/utils/UniversalTeleporterMessengerContractAddress.txt"),
+ )
+ teleporterDeployerAddress := common.HexToAddress(
+ testUtils.ReadHexTextFile("./tests/utils/UniversalTeleporterDeployerAddress.txt"),
+ )
+ teleporterDeployerTransactionStr := testUtils.ReadHexTextFile(
+ "./tests/utils/UniversalTeleporterDeployerTransaction.txt",
+ )
+ teleporterDeployerTransaction, err := hex.DecodeString(
+ utils.SanitizeHexString(teleporterDeployerTransactionStr),
+ )
Expect(err).Should(BeNil())
_, fundedKey := localNetworkInstance.GetFundedAccountInfo()
@@ -61,7 +69,10 @@ var _ = ginkgo.BeforeSuite(func() {
true,
)
log.Info("Deployed Teleporter contracts")
- localNetworkInstance.DeployTeleporterRegistryContracts(teleporterContractAddress, fundedKey)
+ localNetworkInstance.DeployTeleporterRegistryContracts(
+ teleporterContractAddress,
+ fundedKey,
+ )
var ctx context.Context
ctx, cancelDecider = context.WithCancel(context.Background())
@@ -96,9 +107,6 @@ var _ = ginkgo.Describe("[AWM Relayer Integration Tests", func() {
ginkgo.It("Basic Relay", func() {
BasicRelay(localNetworkInstance)
})
- ginkgo.It("Teleporter Registry", func() {
- TeleporterRegistry(localNetworkInstance)
- })
ginkgo.It("Shared Database", func() {
SharedDatabaseAccess(localNetworkInstance)
})
@@ -108,6 +116,9 @@ var _ = ginkgo.Describe("[AWM Relayer Integration Tests", func() {
ginkgo.It("Batch Message", func() {
BatchRelay(localNetworkInstance)
})
+ ginkgo.It("Relay Message API", func() {
+ RelayMessageAPI(localNetworkInstance)
+ })
ginkgo.It("Warp API", func() {
WarpAPIRelay(localNetworkInstance)
})
diff --git a/tests/manual_message.go b/tests/manual_message.go
index 9a487d98..400eb5a0 100644
--- a/tests/manual_message.go
+++ b/tests/manual_message.go
@@ -4,35 +4,45 @@
package tests
import (
+ "bytes"
"context"
- "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "math/big"
+ "net/http"
"time"
- avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp"
- "github.com/ava-labs/awm-relayer/config"
+ runner_sdk "github.com/ava-labs/avalanche-network-runner/client"
+ "github.com/ava-labs/awm-relayer/api"
+ offchainregistry "github.com/ava-labs/awm-relayer/messages/off-chain-registry"
testUtils "github.com/ava-labs/awm-relayer/tests/utils"
"github.com/ava-labs/subnet-evm/accounts/abi/bind"
- "github.com/ava-labs/subnet-evm/core/types"
- subnetEvmInterfaces "github.com/ava-labs/subnet-evm/interfaces"
- "github.com/ava-labs/subnet-evm/precompile/contracts/warp"
"github.com/ava-labs/teleporter/tests/interfaces"
- "github.com/ava-labs/teleporter/tests/utils"
+ teleporterTestUtils "github.com/ava-labs/teleporter/tests/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
-
. "github.com/onsi/gomega"
)
-// This tests relaying a message manually provided in the relayer config
+// Tests relayer support for off-chain Teleporter Registry updates
+// - Configures the relayer to send an off-chain message to the Teleporter Registry
+// - Verifies that the Teleporter Registry is updated
func ManualMessage(network interfaces.LocalNetwork) {
- subnetAInfo := network.GetPrimaryNetworkInfo()
- subnetBInfo, _ := utils.GetTwoSubnets(network)
+ cChainInfo := network.GetPrimaryNetworkInfo()
+ subnetAInfo, subnetBInfo := teleporterTestUtils.GetTwoSubnets(network)
fundedAddress, fundedKey := network.GetFundedAccountInfo()
teleporterContractAddress := network.GetTeleporterContractAddress()
err := testUtils.ClearRelayerStorage()
Expect(err).Should(BeNil())
+ //
+ // Get the current Teleporter Registry version
+ //
+ currentVersion, err := cChainInfo.TeleporterRegistry.LatestVersion(&bind.CallOpts{})
+ Expect(err).Should(BeNil())
+ expectedNewVersion := currentVersion.Add(currentVersion, big.NewInt(1))
+
//
// Fund the relayer address on all subnets
//
@@ -41,89 +51,101 @@ func ManualMessage(network interfaces.LocalNetwork) {
log.Info("Funding relayer address on all subnets")
relayerKey, err := crypto.GenerateKey()
Expect(err).Should(BeNil())
- testUtils.FundRelayers(ctx, []interfaces.SubnetTestInfo{subnetAInfo, subnetBInfo}, fundedKey, relayerKey)
+ testUtils.FundRelayers(ctx, []interfaces.SubnetTestInfo{cChainInfo}, fundedKey, relayerKey)
+
+ //
+ // Define the off-chain Warp message
+ //
+ log.Info("Creating off-chain Warp message")
+ newProtocolAddress := common.HexToAddress("0x0123456789abcdef0123456789abcdef01234567")
+ networkID := network.GetNetworkID()
//
- // Send two Teleporter message on Subnet A, before the relayer is running
+ // Set up the nodes to accept the off-chain message
//
+ // Create chain config file with off chain message for each chain
+ unsignedMessage, warpEnabledChainConfigC := teleporterTestUtils.InitOffChainMessageChainConfig(
+ networkID,
+ cChainInfo,
+ newProtocolAddress,
+ 2,
+ )
+ _, warpEnabledChainConfigA := teleporterTestUtils.InitOffChainMessageChainConfig(
+ networkID,
+ subnetAInfo,
+ newProtocolAddress,
+ 2,
+ )
+ _, warpEnabledChainConfigB := teleporterTestUtils.InitOffChainMessageChainConfig(
+ networkID,
+ subnetBInfo,
+ newProtocolAddress,
+ 2,
+ )
- log.Info("Sending two teleporter messages on subnet A")
- // This message will be delivered by the relayer
- receipt1, _, id1 := testUtils.SendBasicTeleporterMessage(ctx, subnetAInfo, subnetBInfo, fundedKey, fundedAddress)
- msg1 := getWarpMessageFromLog(ctx, receipt1, subnetAInfo)
+ // Create chain config with off chain messages
+ chainConfigs := make(map[string]string)
+ teleporterTestUtils.SetChainConfig(chainConfigs, cChainInfo, warpEnabledChainConfigC)
+ teleporterTestUtils.SetChainConfig(chainConfigs, subnetBInfo, warpEnabledChainConfigB)
+ teleporterTestUtils.SetChainConfig(chainConfigs, subnetAInfo, warpEnabledChainConfigA)
- // This message will not be delivered by the relayer
- _, _, id2 := testUtils.SendBasicTeleporterMessage(ctx, subnetAInfo, subnetBInfo, fundedKey, fundedAddress)
+ // Restart nodes with new chain config
+ nodeNames := network.GetAllNodeNames()
+ log.Info("Restarting nodes with new chain config")
+ network.RestartNodes(ctx, nodeNames, runner_sdk.WithChainConfigs(chainConfigs))
+ // Refresh the subnet info to get the new clients
+ cChainInfo = network.GetPrimaryNetworkInfo()
//
- // Set up relayer config to deliver one of the two previously sent messages
+ // Set up relayer config
//
relayerConfig := testUtils.CreateDefaultRelayerConfig(
- []interfaces.SubnetTestInfo{subnetAInfo, subnetBInfo},
- []interfaces.SubnetTestInfo{subnetAInfo, subnetBInfo},
+ []interfaces.SubnetTestInfo{cChainInfo},
+ []interfaces.SubnetTestInfo{cChainInfo},
teleporterContractAddress,
fundedAddress,
relayerKey,
)
- relayerConfig.ManualWarpMessages = []*config.ManualWarpMessage{
- {
- UnsignedMessageBytes: hex.EncodeToString(msg1.Bytes()),
- SourceBlockchainID: subnetAInfo.BlockchainID.String(),
- DestinationBlockchainID: subnetBInfo.BlockchainID.String(),
- SourceAddress: teleporterContractAddress.Hex(),
- DestinationAddress: teleporterContractAddress.Hex(),
- },
- }
-
relayerConfigPath := testUtils.WriteRelayerConfig(relayerConfig, testUtils.DefaultRelayerCfgFname)
- //
- // Run the Relayer. On startup, we should deliver the message provided in the config
- //
-
- // Subscribe to the destination chain
- newHeadsB := make(chan *types.Header, 10)
- sub, err := subnetBInfo.WSClient.SubscribeNewHead(ctx, newHeadsB)
- Expect(err).Should(BeNil())
- defer sub.Unsubscribe()
-
log.Info("Starting the relayer")
relayerCleanup := testUtils.BuildAndRunRelayerExecutable(ctx, relayerConfigPath)
defer relayerCleanup()
- log.Info("Waiting for a new block confirmation on subnet B")
- <-newHeadsB
- delivered1, err := subnetBInfo.TeleporterMessenger.MessageReceived(
- &bind.CallOpts{}, id1,
- )
- Expect(err).Should(BeNil())
- Expect(delivered1).Should(BeTrue())
+ // Sleep for some time to make sure relayer has started up and subscribed.
+ log.Info("Waiting for the relayer to start up")
+ time.Sleep(15 * time.Second)
- log.Info("Waiting for 10s to ensure no new block confirmations on destination chain")
- Consistently(newHeadsB, 10*time.Second, 500*time.Millisecond).ShouldNot(Receive())
+ reqBody := api.ManualWarpMessageRequest{
+ UnsignedMessageBytes: unsignedMessage.Bytes(),
+ SourceAddress: offchainregistry.OffChainRegistrySourceAddress.Hex(),
+ }
- delivered2, err := subnetBInfo.TeleporterMessenger.MessageReceived(
- &bind.CallOpts{}, id2,
- )
- Expect(err).Should(BeNil())
- Expect(delivered2).Should(BeFalse())
-}
+ client := http.Client{
+ Timeout: 30 * time.Second,
+ }
-func getWarpMessageFromLog(ctx context.Context, receipt *types.Receipt, source interfaces.SubnetTestInfo) *avalancheWarp.UnsignedMessage {
- log.Info("Fetching relevant warp logs from the newly produced block")
- logs, err := source.RPCClient.FilterLogs(ctx, subnetEvmInterfaces.FilterQuery{
- BlockHash: &receipt.BlockHash,
- Addresses: []common.Address{warp.Module.Address},
- })
- Expect(err).Should(BeNil())
- Expect(len(logs)).Should(Equal(1))
+ requestURL := fmt.Sprintf("http://localhost:%d%s", relayerConfig.APIPort, api.RelayMessageAPIPath)
- // Check for relevant warp log from subscription and ensure that it matches
- // the log extracted from the last block.
- txLog := logs[0]
- log.Info("Parsing logData as unsigned warp message")
- unsignedMsg, err := warp.UnpackSendWarpEventDataToMessage(txLog.Data)
- Expect(err).Should(BeNil())
+ // Send request to API
+ {
+ b, err := json.Marshal(reqBody)
+ Expect(err).Should(BeNil())
+ bodyReader := bytes.NewReader(b)
+
+ req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader)
+ Expect(err).Should(BeNil())
+ req.Header.Set("Content-Type", "application/json")
- return unsignedMsg
+ res, err := client.Do(req)
+ Expect(err).Should(BeNil())
+ Expect(res.Status).Should(Equal("200 OK"))
+
+ // Wait for all nodes to see new transaction
+ time.Sleep(1 * time.Second)
+
+ newVersion, err := cChainInfo.TeleporterRegistry.LatestVersion(&bind.CallOpts{})
+ Expect(err).Should(BeNil())
+ Expect(newVersion.Uint64()).Should(Equal(expectedNewVersion.Uint64()))
+ }
}
diff --git a/tests/relay_message_api.go b/tests/relay_message_api.go
new file mode 100644
index 00000000..b737e4c7
--- /dev/null
+++ b/tests/relay_message_api.go
@@ -0,0 +1,171 @@
+// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package tests
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "time"
+
+ "github.com/ava-labs/avalanchego/ids"
+ avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp"
+ "github.com/ava-labs/awm-relayer/api"
+ testUtils "github.com/ava-labs/awm-relayer/tests/utils"
+ "github.com/ava-labs/subnet-evm/core/types"
+ subnetEvmInterfaces "github.com/ava-labs/subnet-evm/interfaces"
+ "github.com/ava-labs/subnet-evm/precompile/contracts/warp"
+ "github.com/ava-labs/teleporter/tests/interfaces"
+ "github.com/ava-labs/teleporter/tests/utils"
+ teleporterTestUtils "github.com/ava-labs/teleporter/tests/utils"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/log"
+ . "github.com/onsi/gomega"
+)
+
+func RelayMessageAPI(network interfaces.LocalNetwork) {
+ ctx := context.Background()
+ subnetAInfo := network.GetPrimaryNetworkInfo()
+ subnetBInfo, _ := utils.GetTwoSubnets(network)
+ fundedAddress, fundedKey := network.GetFundedAccountInfo()
+ teleporterContractAddress := network.GetTeleporterContractAddress()
+ err := testUtils.ClearRelayerStorage()
+ Expect(err).Should(BeNil())
+
+ log.Info("Funding relayer address on all subnets")
+ relayerKey, err := crypto.GenerateKey()
+ Expect(err).Should(BeNil())
+ testUtils.FundRelayers(ctx, []interfaces.SubnetTestInfo{subnetAInfo, subnetBInfo}, fundedKey, relayerKey)
+
+ log.Info("Sending teleporter message")
+ receipt, _, teleporterMessageID := testUtils.SendBasicTeleporterMessage(
+ ctx,
+ subnetAInfo,
+ subnetBInfo,
+ fundedKey,
+ fundedAddress,
+ )
+ warpMessage := getWarpMessageFromLog(ctx, receipt, subnetAInfo)
+
+ // Set up relayer config
+ relayerConfig := testUtils.CreateDefaultRelayerConfig(
+ []interfaces.SubnetTestInfo{subnetAInfo, subnetBInfo},
+ []interfaces.SubnetTestInfo{subnetAInfo, subnetBInfo},
+ teleporterContractAddress,
+ fundedAddress,
+ relayerKey,
+ )
+ // Don't process missed blocks, so we can manually relay
+ relayerConfig.ProcessMissedBlocks = false
+
+ relayerConfigPath := testUtils.WriteRelayerConfig(relayerConfig, testUtils.DefaultRelayerCfgFname)
+
+ log.Info("Starting the relayer")
+ relayerCleanup := testUtils.BuildAndRunRelayerExecutable(ctx, relayerConfigPath)
+ defer relayerCleanup()
+
+ // Sleep for some time to make sure relayer has started up and subscribed.
+ log.Info("Waiting for the relayer to start up")
+ time.Sleep(15 * time.Second)
+
+ reqBody := api.RelayMessageRequest{
+ BlockchainID: subnetAInfo.BlockchainID.String(),
+ MessageID: warpMessage.ID().String(),
+ BlockNum: receipt.BlockNumber.Uint64(),
+ }
+
+ client := http.Client{
+ Timeout: 30 * time.Second,
+ }
+
+ requestURL := fmt.Sprintf("http://localhost:%d%s", relayerConfig.APIPort, api.RelayAPIPath)
+
+ // Send request to API
+ {
+ b, err := json.Marshal(reqBody)
+ Expect(err).Should(BeNil())
+ bodyReader := bytes.NewReader(b)
+
+ req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader)
+ Expect(err).Should(BeNil())
+ req.Header.Set("Content-Type", "application/json")
+
+ res, err := client.Do(req)
+ Expect(err).Should(BeNil())
+ Expect(res.Status).Should(Equal("200 OK"))
+
+ defer res.Body.Close()
+ body, err := io.ReadAll(res.Body)
+ Expect(err).Should(BeNil())
+
+ var response api.RelayMessageResponse
+ err = json.Unmarshal(body, &response)
+ Expect(err).Should(BeNil())
+
+ receipt, err := subnetBInfo.RPCClient.TransactionReceipt(ctx, common.HexToHash(response.TransactionHash))
+ Expect(err).Should(BeNil())
+ receiveEvent, err := teleporterTestUtils.GetEventFromLogs(
+ receipt.Logs,
+ subnetBInfo.TeleporterMessenger.ParseReceiveCrossChainMessage,
+ )
+ Expect(err).Should(BeNil())
+ Expect(ids.ID(receiveEvent.MessageID)).Should(Equal(teleporterMessageID))
+ }
+
+ // Send the same request to ensure the correct response.
+ {
+ b, err := json.Marshal(reqBody)
+ Expect(err).Should(BeNil())
+ bodyReader := bytes.NewReader(b)
+
+ req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader)
+ Expect(err).Should(BeNil())
+ req.Header.Set("Content-Type", "application/json")
+
+ res, err := client.Do(req)
+ Expect(err).Should(BeNil())
+ Expect(res.Status).Should(Equal("200 OK"))
+
+ defer res.Body.Close()
+ body, err := io.ReadAll(res.Body)
+ Expect(err).Should(BeNil())
+
+ var response api.RelayMessageResponse
+ err = json.Unmarshal(body, &response)
+ Expect(err).Should(BeNil())
+ Expect(response.TransactionHash).Should(Equal(
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ ))
+ }
+
+ // Cancel the command and stop the relayer
+ relayerCleanup()
+}
+
+func getWarpMessageFromLog(
+ ctx context.Context,
+ receipt *types.Receipt,
+ source interfaces.SubnetTestInfo,
+) *avalancheWarp.UnsignedMessage {
+ log.Info("Fetching relevant warp logs from the newly produced block")
+ logs, err := source.RPCClient.FilterLogs(ctx, subnetEvmInterfaces.FilterQuery{
+ BlockHash: &receipt.BlockHash,
+ Addresses: []common.Address{warp.Module.Address},
+ })
+ Expect(err).Should(BeNil())
+ Expect(len(logs)).Should(Equal(1))
+
+ // Check for relevant warp log from subscription and ensure that it matches
+ // the log extracted from the last block.
+ txLog := logs[0]
+ log.Info("Parsing logData as unsigned warp message")
+ unsignedMsg, err := warp.UnpackSendWarpEventDataToMessage(txLog.Data)
+ Expect(err).Should(BeNil())
+
+ return unsignedMsg
+}
diff --git a/tests/teleporter_registry.go b/tests/teleporter_registry.go
deleted file mode 100644
index 13c4698e..00000000
--- a/tests/teleporter_registry.go
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
-// See the file LICENSE for licensing terms.
-
-package tests
-
-import (
- "context"
- "encoding/hex"
- "math/big"
-
- runner_sdk "github.com/ava-labs/avalanche-network-runner/client"
- "github.com/ava-labs/awm-relayer/config"
- offchainregistry "github.com/ava-labs/awm-relayer/messages/off-chain-registry"
- testUtils "github.com/ava-labs/awm-relayer/tests/utils"
- "github.com/ava-labs/subnet-evm/accounts/abi/bind"
- "github.com/ava-labs/subnet-evm/core/types"
- "github.com/ava-labs/teleporter/tests/interfaces"
- teleporterTestUtils "github.com/ava-labs/teleporter/tests/utils"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/log"
- . "github.com/onsi/gomega"
-)
-
-// Tests relayer support for off-chain Teleporter Registry updates
-// - Configures the relayer to send an off-chain message to the Teleporter Registry
-// - Verifies that the Teleporter Registry is updated
-func TeleporterRegistry(network interfaces.LocalNetwork) {
- cChainInfo := network.GetPrimaryNetworkInfo()
- subnetAInfo, subnetBInfo := teleporterTestUtils.GetTwoSubnets(network)
- fundedAddress, fundedKey := network.GetFundedAccountInfo()
- teleporterContractAddress := network.GetTeleporterContractAddress()
- err := testUtils.ClearRelayerStorage()
- Expect(err).Should(BeNil())
-
- //
- // Get the current Teleporter Registry version
- //
- currentVersion, err := cChainInfo.TeleporterRegistry.LatestVersion(&bind.CallOpts{})
- Expect(err).Should(BeNil())
- expectedNewVersion := currentVersion.Add(currentVersion, big.NewInt(1))
-
- //
- // Fund the relayer address on all subnets
- //
- ctx := context.Background()
-
- log.Info("Funding relayer address on all subnets")
- relayerKey, err := crypto.GenerateKey()
- Expect(err).Should(BeNil())
- testUtils.FundRelayers(ctx, []interfaces.SubnetTestInfo{cChainInfo}, fundedKey, relayerKey)
-
- //
- // Define the off-chain Warp message
- //
- log.Info("Creating off-chain Warp message")
- newProtocolAddress := common.HexToAddress("0x0123456789abcdef0123456789abcdef01234567")
- networkID := network.GetNetworkID()
-
- //
- // Set up the nodes to accept the off-chain message
- //
- // Create chain config file with off chain message for each chain
- unsignedMessage, warpEnabledChainConfigC := teleporterTestUtils.InitOffChainMessageChainConfig(networkID, cChainInfo, newProtocolAddress, 2)
- _, warpEnabledChainConfigA := teleporterTestUtils.InitOffChainMessageChainConfig(networkID, subnetAInfo, newProtocolAddress, 2)
- _, warpEnabledChainConfigB := teleporterTestUtils.InitOffChainMessageChainConfig(networkID, subnetBInfo, newProtocolAddress, 2)
-
- // Create chain config with off chain messages
- chainConfigs := make(map[string]string)
- teleporterTestUtils.SetChainConfig(chainConfigs, cChainInfo, warpEnabledChainConfigC)
- teleporterTestUtils.SetChainConfig(chainConfigs, subnetBInfo, warpEnabledChainConfigB)
- teleporterTestUtils.SetChainConfig(chainConfigs, subnetAInfo, warpEnabledChainConfigA)
-
- // Restart nodes with new chain config
- nodeNames := network.GetAllNodeNames()
- log.Info("Restarting nodes with new chain config")
- network.RestartNodes(ctx, nodeNames, runner_sdk.WithChainConfigs(chainConfigs))
- // Refresh the subnet info to get the new clients
- cChainInfo = network.GetPrimaryNetworkInfo()
-
- //
- // Set up relayer config
- //
- relayerConfig := testUtils.CreateDefaultRelayerConfig(
- []interfaces.SubnetTestInfo{cChainInfo},
- []interfaces.SubnetTestInfo{cChainInfo},
- teleporterContractAddress,
- fundedAddress,
- relayerKey,
- )
- relayerConfig.ManualWarpMessages = []*config.ManualWarpMessage{
- {
- UnsignedMessageBytes: hex.EncodeToString(unsignedMessage.Bytes()),
- SourceBlockchainID: cChainInfo.BlockchainID.String(),
- DestinationBlockchainID: cChainInfo.BlockchainID.String(),
- SourceAddress: offchainregistry.OffChainRegistrySourceAddress.Hex(),
- DestinationAddress: cChainInfo.TeleporterRegistryAddress.Hex(),
- },
- }
- relayerConfigPath := testUtils.WriteRelayerConfig(relayerConfig, testUtils.DefaultRelayerCfgFname)
- //
- // Run the Relayer. On startup, we should deliver the message provided in the config
- //
-
- // Subscribe to the destination chain
- newHeadsC := make(chan *types.Header, 10)
- sub, err := cChainInfo.WSClient.SubscribeNewHead(ctx, newHeadsC)
- Expect(err).Should(BeNil())
- defer sub.Unsubscribe()
-
- log.Info("Starting the relayer")
- relayerCleanup := testUtils.BuildAndRunRelayerExecutable(ctx, relayerConfigPath)
- defer relayerCleanup()
-
- log.Info("Waiting for a new block confirmation on the C-Chain")
- <-newHeadsC
-
- log.Info("Verifying that the Teleporter Registry was updated")
- newVersion, err := cChainInfo.TeleporterRegistry.LatestVersion(&bind.CallOpts{})
- Expect(err).Should(BeNil())
- Expect(newVersion.Cmp(expectedNewVersion)).Should(Equal(0))
-}
diff --git a/tests/utils/utils.go b/tests/utils/utils.go
index b66f4fd2..5f19b4b6 100644
--- a/tests/utils/utils.go
+++ b/tests/utils/utils.go
@@ -209,6 +209,7 @@ func CreateDefaultRelayerConfig(
DestinationBlockchains: destinations,
DeciderHost: "localhost",
DeciderPort: &deciderPort,
+ APIPort: 8080,
}
}
@@ -301,7 +302,10 @@ func SendBasicTeleporterMessage(
input,
fundedKey,
)
- sendEvent, err := teleporterTestUtils.GetEventFromLogs(receipt.Logs, source.TeleporterMessenger.ParseSendCrossChainMessage)
+ sendEvent, err := teleporterTestUtils.GetEventFromLogs(
+ receipt.Logs,
+ source.TeleporterMessenger.ParseSendCrossChainMessage,
+ )
Expect(err).Should(BeNil())
return receipt, sendEvent.Message, teleporterMessageID
@@ -365,7 +369,10 @@ func RelayBasicMessage(
Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful))
// Check that the transaction emits ReceiveCrossChainMessage
- receiveEvent, err := teleporterTestUtils.GetEventFromLogs(receipt.Logs, destination.TeleporterMessenger.ParseReceiveCrossChainMessage)
+ receiveEvent, err := teleporterTestUtils.GetEventFromLogs(
+ receipt.Logs,
+ destination.TeleporterMessenger.ParseReceiveCrossChainMessage,
+ )
Expect(err).Should(BeNil())
Expect(receiveEvent.SourceBlockchainID[:]).Should(Equal(source.BlockchainID[:]))
Expect(receiveEvent.MessageID[:]).Should(Equal(teleporterMessageID[:]))
@@ -384,7 +391,12 @@ func RelayBasicMessage(
receivedTeleporterMessage, err := teleportermessenger.UnpackTeleporterMessage(addressedPayload.Payload)
Expect(err).Should(BeNil())
- receivedMessageID, err := teleporterUtils.CalculateMessageID(teleporterContractAddress, source.BlockchainID, destination.BlockchainID, teleporterMessage.MessageNonce)
+ receivedMessageID, err := teleporterUtils.CalculateMessageID(
+ teleporterContractAddress,
+ source.BlockchainID,
+ destination.BlockchainID,
+ teleporterMessage.MessageNonce,
+ )
Expect(err).Should(BeNil())
Expect(receivedMessageID).Should(Equal(teleporterMessageID))
Expect(receivedTeleporterMessage.OriginSenderAddress).Should(Equal(teleporterMessage.OriginSenderAddress))
diff --git a/types/types.go b/types/types.go
index 735115d2..b71a6805 100644
--- a/types/types.go
+++ b/types/types.go
@@ -16,12 +16,13 @@ import (
"github.com/ethereum/go-ethereum/common"
)
-var WarpPrecompileLogFilter = warp.WarpABI.Events["SendWarpMessage"].ID
-var ErrInvalidLog = errors.New("invalid warp message log")
+var (
+ WarpPrecompileLogFilter = warp.WarpABI.Events["SendWarpMessage"].ID
+ ErrInvalidLog = errors.New("invalid warp message log")
+)
// WarpBlockInfo describes the block height and logs needed to process Warp messages.
-// WarpBlockInfo instances are populated by the subscriber, and forwared to the
-// Listener to process
+// WarpBlockInfo instances are populated by the subscriber, and forwarded to the Listener to process.
type WarpBlockInfo struct {
BlockNumber uint64
Messages []*WarpMessageInfo
diff --git a/utils/client_utils.go b/utils/client_utils.go
index 051a5705..410fa0b9 100644
--- a/utils/client_utils.go
+++ b/utils/client_utils.go
@@ -16,7 +16,11 @@ import (
var ErrInvalidEndpoint = errors.New("invalid rpc endpoint")
// NewEthClientWithConfig returns an ethclient.Client with the internal RPC client configured with the provided options.
-func NewEthClientWithConfig(ctx context.Context, baseURL string, httpHeaders, queryParams map[string]string) (ethclient.Client, error) {
+func NewEthClientWithConfig(
+ ctx context.Context,
+ baseURL string, httpHeaders,
+ queryParams map[string]string,
+) (ethclient.Client, error) {
client, err := DialWithConfig(ctx, baseURL, httpHeaders, queryParams)
if err != nil {
return nil, err
@@ -25,7 +29,12 @@ func NewEthClientWithConfig(ctx context.Context, baseURL string, httpHeaders, qu
}
// DialWithConfig dials the provided baseURL with the provided httpHeaders and queryParams
-func DialWithConfig(ctx context.Context, baseURL string, httpHeaders, queryParams map[string]string) (*rpc.Client, error) {
+func DialWithConfig(
+ ctx context.Context,
+ baseURL string,
+ httpHeaders map[string]string,
+ queryParams map[string]string,
+) (*rpc.Client, error) {
url, err := addQueryParams(baseURL, queryParams)
if err != nil {
return nil, err
diff --git a/utils/utils.go b/utils/utils.go
index 7648c0ef..36b5cd2f 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -12,6 +12,7 @@ import (
"strings"
"time"
+ "github.com/ava-labs/avalanchego/ids"
"github.com/ethereum/go-ethereum/common"
)
@@ -33,8 +34,14 @@ const (
// AWM Utils
//
-// CheckStakeWeightExceedsThreshold returns true if the accumulated signature weight is at least [quorumNum]/[quorumDen] of [totalWeight].
-func CheckStakeWeightExceedsThreshold(accumulatedSignatureWeight *big.Int, totalWeight uint64, quorumNumerator uint64, quorumDenominator uint64) bool {
+// CheckStakeWeightExceedsThreshold returns true if the accumulated signature weight is at
+// least [quorumNum]/[quorumDen] of [totalWeight].
+func CheckStakeWeightExceedsThreshold(
+ accumulatedSignatureWeight *big.Int,
+ totalWeight uint64,
+ quorumNumerator uint64,
+ quorumDenominator uint64,
+) bool {
if accumulatedSignatureWeight == nil {
return false
}
@@ -117,3 +124,16 @@ func StripFromString(input, substring string) string {
return strippedString
}
+
+// Converts a '0x'-prefixed hex string or cb58-encoded string to an ID.
+// Input length validation is handled by the ids package.
+func HexOrCB58ToID(s string) (ids.ID, error) {
+ if strings.HasPrefix(s, "0x") {
+ bytes, err := hex.DecodeString(SanitizeHexString(s))
+ if err != nil {
+ return ids.ID{}, err
+ }
+ return ids.ToID(bytes)
+ }
+ return ids.FromString(s)
+}
diff --git a/utils/utils_test.go b/utils/utils_test.go
index 2349915b..0ace0515 100644
--- a/utils/utils_test.go
+++ b/utils/utils_test.go
@@ -10,6 +10,44 @@ import (
"github.com/stretchr/testify/require"
)
+func TestHexOrCB58ToID(t *testing.T) {
+ testCases := []struct {
+ name string
+ encoding string
+ expectedResult string
+ errorExpected bool
+ }{
+ {
+ name: "hex conversion",
+ encoding: "0x7fc93d85c6d62c5b2ac0b519c87010ea5294012d1e407030d6acd0021cac10d5",
+ expectedResult: "yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp",
+ errorExpected: false,
+ },
+ {
+ name: "cb58 conversion",
+ encoding: "yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp",
+ expectedResult: "yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp",
+ errorExpected: false,
+ },
+ {
+ name: "non-prefixed hex",
+ encoding: "7fc93d85c6d62c5b2ac0b519c87010ea5294012d1e407030d6acd0021cac10d5",
+ errorExpected: true,
+ },
+ }
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ actualResult, err := HexOrCB58ToID(testCase.encoding)
+ if testCase.errorExpected {
+ require.Error(t, err)
+ } else {
+ require.NoError(t, err)
+ require.Equal(t, testCase.expectedResult, actualResult.String())
+ }
+ })
+ }
+}
+
func TestSanitizeHexString(t *testing.T) {
testCases := []struct {
name string
@@ -87,7 +125,12 @@ func TestCheckStakeWeightExceedsThreshold(t *testing.T) {
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
- actualResult := CheckStakeWeightExceedsThreshold(new(big.Int).SetUint64(testCase.accumulatedSignatureWeight), testCase.totalWeight, testCase.quorumNumerator, testCase.quorumDenominator)
+ actualResult := CheckStakeWeightExceedsThreshold(
+ new(big.Int).SetUint64(testCase.accumulatedSignatureWeight),
+ testCase.totalWeight,
+ testCase.quorumNumerator,
+ testCase.quorumDenominator,
+ )
require.Equal(t, testCase.expectedResult, actualResult)
})
}
diff --git a/vms/destination_client.go b/vms/destination_client.go
index e417a458..e1fbda02 100644
--- a/vms/destination_client.go
+++ b/vms/destination_client.go
@@ -17,10 +17,11 @@ import (
"go.uber.org/zap"
)
-// DestinationClient is the interface for the destination chain client. Methods that interact with the destination chain
-// should generally be implemented in a thread safe way, as they will be called concurrently by the application relayers.
+// DestinationClient is the interface for the destination chain client. Methods that interact with
+// the destination chain should generally be implemented in a thread safe way, as they will be called
+// concurrently by the application relayers.
type DestinationClient interface {
- // SendTx contructs the transaction from warp primitives, and sends to the configured destination chain endpoint.
+ // SendTx constructs the transaction from warp primitives, and sends to the configured destination chain endpoint.
// Returns the hash of the sent transaction.
// TODO: Make generic for any VM.
SendTx(signedMessage *warp.Message, toAddress string, gasLimit uint64, callData []byte) (common.Hash, error)
@@ -45,14 +46,17 @@ func NewDestinationClient(logger logging.Logger, subnetInfo *config.DestinationB
}
// CreateDestinationClients creates destination clients for all subnets configured as destinations
-func CreateDestinationClients(logger logging.Logger, relayerConfig config.Config) (map[ids.ID]DestinationClient, error) {
+func CreateDestinationClients(
+ logger logging.Logger,
+ relayerConfig config.Config,
+) (map[ids.ID]DestinationClient, error) {
destinationClients := make(map[ids.ID]DestinationClient)
for _, subnetInfo := range relayerConfig.DestinationBlockchains {
blockchainID, err := ids.FromString(subnetInfo.BlockchainID)
if err != nil {
logger.Error(
"Failed to decode base-58 encoded source chain ID",
- zap.String("blockchainID", blockchainID.String()),
+ zap.String("blockchainID", subnetInfo.BlockchainID),
zap.Error(err),
)
return nil, err
diff --git a/vms/evm/contract_message.go b/vms/evm/contract_message.go
index 49ace169..5221d8a3 100644
--- a/vms/evm/contract_message.go
+++ b/vms/evm/contract_message.go
@@ -24,8 +24,8 @@ func NewContractMessage(logger logging.Logger, subnetInfo config.SourceBlockchai
}
func (m *contractMessage) UnpackWarpMessage(unsignedMsgBytes []byte) (*avalancheWarp.UnsignedMessage, error) {
- // This function may be called with raw UnsignedMessage bytes or with ABI encoded bytes as emitted by the Warp precompile
- // The latter case is the steady state behavior, so check that first. The former only occurs on startup.
+ // This function may be called with raw UnsignedMessage bytes or with ABI encoded bytes as emitted by the Warp
+ // precompile. The latter case is the steady state behavior, so check that first. The former only occurs on startup.
unsignedMsg, err := warp.UnpackSendWarpEventDataToMessage(unsignedMsgBytes)
if err != nil {
m.logger.Debug(
diff --git a/vms/evm/contract_message_test.go b/vms/evm/contract_message_test.go
index cff0ec97..150f0961 100644
--- a/vms/evm/contract_message_test.go
+++ b/vms/evm/contract_message_test.go
@@ -61,24 +61,24 @@ func TestUnpack(t *testing.T) {
}{
{
name: "valid log data",
- input: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000024c00000000053968786a235cbcfb6e57321b94378e95939b773a9626acf7a8cc440075c02c7268000002220000000000010000001452718d4ea91a6dd9a68940dbd687efa32315d11600000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008db97c7cece249c2b98bdc0226cc4c2a57bf52fcb1d32d469938520383696931c26b9753662db74ad33c012f41e337aa828f1b74000000000000000000000000abcedf1234abcedf1234abcedf1234abcedf12340000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a100ff48a37cab9f87c8b5da933da46ea1a5fb80000000000000000000000000000000000000000000000000000000000000002acafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafe000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ input: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000024c00000000053968786a235cbcfb6e57321b94378e95939b773a9626acf7a8cc440075c02c7268000002220000000000010000001452718d4ea91a6dd9a68940dbd687efa32315d11600000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008db97c7cece249c2b98bdc0226cc4c2a57bf52fcb1d32d469938520383696931c26b9753662db74ad33c012f41e337aa828f1b74000000000000000000000000abcedf1234abcedf1234abcedf1234abcedf12340000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a100ff48a37cab9f87c8b5da933da46ea1a5fb80000000000000000000000000000000000000000000000000000000000000002acafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafe000000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll
networkID: constants.DefaultNetworkID,
expectError: false,
},
{
name: "invalid log data",
- input: "1000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000024c00000000053968786a235cbcfb6e57321b94378e95939b773a9626acf7a8cc440075c02c7268000002220000000000010000001452718d4ea91a6dd9a68940dbd687efa32315d11600000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008db97c7cece249c2b98bdc0226cc4c2a57bf52fcb1d32d469938520383696931c26b9753662db74ad33c012f41e337aa828f1b74000000000000000000000000abcedf1234abcedf1234abcedf1234abcedf12340000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a100ff48a37cab9f87c8b5da933da46ea1a5fb80000000000000000000000000000000000000000000000000000000000000002acafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafe000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ input: "1000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000024c00000000053968786a235cbcfb6e57321b94378e95939b773a9626acf7a8cc440075c02c7268000002220000000000010000001452718d4ea91a6dd9a68940dbd687efa32315d11600000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008db97c7cece249c2b98bdc0226cc4c2a57bf52fcb1d32d469938520383696931c26b9753662db74ad33c012f41e337aa828f1b74000000000000000000000000abcedf1234abcedf1234abcedf1234abcedf12340000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a100ff48a37cab9f87c8b5da933da46ea1a5fb80000000000000000000000000000000000000000000000000000000000000002acafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafe000000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll
expectError: true,
},
{
name: "valid standalone message",
- input: "00000000053968786a235cbcfb6e57321b94378e95939b773a9626acf7a8cc440075c02c7268000002220000000000010000001452718d4ea91a6dd9a68940dbd687efa32315d11600000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008db97c7cece249c2b98bdc0226cc4c2a57bf52fcb1d32d469938520383696931c26b9753662db74ad33c012f41e337aa828f1b74000000000000000000000000abcedf1234abcedf1234abcedf1234abcedf12340000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a100ff48a37cab9f87c8b5da933da46ea1a5fb80000000000000000000000000000000000000000000000000000000000000002acafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafe00000000000000000000000000000000000000000000",
+ input: "00000000053968786a235cbcfb6e57321b94378e95939b773a9626acf7a8cc440075c02c7268000002220000000000010000001452718d4ea91a6dd9a68940dbd687efa32315d11600000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008db97c7cece249c2b98bdc0226cc4c2a57bf52fcb1d32d469938520383696931c26b9753662db74ad33c012f41e337aa828f1b74000000000000000000000000abcedf1234abcedf1234abcedf1234abcedf12340000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a100ff48a37cab9f87c8b5da933da46ea1a5fb80000000000000000000000000000000000000000000000000000000000000002acafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafe00000000000000000000000000000000000000000000", //nolint:lll
networkID: constants.DefaultNetworkID,
expectError: false,
},
{
name: "invalid standalone message",
- input: "ab000000053968786a235cbcfb6e57321b94378e95939b773a9626acf7a8cc440075c02c7268000002220000000000010000001452718d4ea91a6dd9a68940dbd687efa32315d11600000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008db97c7cece249c2b98bdc0226cc4c2a57bf52fcb1d32d469938520383696931c26b9753662db74ad33c012f41e337aa828f1b74000000000000000000000000abcedf1234abcedf1234abcedf1234abcedf12340000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a100ff48a37cab9f87c8b5da933da46ea1a5fb80000000000000000000000000000000000000000000000000000000000000002acafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafe00000000000000000000000000000000000000000000",
+ input: "ab000000053968786a235cbcfb6e57321b94378e95939b773a9626acf7a8cc440075c02c7268000002220000000000010000001452718d4ea91a6dd9a68940dbd687efa32315d11600000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008db97c7cece249c2b98bdc0226cc4c2a57bf52fcb1d32d469938520383696931c26b9753662db74ad33c012f41e337aa828f1b74000000000000000000000000abcedf1234abcedf1234abcedf1234abcedf12340000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a100ff48a37cab9f87c8b5da933da46ea1a5fb80000000000000000000000000000000000000000000000000000000000000002acafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafe00000000000000000000000000000000000000000000", //nolint:lll
expectError: true,
},
}
diff --git a/vms/evm/destination_client_test.go b/vms/evm/destination_client_test.go
index 2658545f..b23576b9 100644
--- a/vms/evm/destination_client_test.go
+++ b/vms/evm/destination_client_test.go
@@ -91,9 +91,17 @@ func TestSendTx(t *testing.T) {
toAddress := "0x27aE10273D17Cd7e80de8580A51f476960626e5f"
gomock.InOrder(
- mockClient.EXPECT().EstimateBaseFee(gomock.Any()).Return(new(big.Int), test.estimateBaseFeeErr).Times(test.estimateBaseFeeTimes),
- mockClient.EXPECT().SuggestGasTipCap(gomock.Any()).Return(new(big.Int), test.suggestGasTipCapErr).Times(test.suggestGasTipCapTimes),
- mockClient.EXPECT().SendTransaction(gomock.Any(), gomock.Any()).Return(test.sendTransactionErr).Times(test.sendTransactionTimes),
+ mockClient.EXPECT().EstimateBaseFee(gomock.Any()).Return(
+ new(big.Int),
+ test.estimateBaseFeeErr,
+ ).Times(test.estimateBaseFeeTimes),
+ mockClient.EXPECT().SuggestGasTipCap(gomock.Any()).Return(
+ new(big.Int),
+ test.suggestGasTipCapErr,
+ ).Times(test.suggestGasTipCapTimes),
+ mockClient.EXPECT().SendTransaction(gomock.Any(), gomock.Any()).Return(
+ test.sendTransactionErr,
+ ).Times(test.sendTransactionTimes),
)
_, err := destinationClient.SendTx(warpMsg, toAddress, 0, []byte{})
diff --git a/vms/evm/signer/kms_signer_test.go b/vms/evm/signer/kms_signer_test.go
index 22ccfbf7..c67f1385 100644
--- a/vms/evm/signer/kms_signer_test.go
+++ b/vms/evm/signer/kms_signer_test.go
@@ -22,8 +22,8 @@ func TestRecoverEIP155Signature(t *testing.T) {
txHash: "69963cf4839e149fea0e7b0969dd6834ea4b22fa7f9209a46683982320e5edfd",
rValue: "00a94bfca53b42454acc43e7328ce7a8f244a629026095c36c0ee82607377cbee4",
sValue: "5964b0dd9bf23723570b6a073cb945fd1ba853ebf5579c8d11bb4fdb305cd079",
- pubKey: "04a3b664aa1f37bf6c46a3f2cdb209091e16070208b30244838e2cb9fe1465c4911ba2ab0c54bfc39972740ef7b24904f8740b0a69aca2bbfce0f2829429b9a5c5",
- expectedSignature: "a94bfca53b42454acc43e7328ce7a8f244a629026095c36c0ee82607377cbee45964b0dd9bf23723570b6a073cb945fd1ba853ebf5579c8d11bb4fdb305cd07901",
+ pubKey: "04a3b664aa1f37bf6c46a3f2cdb209091e16070208b30244838e2cb9fe1465c4911ba2ab0c54bfc39972740ef7b24904f8740b0a69aca2bbfce0f2829429b9a5c5", //nolint:lll
+ expectedSignature: "a94bfca53b42454acc43e7328ce7a8f244a629026095c36c0ee82607377cbee45964b0dd9bf23723570b6a073cb945fd1ba853ebf5579c8d11bb4fdb305cd07901", //nolint:lll
expectedError: false,
},
{
@@ -31,8 +31,8 @@ func TestRecoverEIP155Signature(t *testing.T) {
txHash: "69963cf4839e149fea0e7b0969dd6834ea4b22fa7f9209a46683982320e5edfd",
rValue: "10a94bfca53b42454acc43e7328ce7a8f244a629026095c36c0ee82607377cbee4",
sValue: "5964b0dd9bf23723570b6a073cb945fd1ba853ebf5579c8d11bb4fdb305cd079",
- pubKey: "04a3b664aa1f37bf6c46a3f2cdb209091e16070208b30244838e2cb9fe1465c4911ba2ab0c54bfc39972740ef7b24904f8740b0a69aca2bbfce0f2829429b9a5c5",
- expectedSignature: "a94bfca53b42454acc43e7328ce7a8f244a629026095c36c0ee82607377cbee45964b0dd9bf23723570b6a073cb945fd1ba853ebf5579c8d11bb4fdb305cd07901",
+ pubKey: "04a3b664aa1f37bf6c46a3f2cdb209091e16070208b30244838e2cb9fe1465c4911ba2ab0c54bfc39972740ef7b24904f8740b0a69aca2bbfce0f2829429b9a5c5", //nolint:lll
+ expectedSignature: "a94bfca53b42454acc43e7328ce7a8f244a629026095c36c0ee82607377cbee45964b0dd9bf23723570b6a073cb945fd1ba853ebf5579c8d11bb4fdb305cd07901", //nolint:lll
expectedError: true,
},
{
@@ -40,17 +40,18 @@ func TestRecoverEIP155Signature(t *testing.T) {
txHash: "69963cf4839e149fea0e7b0969dd6834ea4b22fa7f9209a46683982320e5edfd",
rValue: "00a94bfca53b42454acc43e7328ce7a8f244a629026095c36c0ee82607377cbee4",
sValue: "1964b0dd9bf23723570b6a073cb945fd1ba853ebf5579c8d11bb4fdb305cd079",
- pubKey: "04a3b664aa1f37bf6c46a3f2cdb209091e16070208b30244838e2cb9fe1465c4911ba2ab0c54bfc39972740ef7b24904f8740b0a69aca2bbfce0f2829429b9a5c5",
- expectedSignature: "a94bfca53b42454acc43e7328ce7a8f244a629026095c36c0ee82607377cbee45964b0dd9bf23723570b6a073cb945fd1ba853ebf5579c8d11bb4fdb305cd07901",
+ pubKey: "04a3b664aa1f37bf6c46a3f2cdb209091e16070208b30244838e2cb9fe1465c4911ba2ab0c54bfc39972740ef7b24904f8740b0a69aca2bbfce0f2829429b9a5c5", //nolint:lll
+ expectedSignature: "a94bfca53b42454acc43e7328ce7a8f244a629026095c36c0ee82607377cbee45964b0dd9bf23723570b6a073cb945fd1ba853ebf5579c8d11bb4fdb305cd07901", //nolint:lll
expectedError: true,
},
{
- name: "s-value too high",
- txHash: "e29dc6b15950a5433e239155a2208156ba8fd81eeb81ade45329d4b2fb2e8421",
- rValue: "00d993636ed097bf04b7c982e0394d572ead3c8f23ecc8baba0bbdfaa91aba4376",
- sValue: "00d1ac23b517ae2569522626096fa1922681c87612cceac971e855d42103fea7c6", // This is > secp256k1n/2
- pubKey: "04a3b664aa1f37bf6c46a3f2cdb209091e16070208b30244838e2cb9fe1465c4911ba2ab0c54bfc39972740ef7b24904f8740b0a69aca2bbfce0f2829429b9a5c5",
- expectedSignature: "d993636ed097bf04b7c982e0394d572ead3c8f23ecc8baba0bbdfaa91aba43762e53dc4ae851da96add9d9f6905e6dd838e666d3e25dd6c9d77c8a6bcc37997b00",
+ name: "s-value too high",
+ txHash: "e29dc6b15950a5433e239155a2208156ba8fd81eeb81ade45329d4b2fb2e8421",
+ rValue: "00d993636ed097bf04b7c982e0394d572ead3c8f23ecc8baba0bbdfaa91aba4376",
+ // This is > secp256k1n/2
+ sValue: "00d1ac23b517ae2569522626096fa1922681c87612cceac971e855d42103fea7c6",
+ pubKey: "04a3b664aa1f37bf6c46a3f2cdb209091e16070208b30244838e2cb9fe1465c4911ba2ab0c54bfc39972740ef7b24904f8740b0a69aca2bbfce0f2829429b9a5c5", //nolint:lll
+ expectedSignature: "d993636ed097bf04b7c982e0394d572ead3c8f23ecc8baba0bbdfaa91aba43762e53dc4ae851da96add9d9f6905e6dd838e666d3e25dd6c9d77c8a6bcc37997b00", //nolint:lll
expectedError: false,
},
}
diff --git a/vms/evm/subscriber.go b/vms/evm/subscriber.go
index 95878ebd..8027baeb 100644
--- a/vms/evm/subscriber.go
+++ b/vms/evm/subscriber.go
@@ -76,6 +76,7 @@ func (s *subscriber) ProcessFromHeight(height *big.Int, done chan bool) {
bigLatestBlockHeight := big.NewInt(0).SetUint64(latestBlockHeight)
+ //nolint:lll
for fromBlock := big.NewInt(0).Set(height); fromBlock.Cmp(bigLatestBlockHeight) <= 0; fromBlock.Add(fromBlock, big.NewInt(MaxBlocksPerRequest)) {
toBlock := big.NewInt(0).Add(fromBlock, big.NewInt(MaxBlocksPerRequest-1))