Skip to content

Commit

Permalink
Merge pull request #279 from najeal/eth-rpc-options
Browse files Browse the repository at this point in the history
Eth Client: Use http-headers and query-parameters
  • Loading branch information
cam-schultz authored May 14, 2024
2 parents bb8c561 + 5dd87e2 commit 38a33ad
Show file tree
Hide file tree
Showing 15 changed files with 224 additions and 79 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,13 +210,13 @@ The relayer is configured via a JSON file, the path to which is passed in via th

- The VM type of the source blockchain.

`"rpc-endpoint": string`
`"rpc-endpoint": APIConfig`

- The RPC endpoint of the source blockchain's API node.
- The RPC endpoint configuration of the source blockchain's API node.

`"ws-endpoint": string`
`"ws-endpoint": APIConfig`

- The WebSocket endpoint of the source blockchain's API node.
- The WebSocket endpoint configuration of the source blockchain's API node.

`"message-contracts": map[string]MessageProtocolConfig`

Expand Down Expand Up @@ -250,9 +250,9 @@ The relayer is configured via a JSON file, the path to which is passed in via th

- The VM type of the source blockchain.

`"rpc-endpoint": string`
`"rpc-endpoint": APIConfig`

- The RPC endpoint of the destination blockchains's API node.
- The RPC endpoint configuration of the destination blockchains's API node.

`"account-private-key": string`

Expand Down
34 changes: 17 additions & 17 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
"github.com/ava-labs/avalanchego/utils/set"
"github.com/ava-labs/awm-relayer/utils"

"github.com/ava-labs/subnet-evm/ethclient"
"github.com/ava-labs/awm-relayer/ethclient"
"github.com/ava-labs/subnet-evm/precompile/contracts/warp"

// Force-load precompiles to trigger registration
Expand Down Expand Up @@ -85,8 +85,8 @@ type SourceBlockchain struct {
SubnetID string `mapstructure:"subnet-id" json:"subnet-id"`
BlockchainID string `mapstructure:"blockchain-id" json:"blockchain-id"`
VM string `mapstructure:"vm" json:"vm"`
RPCEndpoint string `mapstructure:"rpc-endpoint" json:"rpc-endpoint"`
WSEndpoint string `mapstructure:"ws-endpoint" json:"ws-endpoint"`
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"`
Expand All @@ -100,13 +100,13 @@ type SourceBlockchain struct {

// 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"`
VM string `mapstructure:"vm" json:"vm"`
RPCEndpoint string `mapstructure:"rpc-endpoint" json:"rpc-endpoint"`
KMSKeyID string `mapstructure:"kms-key-id" json:"kms-key-id"`
KMSAWSRegion string `mapstructure:"kms-aws-region" json:"kms-aws-region"`
AccountPrivateKey string `mapstructure:"account-private-key" json:"account-private-key"`
SubnetID string `mapstructure:"subnet-id" json:"subnet-id"`
BlockchainID string `mapstructure:"blockchain-id" json:"blockchain-id"`
VM string `mapstructure:"vm" json:"vm"`
RPCEndpoint APIConfig `mapstructure:"rpc-endpoint" json:"rpc-endpoint"`
KMSKeyID string `mapstructure:"kms-key-id" json:"kms-key-id"`
KMSAWSRegion string `mapstructure:"kms-aws-region" json:"kms-aws-region"`
AccountPrivateKey string `mapstructure:"account-private-key" json:"account-private-key"`

// Fetched from the chain after startup
warpQuorum WarpQuorum
Expand Down Expand Up @@ -436,11 +436,11 @@ func (s *SourceBlockchain) Validate(destinationBlockchainIDs *set.Set[string]) e
if _, err := ids.FromString(s.BlockchainID); err != nil {
return fmt.Errorf("invalid blockchainID in source subnet configuration. Provided ID: %s", s.BlockchainID)
}
if _, err := url.ParseRequestURI(s.WSEndpoint); err != nil {
return fmt.Errorf("invalid relayer subscribe URL in source subnet configuration: %w", err)
if err := s.RPCEndpoint.Validate(); err != nil {
return fmt.Errorf("invalid rpc-endpoint in source subnet configuration: %w", err)
}
if _, err := url.ParseRequestURI(s.RPCEndpoint); err != nil {
return fmt.Errorf("invalid relayer RPC URL in source subnet configuration: %w", err)
if err := s.WSEndpoint.Validate(); err != nil {
return fmt.Errorf("invalid ws-endpoint in source subnet configuration: %w", err)
}

// Validate the VM specific settings
Expand Down Expand Up @@ -543,8 +543,8 @@ func (s *DestinationBlockchain) Validate() error {
if _, err := ids.FromString(s.BlockchainID); err != nil {
return fmt.Errorf("invalid blockchainID in source subnet configuration. Provided ID: %s", s.BlockchainID)
}
if _, err := url.ParseRequestURI(s.RPCEndpoint); err != nil {
return fmt.Errorf("invalid relayer broadcast URL: %w", err)
if err := s.RPCEndpoint.Validate(); err != nil {
return fmt.Errorf("invalid rpc-endpoint in destination subnet configuration: %w", err)
}
if s.KMSKeyID != "" {
if s.KMSAWSRegion == "" {
Expand Down Expand Up @@ -598,7 +598,7 @@ func (s *DestinationBlockchain) initializeWarpQuorum() error {
return fmt.Errorf("invalid subnetID in configuration. error: %w", err)
}

client, err := ethclient.Dial(s.RPCEndpoint)
client, err := ethclient.DialWithConfig(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)
}
Expand Down
14 changes: 10 additions & 4 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func TestGetRelayerAccountPrivateKey_set_pk_with_subnet_env(t *testing.T) {
}
runConfigModifierEnvVarTest(t, testCase)
}

func TestGetRelayerAccountPrivateKey_set_pk_with_global_env(t *testing.T) {
testCase := configMondifierEnvVarTestCase{
baseConfig: TestValidConfig,
Expand Down Expand Up @@ -218,6 +219,7 @@ func TestEitherKMSOrAccountPrivateKey(t *testing.T) {
}
}
}

func TestGetWarpQuorum(t *testing.T) {
blockchainID, err := ids.FromString("p433wpuXyJiDhyazPYyZMJeaoPSW76CBZ2x7wrVPLgvokotXz")
require.NoError(t, err)
Expand Down Expand Up @@ -347,10 +349,14 @@ func TestGetWarpQuorum(t *testing.T) {
func TestValidateSourceBlockchain(t *testing.T) {
validSourceCfg := SourceBlockchain{
BlockchainID: testBlockchainID,
RPCEndpoint: fmt.Sprintf("http://test.avax.network/ext/bc/%s/rpc", testBlockchainID),
WSEndpoint: fmt.Sprintf("ws://test.avax.network/ext/bc/%s/ws", testBlockchainID),
SubnetID: testSubnetID,
VM: "evm",
RPCEndpoint: APIConfig{
BaseURL: fmt.Sprintf("http://test.avax.network/ext/bc/%s/rpc", testBlockchainID),
},
WSEndpoint: APIConfig{
BaseURL: fmt.Sprintf("ws://test.avax.network/ext/bc/%s/ws", testBlockchainID),
},
SubnetID: testSubnetID,
VM: "evm",
SupportedDestinations: []*SupportedDestination{
{
BlockchainID: testBlockchainID,
Expand Down
30 changes: 21 additions & 9 deletions config/test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ var (
},
SourceBlockchains: []*SourceBlockchain{
{
RPCEndpoint: fmt.Sprintf("http://test.avax.network/ext/bc/%s/rpc", testBlockchainID),
WSEndpoint: fmt.Sprintf("ws://test.avax.network/ext/bc/%s/ws", testBlockchainID),
RPCEndpoint: APIConfig{
BaseURL: fmt.Sprintf("http://test.avax.network/ext/bc/%s/rpc", testBlockchainID),
},
WSEndpoint: APIConfig{
BaseURL: fmt.Sprintf("ws://test.avax.network/ext/bc/%s/ws", testBlockchainID),
},
BlockchainID: testBlockchainID,
SubnetID: testSubnetID,
VM: "evm",
Expand All @@ -49,7 +53,9 @@ var (
},
DestinationBlockchains: []*DestinationBlockchain{
{
RPCEndpoint: fmt.Sprintf("http://test.avax.network/ext/bc/%s/rpc", testBlockchainID),
RPCEndpoint: APIConfig{
BaseURL: fmt.Sprintf("http://test.avax.network/ext/bc/%s/rpc", testBlockchainID),
},
BlockchainID: testBlockchainID,
SubnetID: testSubnetID,
VM: "evm",
Expand All @@ -58,8 +64,12 @@ var (
},
}
TestValidSourceBlockchainConfig = SourceBlockchain{
RPCEndpoint: "http://test.avax.network/ext/bc/C/rpc",
WSEndpoint: "ws://test.avax.network/ext/bc/C/ws",
RPCEndpoint: APIConfig{
BaseURL: "http://test.avax.network/ext/bc/C/rpc",
},
WSEndpoint: APIConfig{
BaseURL: "ws://test.avax.network/ext/bc/C/ws",
},
BlockchainID: "S4mMqUXe7vHsGiRAma6bv3CKnyaLssyAxmQ2KvFpX1KEvfFCD",
SubnetID: "2TGBXcnwx5PqiXWiqxAKUaNSqDguXNh1mxnp82jui68hxJSZAx",
VM: "evm",
Expand All @@ -70,10 +80,12 @@ var (
},
}
TestValidDestinationBlockchainConfig = DestinationBlockchain{
SubnetID: "2TGBXcnwx5PqiXWiqxAKUaNSqDguXNh1mxnp82jui68hxJSZAx",
BlockchainID: "S4mMqUXe7vHsGiRAma6bv3CKnyaLssyAxmQ2KvFpX1KEvfFCD",
VM: "evm",
RPCEndpoint: "http://test.avax.network/ext/bc/C/rpc",
SubnetID: "2TGBXcnwx5PqiXWiqxAKUaNSqDguXNh1mxnp82jui68hxJSZAx",
BlockchainID: "S4mMqUXe7vHsGiRAma6bv3CKnyaLssyAxmQ2KvFpX1KEvfFCD",
VM: "evm",
RPCEndpoint: APIConfig{
BaseURL: "http://test.avax.network/ext/bc/C/rpc",
},
AccountPrivateKey: "1234567890123456789012345678901234567890123456789012345678901234",
}
)
54 changes: 54 additions & 0 deletions ethclient/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package ethclient

import (
"context"
"errors"
"fmt"
"net/url"

"github.com/ava-labs/subnet-evm/ethclient"
"github.com/ava-labs/subnet-evm/rpc"
)

var ErrInvalidEndpoint = errors.New("invalid rpc endpoint")

type Client ethclient.Client

// DialWithContext returns an ethclient.Client with the internal RPC client configured with the provided options.
func DialWithConfig(ctx context.Context, baseURL string, httpHeaders, queryParams map[string]string) (Client, error) {
url, err := addQueryParams(baseURL, queryParams)
if err != nil {
return nil, err
}
client, err := rpc.DialOptions(ctx, url, newClientHeaderOptions(httpHeaders)...)
if err != nil {
return nil, err
}
return ethclient.NewClient(client), nil
}

// addQueryParams adds the query parameters to the url
func addQueryParams(endpoint string, queryParams map[string]string) (string, error) {
uri, err := url.ParseRequestURI(endpoint)
if err != nil {
return "", fmt.Errorf("%w: %v", ErrInvalidEndpoint, err)
}
values := uri.Query()
for key, value := range queryParams {
values.Add(key, value)
}
uri.RawQuery = values.Encode()
return uri.String(), nil
}

// newClientOptions creates a ClientOption slice from httpHeaders
func newClientHeaderOptions(httpHeaders map[string]string) []rpc.ClientOption {
opts := make([]rpc.ClientOption, 0, len(httpHeaders))
for key, value := range httpHeaders {
opts = append(opts, rpc.WithHeader(key, value))
}
return opts
}
45 changes: 45 additions & 0 deletions ethclient/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package ethclient

import (
"errors"
"testing"

"github.com/stretchr/testify/require"
)

func TestAddQueryParams(t *testing.T) {
t.Run("NoQueryParams", func(t *testing.T) {
newurl, err := addQueryParams("https://avalabs.com", nil)
require.NoError(t, err)
require.Equal(t, "https://avalabs.com", newurl)
})
t.Run("TwoQueryParams", func(t *testing.T) {
newurl, err := addQueryParams("https://avalabs.com", map[string]string{
"first": "value1",
"second": "value2",
})
require.NoError(t, err)
require.Equal(t, "https://avalabs.com?first=value1&second=value2", newurl)
})
t.Run("InvalidEndpoint", func(t *testing.T) {
_, err := addQueryParams("invalid-endpoint", nil)
require.True(t, errors.Is(err, ErrInvalidEndpoint))
})
}

func TestNewClientOptions(t *testing.T) {
t.Run("NoHttpHeaders", func(t *testing.T) {
opts := newClientHeaderOptions(nil)
require.Len(t, opts, 0)
})
t.Run("TwoHttpHeaders", func(t *testing.T) {
opts := newClientHeaderOptions(map[string]string{
"first": "value1",
"second": "value2",
})
require.Len(t, opts, 2)
})
}
6 changes: 2 additions & 4 deletions messages/off-chain-registry/message_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,15 @@ import (
"github.com/ava-labs/avalanchego/vms/platformvm/warp"
warpPayload "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload"
"github.com/ava-labs/awm-relayer/config"
"github.com/ava-labs/awm-relayer/ethclient"
"github.com/ava-labs/awm-relayer/vms"
"github.com/ava-labs/subnet-evm/accounts/abi/bind"
"github.com/ava-labs/subnet-evm/ethclient"
teleporterregistry "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/upgrades/TeleporterRegistry"
"github.com/ethereum/go-ethereum/common"
"go.uber.org/zap"
)

var (
OffChainRegistrySourceAddress = common.HexToAddress("0x0000000000000000000000000000000000000000")
)
var OffChainRegistrySourceAddress = common.HexToAddress("0x0000000000000000000000000000000000000000")

const (
addProtocolVersionGasLimit uint64 = 500_000
Expand Down
2 changes: 1 addition & 1 deletion messages/teleporter/message_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import (
"github.com/ava-labs/avalanchego/vms/platformvm/warp"
warpPayload "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload"
"github.com/ava-labs/awm-relayer/config"
"github.com/ava-labs/awm-relayer/ethclient"
"github.com/ava-labs/awm-relayer/vms"
"github.com/ava-labs/subnet-evm/accounts/abi/bind"
"github.com/ava-labs/subnet-evm/ethclient"
teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger"
gasUtils "github.com/ava-labs/teleporter/utils/gas-utils"
teleporterUtils "github.com/ava-labs/teleporter/utils/teleporter-utils"
Expand Down
11 changes: 8 additions & 3 deletions relayer/application_relayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ import (
avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp"
"github.com/ava-labs/awm-relayer/config"
"github.com/ava-labs/awm-relayer/database"
"github.com/ava-labs/awm-relayer/ethclient"
"github.com/ava-labs/awm-relayer/messages"
"github.com/ava-labs/awm-relayer/peers"
"github.com/ava-labs/awm-relayer/utils"
coreEthMsg "github.com/ava-labs/coreth/plugin/evm/message"
"github.com/ava-labs/subnet-evm/ethclient"
msg "github.com/ava-labs/subnet-evm/plugin/evm/message"
warpBackend "github.com/ava-labs/subnet-evm/warp"
"go.uber.org/zap"
Expand Down Expand Up @@ -191,7 +191,7 @@ func (r *applicationRelayer) relayMessage(
func (r *applicationRelayer) createSignedMessage(unsignedMessage *avalancheWarp.UnsignedMessage) (*avalancheWarp.Message, error) {
r.logger.Info("Fetching aggregate signature from the source chain validators via API")
// TODO: To properly support this, we should provide a dedicated Warp API endpoint in the config
uri := utils.StripFromString(r.sourceBlockchain.RPCEndpoint, "/ext")
uri := utils.StripFromString(r.sourceBlockchain.RPCEndpoint.BaseURL, "/ext")
warpClient, err := warpBackend.NewClient(uri, r.sourceBlockchain.GetBlockchainID().String())
if err != nil {
r.logger.Error(
Expand Down Expand Up @@ -639,7 +639,12 @@ func (r *applicationRelayer) calculateStartingBlockHeight(processHistoricalBlock

// Gets the height of the chain head, writes it to the database, then returns it.
func (r *applicationRelayer) setProcessedBlockHeightToLatest() (uint64, error) {
ethClient, err := ethclient.Dial(r.sourceBlockchain.RPCEndpoint)
ethClient, err := ethclient.DialWithConfig(
context.Background(),
r.sourceBlockchain.RPCEndpoint.BaseURL,
r.sourceBlockchain.RPCEndpoint.HTTPHeaders,
r.sourceBlockchain.RPCEndpoint.QueryParams,
)
if err != nil {
r.logger.Error(
"Failed to dial node",
Expand Down
12 changes: 9 additions & 3 deletions sample-relayer-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@
"subnet-id": "11111111111111111111111111111111LpoYY",
"blockchain-id": "yH8D7ThNJkxmtkuv2jgBa4P1Rn3Qpr4pPr7QYNfcdoS6k6HWp",
"vm": "evm",
"rpc-endpoint": "https://api.avax-test.network/ext/bc/C/rpc",
"ws-endpoint": "wss://api.avax-test.network/ext/bc/C/ws",
"rpc-endpoint": {
"base-url": "https://api.avax-test.network/ext/bc/C/rpc"
},
"ws-endpoint": {
"base-url": "wss://api.avax-test.network/ext/bc/C/ws"
},
"message-contracts": {
"0x253b2784c75e510dD0fF1da844684a1aC0aa5fcf": {
"message-format": "teleporter",
Expand All @@ -27,7 +31,9 @@
"subnet-id": "7WtoAMPhrmh5KosDUsFL9yTcvw7YSxiKHPpdfs4JsgW47oZT5",
"blockchain-id": "2D8RG4UpSXbPbvPCAWppNJyqTG2i2CAXSkTgmTBBvs7GKNZjsY",
"vm": "evm",
"rpc-endpoint": "https://subnets.avax.network/dispatch/testnet/rpc",
"rpc-endpoint": {
"base-url": "https://subnets.avax.network/dispatch/testnet/rpc"
},
"account-private-key": "0x7493..."
}
]
Expand Down
Loading

0 comments on commit 38a33ad

Please sign in to comment.