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

Node relayer for fuji #133

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions avalanche/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"

"github.com/ava-labs/avalanche-tooling-sdk-go/utils"

"github.com/ava-labs/avalanchego/genesis"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/constants"
Expand Down
1 change: 1 addition & 0 deletions constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const (
AvalancheGoGitRepo = "https://github.com/ava-labs/avalanchego"
SubnetEVMRepoName = "subnet-evm"

CloudNodeAWMRelayerPath = "/home/ubuntu/.awm-relayer"
AWMRelayerInstallDir = "awm-relayer"
AWMRelayerConfigFilename = "awm-relayer-config.json"

Expand Down
102 changes: 102 additions & 0 deletions examples/interchain_aws.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package main

import (
"context"
"fmt"
"time"

"github.com/ava-labs/avalanche-tooling-sdk-go/avalanche"
awsAPI "github.com/ava-labs/avalanche-tooling-sdk-go/cloud/aws"
"github.com/ava-labs/avalanche-tooling-sdk-go/node"
"github.com/ava-labs/avalanche-tooling-sdk-go/utils"
)

func CreateAWMRelayerNode() {
ctx := context.Background()

// Get the default cloud parameters for AWS
cp, err := node.GetDefaultCloudParams(ctx, node.AWSCloud)
if err != nil {
panic(err)
}

securityGroupName := "SECURITY_GROUP_NAME"
// Create a new security group in AWS if you do not currently have one in the selected
// AWS region.
sgID, err := awsAPI.CreateSecurityGroup(ctx, securityGroupName, cp.AWSConfig.AWSProfile, cp.Region)
if err != nil {
panic(err)
}
// Set the security group we are using when creating our Avalanche Nodes
cp.AWSConfig.AWSSecurityGroupName = securityGroupName
cp.AWSConfig.AWSSecurityGroupID = sgID

keyPairName := "KEY_PAIR_NAME"
sshPrivateKeyPath := utils.ExpandHome("PRIVATE_KEY_FILEPATH")
// Create a new AWS SSH key pair if you do not currently have one in your selected AWS region.
// Note that the created key pair can only be used in the region that it was created in.
// The private key to the created key pair will be stored in the filepath provided in
// sshPrivateKeyPath.
if err := awsAPI.CreateSSHKeyPair(ctx, cp.AWSConfig.AWSProfile, cp.Region, keyPairName, sshPrivateKeyPath); err != nil {
panic(err)
}
// Set the key pair we are using when creating our Avalanche Nodes
cp.AWSConfig.AWSKeyPair = keyPairName

const (
awmVersion = "v1.4.0"
)

// Create two new Avalanche Validator nodes on Fuji Network on AWS without Elastic IPs
// attached. Once CreateNodes is completed, the validators will begin bootstrapping process
// to Primary Network in Fuji Network. Nodes need to finish bootstrapping process
// before they can validate Avalanche Primary Network / Subnet.
//
// SDK function for nodes to start validating Primary Network / Subnet will be available
// in the next Avalanche Tooling SDK release.
hosts, err := node.CreateNodes(ctx,
&node.NodeParams{
CloudParams: cp,
Count: 1,
Roles: []node.SupportedRole{node.AWMRelayer},
Network: avalanche.FujiNetwork(),
AWMRelayerVersion: awmVersion,
UseStaticIP: false,
SSHPrivateKeyPath: sshPrivateKeyPath,
})
if err != nil {
panic(err)
}

const (
sshTimeout = 120 * time.Second
sshCommandTimeout = 10 * time.Second
)

// Examples showing how to run ssh commands on the created nodes
for _, h := range hosts {
// Wait for the host to be ready (only needs to be done once for newly created nodes)
fmt.Println("Waiting for SSH shell")
if err := h.WaitForSSHShell(sshTimeout); err != nil {
panic(err)
}
// check if awm-relayer is running
if output, err := h.Commandf(nil, sshCommandTimeout, "docker ps"); err != nil {
panic(err)
} else {
fmt.Println(string(output))
}
}

for _, h := range hosts {
fmt.Println("Get awm-relayer configuratuin")
awmConfig, err := h.GetAMWRelayerConfig()
if err != nil {
panic(err)
}
fmt.Println(awmConfig)
}
}
26 changes: 26 additions & 0 deletions node/config/awmRelayer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package services

import (
"path/filepath"

"github.com/ava-labs/avalanche-tooling-sdk-go/constants"
)

const DockerAWMRelayerPath = "/.awm-relayer"

func GetRemoteAMWRelayerConfig() string {
return filepath.Join(constants.CloudNodeAWMRelayerPath, constants.AWMRelayerConfigFilename)
}

func GetDockerAWMRelayerFolder() string {
return filepath.Join(DockerAWMRelayerPath, "storage")
}

func AWMRelayerFoldersToCreate() []string {
return []string{
filepath.Join(constants.CloudNodeAWMRelayerPath, "storage"),
}
}
15 changes: 11 additions & 4 deletions node/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ type NodeParams struct {
// AvalancheGoVersion is the version of Avalanche Go to install in the created node
AvalancheGoVersion string

// AWMRelayerVersion is the version of AWM Relayer to install in the created node
AWMRelayerVersion string

// UseStaticIP is whether the created node should have static IP attached to it. Note that
// assigning Static IP to a node may incur additional charges on AWS / GCP. There could also be
// a limit to how many Static IPs you can have in a region in AWS & GCP.
Expand Down Expand Up @@ -272,7 +275,7 @@ func provisionHost(node Node, nodeParams *NodeParams) error {
return err
}
case AWMRelayer:
if err := provisionAWMRelayerHost(node); err != nil {
if err := provisionAWMRelayerHost(node, nodeParams); err != nil {
return err
}
default:
Expand Down Expand Up @@ -329,9 +332,13 @@ func provisionMonitoringHost(node Node) error {
return nil
}

func provisionAWMRelayerHost(node Node) error { // stub
if err := node.ComposeSSHSetupAWMRelayer(); err != nil {
func provisionAWMRelayerHost(node Node, nodeParams *NodeParams) error {
if err := node.RunSSHSetupDockerService(); err != nil {
return err
}
return node.StartDockerComposeService(utils.GetRemoteComposeFile(), constants.ServiceAWMRelayer, constants.SSHLongRunningScriptTimeout)
if err := node.ComposeSSHSetupAWMRelayer(nodeParams.Network, nodeParams.AWMRelayerVersion); err != nil {
return err
}

return node.StartDockerCompose(constants.SSHScriptTimeout)
}
12 changes: 6 additions & 6 deletions node/docker_compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import (
)

type dockerComposeInputs struct {
WithMonitoring bool
WithAvalanchego bool
AvalanchegoVersion string
E2E bool
E2EIP string
E2ESuffix string
WithMonitoring bool
WithAvalanchego bool
Version string
E2E bool
E2EIP string
E2ESuffix string
}

//go:embed templates/*.docker-compose.yml
Expand Down
52 changes: 52 additions & 0 deletions node/docker_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@
package node

import (
"encoding/json"
"errors"
"fmt"
"os"
"slices"

"github.com/ava-labs/avalanche-tooling-sdk-go/constants"
"github.com/ava-labs/avalanche-tooling-sdk-go/interchain/relayer"
remoteconfig "github.com/ava-labs/avalanche-tooling-sdk-go/node/config"
"github.com/ava-labs/avalanche-tooling-sdk-go/utils"
"github.com/ava-labs/awm-relayer/config"
)

// PrepareAvalanchegoConfig creates the config files for the AvalancheGo
Expand Down Expand Up @@ -102,3 +108,49 @@ func prepareGrafanaConfig() (string, string, string, string, error) {
}
return grafanaConfigFile.Name(), grafanaDashboardsFile.Name(), grafanaDataSourceFile.Name(), grafanaPromDataSourceFile.Name(), nil
}

func (h *Node) GetAMWRelayerConfig() (*config.Config, error) {
remoteAWMConf := remoteconfig.GetRemoteAMWRelayerConfig()
if !slices.Contains(h.Roles, AWMRelayer) {
return nil, errors.New("node is not an AWM Relayer")
}

if configExists, err := h.FileExists(remoteAWMConf); err != nil || !configExists {
return nil, fmt.Errorf("%s: config file %s does not exist or not available", h.NodeID, remoteAWMConf)
}

c, err := h.ReadFileBytes(remoteAWMConf, constants.SSHFileOpsTimeout)
if err != nil {
return nil, fmt.Errorf("%s: failed to read config file %s: %w", h.NodeID, remoteAWMConf, err)
}

awmConfig := &config.Config{}
if err := json.Unmarshal(c, &awmConfig); err != nil {
return nil, fmt.Errorf("%s: failed to parse config file %s", h.NodeID, remoteAWMConf)
}

return awmConfig, nil
}

// AddBlockchainToRelayerConfig adds a blockchain to the AWM relayer config
func (h *Node) SetAMWRelayerConfig(awmConfig *config.Config) error {
if !slices.Contains(h.Roles, AWMRelayer) {
return errors.New("node is not a AWM Relayer")
}
tmpRelayerConfig, err := os.CreateTemp("", "avalancecli-awm-relayer-config-*.yml")
if err != nil {
return err
}
defer os.Remove(tmpRelayerConfig.Name())
configData, err := relayer.SerializeRelayerConfig(awmConfig)
if err != nil {
return err
}
if _, err := tmpRelayerConfig.Write(configData); err != nil {
return err
}
if err := h.Upload(tmpRelayerConfig.Name(), remoteconfig.GetRemoteAMWRelayerConfig(), constants.SSHFileOpsTimeout); err != nil {
return err
}
return h.RestartDockerComposeService(utils.GetRemoteComposeFile(), constants.ServiceAWMRelayer, constants.SSHLongRunningScriptTimeout)
}
35 changes: 35 additions & 0 deletions node/docker_logs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package node

import (
"fmt"
"strings"
"time"

"github.com/ava-labs/avalanche-tooling-sdk-go/constants"
)

func (h *Node) GetContainerLogs(containerName string, tailLines uint, timeout time.Duration) ([]string, error) {
if containerName == "" {
return nil, fmt.Errorf("container name cannot be empty")
}
tailLinesString := "all"
if tailLines > 0 {
tailLinesString = fmt.Sprintf("%d", tailLines)
}
output, err := h.Commandf(nil, timeout, "docker logs --tail %s %s", tailLinesString, containerName)
if err != nil {
return nil, err
}
return strings.Split(string(output), "\n"), nil
}

func (h *Node) GetAvalanchegoLogs(tailLines uint, timeout time.Duration) ([]string, error) {
return h.GetContainerLogs(constants.ServiceAvalanchego, tailLines, timeout)
}

func (h *Node) GetAWMRelayerLogs(tailLines uint, timeout time.Duration) ([]string, error) {
return h.GetContainerLogs(constants.ServiceAWMRelayer, tailLines, timeout)
}
45 changes: 37 additions & 8 deletions node/docker_ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ import (
"path/filepath"
"time"

"github.com/ava-labs/avalanche-tooling-sdk-go/avalanche"
"github.com/ava-labs/avalanche-tooling-sdk-go/constants"
"github.com/ava-labs/avalanche-tooling-sdk-go/interchain/relayer"
remoteconfig "github.com/ava-labs/avalanche-tooling-sdk-go/node/config"
"github.com/ava-labs/avalanche-tooling-sdk-go/utils"
"github.com/ava-labs/avalanchego/utils/logging"
)

// ValidateComposeFile validates a docker-compose file on a remote node.
Expand Down Expand Up @@ -46,12 +49,12 @@ func (h *Node) ComposeSSHSetupNode(networkID string, subnetsToTrack []string, av
constants.SSHScriptTimeout,
"templates/avalanchego.docker-compose.yml",
dockerComposeInputs{
AvalanchegoVersion: avalancheGoVersion,
WithMonitoring: withMonitoring,
WithAvalanchego: true,
E2E: utils.IsE2E(),
E2EIP: utils.E2EConvertIP(h.IP),
E2ESuffix: utils.E2ESuffix(h.IP),
Version: avalancheGoVersion,
WithMonitoring: withMonitoring,
WithAvalanchego: true,
E2E: utils.IsE2E(),
E2EIP: utils.E2EConvertIP(h.IP),
E2ESuffix: utils.E2ESuffix(h.IP),
})
}

Expand Down Expand Up @@ -114,9 +117,35 @@ func (h *Node) ComposeSSHSetupMonitoring() error {
dockerComposeInputs{})
}

func (h *Node) ComposeSSHSetupAWMRelayer() error {
func (h *Node) ComposeSSHSetupAWMRelayer(network avalanche.Network, awmRelayerVersion string) error {
for _, folder := range remoteconfig.AWMRelayerFoldersToCreate() {
if h.MkdirAll(folder, constants.SSHFileOpsTimeout) != nil {
return fmt.Errorf("error creating folder %s on node %s", folder, h.NodeID)
}
}

// provide basic configuration for AWM Relayer
tmpRelayerConfig, err := os.CreateTemp("", "avalancecli-awm-relayer-config-*.yml")
if err != nil {
return err
}
defer os.Remove(tmpRelayerConfig.Name())
awmConfig := relayer.CreateBaseRelayerConfig(logging.Info.LowerString(), remoteconfig.GetDockerAWMRelayerFolder(), 0, network)
configData, err := relayer.SerializeRelayerConfig(awmConfig)
if err != nil {
return err
}
if _, err := tmpRelayerConfig.Write(configData); err != nil {
return err
}
// upload the configuration file to the remote node
if err := h.Upload(tmpRelayerConfig.Name(), remoteconfig.GetRemoteAMWRelayerConfig(), constants.SSHFileOpsTimeout); err != nil {
return err
}
return h.ComposeOverSSH("Setup AWM Relayer",
constants.SSHScriptTimeout,
"templates/awmrelayer.docker-compose.yml",
dockerComposeInputs{})
dockerComposeInputs{
Version: awmRelayerVersion,
})
}
12 changes: 6 additions & 6 deletions node/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,12 @@ func (h *Node) RunSSHUpgradeAvalanchego(avalancheGoVersion string) error {
constants.SSHScriptTimeout,
"templates/avalanchego.docker-compose.yml",
dockerComposeInputs{
AvalanchegoVersion: avalancheGoVersion,
WithMonitoring: withMonitoring,
WithAvalanchego: true,
E2E: utils.IsE2E(),
E2EIP: utils.E2EConvertIP(h.IP),
E2ESuffix: utils.E2ESuffix(h.IP),
Version: avalancheGoVersion,
WithMonitoring: withMonitoring,
WithAvalanchego: true,
E2E: utils.IsE2E(),
E2EIP: utils.E2EConvertIP(h.IP),
E2ESuffix: utils.E2ESuffix(h.IP),
}); err != nil {
return err
}
Expand Down
Loading
Loading