Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup config #206

Merged
merged 13 commits into from
Mar 4, 2024
53 changes: 13 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,25 +90,22 @@ The relayer binary accepts a path to a JSON configuration file as the sole argum

The relayer is configured via a JSON file, the path to which is passed in via the `--config-file` command line argument. The following configuration options are available:

`"log-level": "debug" | "info" | "warn" | "error" | "fatal" | "panic"`
`"log-level": "verbo" | "debug" | "info" | "warn" | "error" | "fatal" | "panic"`

- The log level for the relayer. Defaults to `info`.

`"network-id": unsigned integer`

- The ID of the Avalanche network to which the relayer will connect. Defaults to `1` (Mainnet).

`"p-chain-api-url": string`

- The URL of the Avalanche P-Chain API node to which the relayer will connect. This API node needs to have the following methods enabled:
- info.peers
- platform.getHeight
- platform.validatedBy
- platform.getValidatorsAt

`"encrypt-connection": boolean`
`"info-api-url": string`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not too familiar with the info api nodes, is it common for the p chain api node to have info api enabled as well?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thinking if worth have more documentation about how to find their info api node, or whether to add a comment about testing with the p chain api node

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's entirely up to the node operator which APIs to support. The P-Chain and Info APIs are independent, so we shouldn't assume that they'll both be enabled on the same node here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public APIs support both the info endpoints and most of the P-chain endpoints (with the exception of getValidatorsAt.

$ curl 'https://api.avax-test.network/ext/info' \
> --header 'Content-Type: application/json' \
> --data '{
>     "jsonrpc":"2.0",
>     "id"     :1,
>     "method" :"info.getNetworkID"
> }'
{"jsonrpc":"2.0","result":{"networkID":"5"},"id":1}

I probably would have gone the route of having a single avalancheAPIURL configuration option and adding the path extensions as needed in the application, but not opposed to having these split up in the event that the node used for the P-chain API (possibly private node) doesn't have the Info API enabled (all public APIs have the Info API available).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, in the case of the public API, both the info and P-chain API share the same base URL. That's not true in general though, so I think it's better to make as few assumptions here as we can.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, totally agree. Thanks for elaborating.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any additional comment/documentation we can add for finding the info api, or is this relatively understood/straightforward for node operators?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is the place to dive too deeply into API availability. We can add a note that the public API should support the required methods though.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, I agree


- Whether or not to encrypt the connection to the P-Chain API node. Defaults to `true`.
- The URL of the Avalanche Info API node to which the relayer will connect. This API node needs to have the following methods enabled:
- info.peers
- info.getNetworkID

`"storage-location": string`

Expand Down Expand Up @@ -148,39 +145,27 @@ 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 Subnet ID.

`"blockchain-id": string`

- cb58-encoded Blockchain ID
- cb58-encoded Blockchain ID.

`"vm": string`

- The VM type of the source subnet.

`"api-node-host": string`

- The host of the source subnet's API node.

`"api-node-port": unsigned integer`

- The port of the source subnet's API node.

`"encrypt-connection": boolean`

- Whether or not to encrypt the connection to the source subnet's API node.

`"rpc-endpoint": string`

- The RPC endpoint of the source subnet's API node. Used in favor of `api-node-host`, `api-node-port`, and `encrypt-connection` when constructing the endpoint
- The RPC endpoint of the source subnet's API node.

`"ws-endpoint": string`

- The WebSocket endpoint of the source subnet's API node. Used in favor of `api-node-host`, `api-node-port`, and `encrypt-connection` when constructing the endpoint
- The WebSocket endpoint of the source subnet's API node.

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

- Map of contract addresses to the config options of the protocol at that address. Each `MessageProtocolConfig` consists of a unique `message-format` name, and the raw JSON `settings`
- Map of contract addresses to the config options of the protocol at that address. Each `MessageProtocolConfig` consists of a unique `message-format` name, and the raw JSON `settings`.

`"supported-destinations": []string`

Expand All @@ -196,31 +181,19 @@ 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 Subnet ID.

`"blockchain-id": string`

- cb58-encoded Blockchain ID
- cb58-encoded Blockchain ID.

`"vm": string`

- The VM type of the source subnet.

`"api-node-host": string`

- The host of the source subnet's API node.

`"api-node-port": unsigned integer`

- The port of the source subnet's API node.

`"encrypt-connection": boolean`

- Whether or not to encrypt the connection to the source subnet's API node.

`"rpc-endpoint": string`

- The RPC endpoint of the destination subnet's API node. Used in favor of `api-node-host`, `api-node-port`, and `encrypt-connection` when constructing the endpoint
- The RPC endpoint of the destination subnet's API node.

`"account-private-key": string`

Expand Down
122 changes: 9 additions & 113 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,6 @@ type SourceSubnet struct {
SubnetID string `mapstructure:"subnet-id" json:"subnet-id"`
BlockchainID string `mapstructure:"blockchain-id" json:"blockchain-id"`
VM string `mapstructure:"vm" json:"vm"`
APINodeHost string `mapstructure:"api-node-host" json:"api-node-host"`
APINodePort uint32 `mapstructure:"api-node-port" json:"api-node-port"`
EncryptConnection bool `mapstructure:"encrypt-connection" json:"encrypt-connection"`
RPCEndpoint string `mapstructure:"rpc-endpoint" json:"rpc-endpoint"`
WSEndpoint string `mapstructure:"ws-endpoint" json:"ws-endpoint"`
MessageContracts map[string]MessageProtocolConfig `mapstructure:"message-contracts" json:"message-contracts"`
Expand All @@ -76,9 +73,6 @@ type DestinationSubnet struct {
SubnetID string `mapstructure:"subnet-id" json:"subnet-id"`
BlockchainID string `mapstructure:"blockchain-id" json:"blockchain-id"`
VM string `mapstructure:"vm" json:"vm"`
APINodeHost string `mapstructure:"api-node-host" json:"api-node-host"`
APINodePort uint32 `mapstructure:"api-node-port" json:"api-node-port"`
EncryptConnection bool `mapstructure:"encrypt-connection" json:"encrypt-connection"`
RPCEndpoint string `mapstructure:"rpc-endpoint" json:"rpc-endpoint"`
AccountPrivateKey string `mapstructure:"account-private-key" json:"account-private-key"`

Expand All @@ -93,9 +87,8 @@ type WarpQuorum struct {

type Config struct {
LogLevel string `mapstructure:"log-level" json:"log-level"`
NetworkID uint32 `mapstructure:"network-id" json:"network-id"`
PChainAPIURL string `mapstructure:"p-chain-api-url" json:"p-chain-api-url"`
EncryptConnection bool `mapstructure:"encrypt-connection" json:"encrypt-connection"`
InfoAPIURL string `mapstructure:"info-api-url" json:"info-api-url"`
StorageLocation string `mapstructure:"storage-location" json:"storage-location"`
SourceSubnets []*SourceSubnet `mapstructure:"source-subnets" json:"source-subnets"`
DestinationSubnets []*DestinationSubnet `mapstructure:"destination-subnets" json:"destination-subnets"`
Expand All @@ -109,8 +102,6 @@ type Config struct {

func SetDefaultConfigValues(v *viper.Viper) {
v.SetDefault(LogLevelKey, logging.Info.String())
v.SetDefault(NetworkIDKey, constants.MainnetID)
v.SetDefault(EncryptConnectionKey, true)
v.SetDefault(StorageLocationKey, "./.awm-relayer-storage")
v.SetDefault(ProcessMissedBlocksKey, true)
}
Expand Down Expand Up @@ -138,9 +129,8 @@ func BuildConfig(v *viper.Viper) (Config, bool, error) {
)

cfg.LogLevel = v.GetString(LogLevelKey)
cfg.NetworkID = v.GetUint32(NetworkIDKey)
cfg.PChainAPIURL = v.GetString(PChainAPIURLKey)
cfg.EncryptConnection = v.GetBool(EncryptConnectionKey)
cfg.InfoAPIURL = v.GetString(InfoAPIURLKey)
cfg.StorageLocation = v.GetString(StorageLocationKey)
cfg.ProcessMissedBlocks = v.GetBool(ProcessMissedBlocksKey)
if err := v.UnmarshalKey(ManualWarpMessagesKey, &cfg.ManualWarpMessages); err != nil {
Expand Down Expand Up @@ -181,19 +171,6 @@ func BuildConfig(v *viper.Viper) (Config, bool, error) {
return Config{}, false, fmt.Errorf("failed to validate configuration: %w", err)
}

var protocol string
if cfg.EncryptConnection {
protocol = "https"
} else {
protocol = "http"
}

pChainapiUrl, err := utils.ConvertProtocol(cfg.PChainAPIURL, protocol)
if err != nil {
return Config{}, false, err
}
cfg.PChainAPIURL = pChainapiUrl

return cfg, optionOverwritten, nil
}

Expand All @@ -210,6 +187,9 @@ func (c *Config) Validate() error {
if _, err := url.ParseRequestURI(c.PChainAPIURL); err != nil {
return err
}
if _, err := url.ParseRequestURI(c.InfoAPIURL); err != nil {
return err
}

// Validate the destination chains
destinationChains := set.NewSet[string](len(c.DestinationSubnets))
Expand Down Expand Up @@ -403,10 +383,10 @@ func (s *SourceSubnet) Validate(destinationBlockchainIDs *set.Set[string]) 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.GetNodeWSEndpoint()); err != nil {
if _, err := url.ParseRequestURI(s.WSEndpoint); err != nil {
return fmt.Errorf("invalid relayer subscribe URL in source subnet configuration: %w", err)
}
if _, err := url.ParseRequestURI(s.GetNodeRPCEndpoint()); err != nil {
if _, err := url.ParseRequestURI(s.RPCEndpoint); err != nil {
return fmt.Errorf("invalid relayer RPC URL in source subnet configuration: %w", err)
}

Expand Down Expand Up @@ -468,7 +448,7 @@ func (s *DestinationSubnet) 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.GetNodeRPCEndpoint()); err != nil {
if _, err := url.ParseRequestURI(s.RPCEndpoint); err != nil {
return fmt.Errorf("invalid relayer broadcast URL: %w", err)
}

Expand Down Expand Up @@ -499,7 +479,7 @@ func (s *DestinationSubnet) initializeWarpQuorum() error {
return fmt.Errorf("invalid subnetID in configuration. error: %w", err)
}

client, err := ethclient.Dial(s.GetNodeRPCEndpoint())
client, err := ethclient.Dial(s.RPCEndpoint)
if err != nil {
return fmt.Errorf("failed to dial destination blockchain %s: %w", blockchainID, err)
}
Expand All @@ -513,90 +493,6 @@ func (s *DestinationSubnet) initializeWarpQuorum() error {
return nil
}

func constructURL(protocol string, host string, port uint32, encrypt bool, blockchainIDStr string, subnetIDStr string) string {
var protocolPathMap = map[string]string{
"http": "rpc",
"ws": "ws",
}
path := protocolPathMap[protocol]

if encrypt {
protocol = protocol + "s"
}
portStr := ""
if port != 0 {
portStr = fmt.Sprintf(":%d", port)
}
subnetID, _ := ids.FromString(subnetIDStr) // already validated in Validate()
if subnetID == constants.PrimaryNetworkID {
blockchainIDStr = cChainIdentifierString
}
return fmt.Sprintf("%s://%s%s/ext/bc/%s/%s", protocol, host, portStr, blockchainIDStr, path)
}

// Constructs an RPC endpoint for the subnet.
// If the RPCEndpoint field is set in the configuration, returns that directly.
// Otherwise, constructs the endpoint from the APINodeHost, APINodePort, and EncryptConnection fields,
// following the /ext/bc/{blockchainID}/rpc format.
func (s *DestinationSubnet) GetNodeRPCEndpoint() string {
if s.RPCEndpoint != "" {
return s.RPCEndpoint
}

// Save this result for future use
s.RPCEndpoint = constructURL(
"http",
s.APINodeHost,
s.APINodePort,
s.EncryptConnection,
s.BlockchainID,
s.SubnetID,
)
return s.RPCEndpoint
}

// Constructs an RPC endpoint for the subnet.
// If the RPCEndpoint field is set in the configuration, returns that directly.
// Otherwise, constructs the endpoint from the APINodeHost, APINodePort, and EncryptConnection fields,
// following the /ext/bc/{blockchainID}/rpc format.
func (s *SourceSubnet) GetNodeRPCEndpoint() string {
if s.RPCEndpoint != "" {
return s.RPCEndpoint
}

// Save this result for future use
s.RPCEndpoint = constructURL(
"http",
s.APINodeHost,
s.APINodePort,
s.EncryptConnection,
s.BlockchainID,
s.SubnetID,
)
return s.RPCEndpoint
}

// Constructs a WS endpoint for the subnet.
// If the WSEndpoint field is set in the configuration, returns that directly.
// Otherwise, constructs the endpoint from the APINodeHost, APINodePort, and EncryptConnection fields,
// following the /ext/bc/{blockchainID}/ws format.
func (s *SourceSubnet) GetNodeWSEndpoint() string {
if s.WSEndpoint != "" {
return s.WSEndpoint
}

// Save this result for future use
s.WSEndpoint = constructURL(
"ws",
s.APINodeHost,
s.APINodePort,
s.EncryptConnection,
s.BlockchainID,
s.SubnetID,
)
return s.WSEndpoint
}

// Get the private key and derive the wallet address from a relayer's configured private key for a given destination subnet.
func (s *DestinationSubnet) GetRelayerAccountInfo() (*ecdsa.PrivateKey, common.Address, error) {
pk, err := crypto.HexToECDSA(s.AccountPrivateKey)
Expand Down
Loading
Loading