forked from Mouradif/avalanche-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
1 parent
050556b
commit 84adb48
Showing
20 changed files
with
601 additions
and
350 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.