Skip to content

Commit

Permalink
Node deploy cmd (ava-labs#1154)
Browse files Browse the repository at this point in the history
* almost

* add node deploy command

* mv find ledger indices to helpers

* fix endpoint issue

* lint

* avoid prompt for devnet endpoint on create

* nit

* move helpers only constants to helpers

* change to node devnet deploy

* address PR comments

* lint
  • Loading branch information
felipemadero authored Nov 9, 2023
1 parent 050556b commit 84adb48
Show file tree
Hide file tree
Showing 20 changed files with 601 additions and 350 deletions.
7 changes: 6 additions & 1 deletion cmd/nodecmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,17 @@ func createNodes(_ *cobra.Command, args []string) error {
}
clusterName := args[0]

endpoint := ""
if createDevnet {
// avoid prompt asking for endpoint if Devnet, it will be set later on when the info is available
endpoint = "toIgnore"
}
network, err := subnetcmd.GetNetworkFromCmdLineFlags(
false,
createDevnet,
createOnFuji,
createOnMainnet,
"",
endpoint,
[]models.NetworkKind{models.Fuji, models.Devnet},
)
if err != nil {
Expand Down
213 changes: 213 additions & 0 deletions cmd/nodecmd/create_devnet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
// Copyright (C) 2022, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package nodecmd

import (
_ "embed"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"time"

"github.com/ava-labs/avalanche-cli/pkg/ansible"
"github.com/ava-labs/avalanche-cli/pkg/constants"
"github.com/ava-labs/avalanche-cli/pkg/key"
"github.com/ava-labs/avalanche-cli/pkg/models"
"github.com/ava-labs/avalanche-cli/pkg/utils"
"github.com/ava-labs/avalanche-cli/pkg/ux"
"github.com/ava-labs/avalanchego/config"
coreth_params "github.com/ava-labs/coreth/params"
)

// difference between unlock schedule locktime and startime in original genesis
const (
genesisLocktimeStartimeDelta = 2836800
hexa0Str = "0x0"
defaultLocalCChainFundedAddress = "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"
defaultLocalCChainFundedBalance = "0x295BE96E64066972000000"
allocationCommonEthAddress = "0xb3d82b1367d362de99ab59a658165aff520cbd4d"
)

func generateCustomCchainGenesis() ([]byte, error) {
cChainGenesisMap := map[string]interface{}{}
cChainGenesisMap["config"] = coreth_params.AvalancheLocalChainConfig
cChainGenesisMap["nonce"] = hexa0Str
cChainGenesisMap["timestamp"] = hexa0Str
cChainGenesisMap["extraData"] = "0x00"
cChainGenesisMap["gasLimit"] = "0x5f5e100"
cChainGenesisMap["difficulty"] = hexa0Str
cChainGenesisMap["mixHash"] = "0x0000000000000000000000000000000000000000000000000000000000000000"
cChainGenesisMap["coinbase"] = "0x0000000000000000000000000000000000000000"
cChainGenesisMap["alloc"] = map[string]interface{}{
defaultLocalCChainFundedAddress: map[string]interface{}{
"balance": defaultLocalCChainFundedBalance,
},
}
cChainGenesisMap["number"] = hexa0Str
cChainGenesisMap["gasUsed"] = hexa0Str
cChainGenesisMap["parentHash"] = "0x0000000000000000000000000000000000000000000000000000000000000000"
return json.Marshal(cChainGenesisMap)
}

func generateCustomGenesis(networkID uint32, walletAddr string, stakingAddr string, nodeIDs []string) ([]byte, error) {
genesisMap := map[string]interface{}{}

// cchain
cChainGenesisBytes, err := generateCustomCchainGenesis()
if err != nil {
return nil, err
}
genesisMap["cChainGenesis"] = string(cChainGenesisBytes)

// pchain genesis
genesisMap["networkID"] = networkID
startTime := time.Now().Unix()
genesisMap["startTime"] = startTime
initialStakers := []map[string]interface{}{}
for _, nodeID := range nodeIDs {
initialStaker := map[string]interface{}{
"nodeID": nodeID,
"rewardAddress": walletAddr,
"delegationFee": 1000000,
}
initialStakers = append(initialStakers, initialStaker)
}
genesisMap["initialStakeDuration"] = 31536000
genesisMap["initialStakeDurationOffset"] = 5400
genesisMap["initialStakers"] = initialStakers
lockTime := startTime + genesisLocktimeStartimeDelta
allocations := []interface{}{}
alloc := map[string]interface{}{
"avaxAddr": walletAddr,
"ethAddr": allocationCommonEthAddress,
"initialAmount": 300000000000000000,
"unlockSchedule": []interface{}{
map[string]interface{}{"amount": 20000000000000000},
map[string]interface{}{"amount": 10000000000000000, "locktime": lockTime},
},
}
allocations = append(allocations, alloc)
alloc = map[string]interface{}{
"avaxAddr": stakingAddr,
"ethAddr": allocationCommonEthAddress,
"initialAmount": 0,
"unlockSchedule": []interface{}{
map[string]interface{}{"amount": 10000000000000000, "locktime": lockTime},
},
}
allocations = append(allocations, alloc)
genesisMap["allocations"] = allocations
genesisMap["initialStakedFunds"] = []interface{}{
stakingAddr,
}
genesisMap["message"] = "{{ fun_quote }}"

return json.MarshalIndent(genesisMap, "", " ")
}

func setupDevnet(clusterName string) error {
if err := checkCluster(clusterName); err != nil {
return err
}
if err := setupAnsible(clusterName); err != nil {
return err
}
ansibleHostIDs, err := ansible.GetAnsibleHostsFromInventory(app.GetAnsibleInventoryDirPath(clusterName))
if err != nil {
return err
}
ansibleHosts, err := ansible.GetHostMapfromAnsibleInventory(app.GetAnsibleInventoryDirPath(clusterName))
if err != nil {
return err
}
cloudHostIDs, err := utils.MapWithError(ansibleHostIDs, func(s string) (string, error) { _, o, err := models.HostAnsibleIDToCloudID(s); return o, err })
if err != nil {
return err
}
nodeIDs, err := utils.MapWithError(cloudHostIDs, func(s string) (string, error) {
n, err := getNodeID(app.GetNodeInstanceDirPath(s))
return n.String(), err
})
if err != nil {
return err
}

// set devnet network
network := models.NewDevnetNetwork(ansibleHosts[ansibleHostIDs[0]].IP, 9650)
ux.Logger.PrintToUser("Devnet Network Id: %d", network.ID)
ux.Logger.PrintToUser("Devnet Endpoint: %s", network.Endpoint)

// get random staking key for devnet genesis
k, err := key.NewSoft(network.ID)
if err != nil {
return err
}
stakingAddrStr := k.X()[0]

// get ewoq key as funded key for devnet genesis
k, err = key.LoadEwoq(network.ID)
if err != nil {
return err
}
walletAddrStr := k.X()[0]

// create genesis file at each node dir
genesisBytes, err := generateCustomGenesis(network.ID, walletAddrStr, stakingAddrStr, nodeIDs)
if err != nil {
return err
}
for _, cloudHostID := range cloudHostIDs {
outFile := filepath.Join(app.GetNodeInstanceDirPath(cloudHostID), "genesis.json")
if err := os.WriteFile(outFile, genesisBytes, constants.WriteReadReadPerms); err != nil {
return err
}
}

// create avalanchego conf node.json at each node dir
bootstrapIPs := []string{}
bootstrapIDs := []string{}
for i, ansibleHostID := range ansibleHostIDs {
cloudHostID := cloudHostIDs[i]
confMap := map[string]interface{}{}
confMap[config.HTTPHostKey] = ""
confMap[config.PublicIPKey] = ansibleHosts[ansibleHostID].IP
confMap[config.NetworkNameKey] = fmt.Sprintf("network-%d", network.ID)
confMap[config.BootstrapIDsKey] = strings.Join(bootstrapIDs, ",")
confMap[config.BootstrapIPsKey] = strings.Join(bootstrapIPs, ",")
confMap[config.GenesisFileKey] = "/home/ubuntu/.avalanchego/configs/genesis.json"
bootstrapIDs = append(bootstrapIDs, nodeIDs[i])
bootstrapIPs = append(bootstrapIPs, ansibleHosts[ansibleHostID].IP+":9651")
confBytes, err := json.MarshalIndent(confMap, "", " ")
if err != nil {
return err
}
outFile := filepath.Join(app.GetNodeInstanceDirPath(cloudHostID), "node.json")
if err := os.WriteFile(outFile, confBytes, constants.WriteReadReadPerms); err != nil {
return err
}
}

// update node/s genesis + conf and start
if err := ansible.RunAnsiblePlaybookSetupDevnet(
app.GetAnsibleDir(),
strings.Join(ansibleHostIDs, ","),
app.GetNodesDir(),
app.GetAnsibleInventoryDirPath(clusterName),
); err != nil {
return err
}

// update cluster config with network information
clustersConfig, err := app.LoadClustersConfig()
if err != nil {
return err
}
clusterConfig := clustersConfig.Clusters[clusterName]
clustersConfig.Clusters[clusterName] = models.ClusterConfig{
Network: network,
Nodes: clusterConfig.Nodes,
}
return app.WriteClustersConfigFile(&clustersConfig)
}
105 changes: 105 additions & 0 deletions cmd/nodecmd/deploy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright (C) 2022, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package nodecmd

import (
"fmt"

"github.com/ava-labs/avalanche-cli/cmd/subnetcmd"
"github.com/ava-labs/avalanche-cli/pkg/models"
"github.com/ava-labs/avalanche-cli/pkg/ux"
"github.com/spf13/cobra"
)

func newDeployCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "deploy [clusterName] [subnetName]",
Short: "(ALPHA Warning) Deploy a subnet into a devnet cluster",
Long: `(ALPHA Warning) This command is currently in experimental mode.
The node devnet deploy command deploys a subnet into a devnet cluster, creating subnet and blockchain txs for it.
It saves the deploy info both locally and remotely.
`,
SilenceUsage: true,
Args: cobra.ExactArgs(2),
RunE: deploySubnet,
}
return cmd
}

func deploySubnet(cmd *cobra.Command, args []string) error {
clusterName := args[0]
subnetName := args[1]
if err := checkCluster(clusterName); err != nil {
return err
}
if err := setupAnsible(clusterName); err != nil {
return err
}
if _, err := subnetcmd.ValidateSubnetNameAndGetChains([]string{subnetName}); err != nil {
return err
}
clustersConfig, err := app.LoadClustersConfig()
if err != nil {
return err
}
if clustersConfig.Clusters[clusterName].Network.Kind != models.Devnet {
return fmt.Errorf("node deploy command must be applied to devnet clusters")
}

notHealthyNodes, err := checkClusterIsHealthy(clusterName)
if err != nil {
return err
}
if len(notHealthyNodes) > 0 {
return fmt.Errorf("node(s) %s are not healthy yet, please try again later", notHealthyNodes)
}
incompatibleNodes, err := checkAvalancheGoVersionCompatible(clusterName, subnetName)
if err != nil {
return err
}
if len(incompatibleNodes) > 0 {
sc, err := app.LoadSidecar(subnetName)
if err != nil {
return err
}
ux.Logger.PrintToUser("Either modify your Avalanche Go version or modify your VM version")
ux.Logger.PrintToUser("To modify your Avalanche Go version: https://docs.avax.network/nodes/maintain/upgrade-your-avalanchego-node")
switch sc.VM {
case models.SubnetEvm:
ux.Logger.PrintToUser("To modify your Subnet-EVM version: https://docs.avax.network/build/subnet/upgrade/upgrade-subnet-vm")
case models.CustomVM:
ux.Logger.PrintToUser("To modify your Custom VM binary: avalanche subnet upgrade vm %s --config", subnetName)
}
ux.Logger.PrintToUser("Yoy can use \"avalanche node upgrade\" to upgrade Avalanche Go and/or Subnet-EVM to their latest versions")
return fmt.Errorf("the Avalanche Go version of node(s) %s is incompatible with VM RPC version of %s", incompatibleNodes, subnetName)
}

deployLocal := false
deployDevnet := true
deployTestnet := false
deployMainnet := false
endpoint := clustersConfig.Clusters[clusterName].Network.Endpoint
keyNameParam := ""
useLedgerParam := false
useEwoqParam := true
sameControlKey := true

if err := subnetcmd.CallDeploy(
cmd,
subnetName,
deployLocal,
deployDevnet,
deployTestnet,
deployMainnet,
endpoint,
keyNameParam,
useLedgerParam,
useEwoqParam,
sameControlKey,
); err != nil {
return err
}
ux.Logger.PrintToUser("Subnet successfully deployed into devnet!")
return nil
}
Loading

0 comments on commit 84adb48

Please sign in to comment.