mirror of
https://github.com/luxfi/cli.git
synced 2026-10-30 17:01:20 +00:00
754 lines
23 KiB
Go
754 lines
23 KiB
Go
// Copyright (C) 2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
package nodecmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
sdkutils "github.com/luxfi/cli/v2/sdk/utils"
|
|
|
|
"github.com/luxfi/cli/v2/pkg/dependencies"
|
|
|
|
"github.com/luxfi/cli/v2/cmd/flags"
|
|
"github.com/luxfi/cli/v2/pkg/blockchain"
|
|
"github.com/luxfi/cli/v2/pkg/cobrautils"
|
|
"github.com/luxfi/cli/v2/pkg/constants"
|
|
"github.com/luxfi/cli/v2/pkg/contract"
|
|
"github.com/luxfi/cli/v2/pkg/keychain"
|
|
"github.com/luxfi/cli/v2/pkg/localnet"
|
|
"github.com/luxfi/cli/v2/pkg/models"
|
|
"github.com/luxfi/cli/v2/pkg/networkoptions"
|
|
"github.com/luxfi/cli/v2/pkg/node"
|
|
"github.com/luxfi/cli/v2/pkg/prompts"
|
|
"github.com/luxfi/cli/v2/pkg/prompts/comparator"
|
|
"github.com/luxfi/cli/v2/pkg/signatureaggregator"
|
|
"github.com/luxfi/cli/v2/pkg/subnet"
|
|
"github.com/luxfi/cli/v2/pkg/utils"
|
|
"github.com/luxfi/cli/v2/pkg/ux"
|
|
"github.com/luxfi/cli/v2/pkg/validatormanager"
|
|
"github.com/luxfi/node/v2/v2/api/info"
|
|
"github.com/luxfi/node/v2/v2/config"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/node/v2/v2/utils/formatting/address"
|
|
"github.com/luxfi/node/v2/v2/utils/logging"
|
|
"github.com/luxfi/node/v2/v2/utils/units"
|
|
"github.com/luxfi/node/v2/v2/vms/platformvm"
|
|
"github.com/luxfi/node/v2/v2/vms/platformvm/api"
|
|
warpMessage "github.com/luxfi/warp"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
luxdBinaryPath string
|
|
|
|
bootstrapIDs []string
|
|
bootstrapIPs []string
|
|
genesisPath string
|
|
upgradePath string
|
|
stakingTLSKeyPaths []string
|
|
stakingCertKeyPaths []string
|
|
stakingSignerKeyPaths []string
|
|
numNodes uint32
|
|
nodeConfigPath string
|
|
partialSync bool
|
|
stakeAmount uint64
|
|
balanceLUX float64
|
|
remainingBalanceOwnerAddr string
|
|
disableOwnerAddr string
|
|
delegationFee uint16
|
|
minimumStakeDuration uint64
|
|
rewardsRecipientAddr string
|
|
latestLuxdReleaseVersion bool
|
|
latestLuxdPreReleaseVersion bool
|
|
validatorManagerAddress string
|
|
useACP99 bool
|
|
httpPorts []uint
|
|
stakingPorts []uint
|
|
localValidateFlags NodeLocalValidateFlags
|
|
)
|
|
|
|
// const snapshotName = "local_snapshot"
|
|
func newLocalCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "local",
|
|
Short: "Suite of commands for a local lux node",
|
|
Long: `The node local command suite provides a collection of commands related to local nodes`,
|
|
RunE: cobrautils.CommandSuiteUsage,
|
|
}
|
|
// node local start
|
|
cmd.AddCommand(newLocalStartCmd())
|
|
// node local stop
|
|
cmd.AddCommand(newLocalStopCmd())
|
|
// node local destroy
|
|
cmd.AddCommand(newLocalDestroyCmd())
|
|
// node local track
|
|
cmd.AddCommand(newLocalTrackCmd())
|
|
// node local status
|
|
cmd.AddCommand(newLocalStatusCmd())
|
|
// node local validate
|
|
cmd.AddCommand(newLocalValidateCmd())
|
|
return cmd
|
|
}
|
|
|
|
func newLocalStartCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "start [clusterName]",
|
|
Short: "Create or restart Lux nodes on local machine",
|
|
Long: `The node local start command creates Lux nodes on the local machine,
|
|
or restarts previously created ones.
|
|
Once this command is completed, you will have to wait for the Lux node
|
|
to finish bootstrapping on the primary network before running further
|
|
commands on it, e.g. validating a Subnet.
|
|
|
|
You can check the bootstrapping status by running lux node status local.
|
|
`,
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: localStartNode,
|
|
PersistentPostRun: handlePostRun,
|
|
}
|
|
networkoptions.AddNetworkFlagsToCmd(cmd, &globalNetworkFlags, false, networkoptions.DefaultSupportedNetworkOptions)
|
|
cmd.Flags().BoolVar(&latestLuxdReleaseVersion, "latest-luxd-version", true, "install latest luxd release version on node/s")
|
|
cmd.Flags().BoolVar(&latestLuxdPreReleaseVersion, "latest-luxd-pre-release-version", false, "install latest luxd pre-release version on node/s")
|
|
cmd.Flags().StringVar(&useCustomLuxgoVersion, "custom-luxd-version", "", "install given luxd version on node/s")
|
|
cmd.Flags().StringVar(&luxdBinaryPath, "luxd-path", "", "use this luxd binary path")
|
|
cmd.Flags().StringArrayVar(&bootstrapIDs, "bootstrap-id", []string{}, "nodeIDs of bootstrap nodes")
|
|
cmd.Flags().StringArrayVar(&bootstrapIPs, "bootstrap-ip", []string{}, "IP:port pairs of bootstrap nodes")
|
|
cmd.Flags().StringVar(&genesisPath, "genesis", "", "path to genesis file")
|
|
cmd.Flags().StringVar(&upgradePath, "upgrade", "", "path to upgrade file")
|
|
cmd.Flags().StringSliceVar(&stakingTLSKeyPaths, "staking-tls-key-path", []string{}, "path to provided staking tls key for node(s)")
|
|
cmd.Flags().StringSliceVar(&stakingCertKeyPaths, "staking-cert-key-path", []string{}, "path to provided staking cert key for node(s)")
|
|
cmd.Flags().StringSliceVar(&stakingSignerKeyPaths, "staking-signer-key-path", []string{}, "path to provided staking signer key for node(s)")
|
|
cmd.Flags().Uint32Var(&numNodes, "num-nodes", 1, "number of Lux nodes to create on local machine")
|
|
cmd.Flags().StringVar(&nodeConfigPath, "node-config", "", "path to common luxd config settings for all nodes")
|
|
cmd.Flags().BoolVar(&partialSync, "partial-sync", true, "primary network partial sync")
|
|
cmd.Flags().UintSliceVar(&httpPorts, "http-port", []uint{}, "http port for node(s)")
|
|
cmd.Flags().UintSliceVar(&stakingPorts, "staking-port", []uint{}, "staking port for node(s)")
|
|
return cmd
|
|
}
|
|
|
|
func newLocalStopCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "stop [clusterName]",
|
|
Short: "Stop local nodes",
|
|
Long: `Stop local nodes.`,
|
|
Args: cobra.MaximumNArgs(1),
|
|
RunE: localStopNode,
|
|
}
|
|
}
|
|
|
|
func newLocalTrackCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "track [clusterName] [blockchainName]",
|
|
Short: "Track specified blockchain with local node",
|
|
Long: "Track specified blockchain with local node",
|
|
Args: cobra.ExactArgs(2),
|
|
RunE: localTrack,
|
|
}
|
|
return cmd
|
|
}
|
|
|
|
func newLocalDestroyCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "destroy [clusterName]",
|
|
Short: "Cleanup local node",
|
|
Long: `Cleanup local node.`,
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: localDestroyNode,
|
|
}
|
|
}
|
|
|
|
func newLocalStatusCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "status",
|
|
Short: "Get status of local node",
|
|
Long: `Get status of local node.`,
|
|
Args: cobra.MaximumNArgs(1),
|
|
RunE: localStatus,
|
|
}
|
|
|
|
cmd.Flags().StringVar(&blockchainName, "l1", "", "specify the blockchain the node is syncing with")
|
|
cmd.Flags().StringVar(&blockchainName, "blockchain", "", "specify the blockchain the node is syncing with")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func localStartNode(_ *cobra.Command, args []string) error {
|
|
clusterName := args[0]
|
|
var (
|
|
err error
|
|
genesis []byte
|
|
upgrade []byte
|
|
)
|
|
if genesisPath != "" {
|
|
genesis, err = os.ReadFile(genesisPath)
|
|
if err != nil {
|
|
return fmt.Errorf("could not read genesis at %s: %w", genesisPath, err)
|
|
}
|
|
}
|
|
if upgradePath != "" {
|
|
upgrade, err = os.ReadFile(upgradePath)
|
|
if err != nil {
|
|
return fmt.Errorf("could not read upgrade at %s: %w", upgradePath, err)
|
|
}
|
|
}
|
|
connectionSettings := localnet.ConnectionSettings{
|
|
Genesis: genesis,
|
|
Upgrade: upgrade,
|
|
BootstrapIDs: bootstrapIDs,
|
|
BootstrapIPs: bootstrapIPs,
|
|
}
|
|
if len(stakingSignerKeyPaths) != len(stakingCertKeyPaths) || len(stakingSignerKeyPaths) != len(stakingTLSKeyPaths) {
|
|
return fmt.Errorf("staking key inputs must be for the same number of nodes")
|
|
}
|
|
nodeSettingsLen := max(len(stakingSignerKeyPaths), len(httpPorts), len(stakingPorts))
|
|
nodeSettings := make([]localnet.NodeSetting, nodeSettingsLen)
|
|
for i := range nodeSettingsLen {
|
|
nodeSetting := localnet.NodeSetting{}
|
|
if i < len(stakingSignerKeyPaths) {
|
|
stakingSignerKey, err := os.ReadFile(stakingSignerKeyPaths[i])
|
|
if err != nil {
|
|
return fmt.Errorf("could not read staking signer key at %s: %w", stakingSignerKeyPaths[i], err)
|
|
}
|
|
stakingCertKey, err := os.ReadFile(stakingCertKeyPaths[i])
|
|
if err != nil {
|
|
return fmt.Errorf("could not read staking cert key at %s: %w", stakingCertKeyPaths[i], err)
|
|
}
|
|
stakingTLSKey, err := os.ReadFile(stakingTLSKeyPaths[i])
|
|
if err != nil {
|
|
return fmt.Errorf("could not read staking TLS key at %s: %w", stakingTLSKeyPaths[i], err)
|
|
}
|
|
nodeSetting.StakingSignerKey = stakingSignerKey
|
|
nodeSetting.StakingCertKey = stakingCertKey
|
|
nodeSetting.StakingTLSKey = stakingTLSKey
|
|
}
|
|
if i < len(httpPorts) {
|
|
nodeSetting.HTTPPort = uint64(httpPorts[i])
|
|
}
|
|
if i < len(stakingPorts) {
|
|
nodeSetting.StakingPort = uint64(stakingPorts[i])
|
|
}
|
|
nodeSettings[i] = nodeSetting
|
|
}
|
|
|
|
network := models.UndefinedNetwork
|
|
if !localnet.LocalClusterExists(app, clusterName) {
|
|
network, err = networkoptions.GetNetworkFromCmdLineFlags(
|
|
app,
|
|
"",
|
|
globalNetworkFlags,
|
|
false,
|
|
true,
|
|
networkoptions.DefaultSupportedNetworkOptions,
|
|
"",
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if useCustomLuxgoVersion != "" {
|
|
// TODO: we'll have to refactor all these when we consolidate input and flag handling for dependency versioning
|
|
if err = dependencies.CheckVersionIsOverMin(app, constants.LuxdRepoName, network, useCustomLuxgoVersion); err != nil {
|
|
return err
|
|
}
|
|
latestLuxdPreReleaseVersion = false
|
|
latestLuxdReleaseVersion = false
|
|
}
|
|
luxdVersionSetting := dependencies.LuxdVersionSettings{
|
|
UseCustomLuxgoVersion: useCustomLuxgoVersion,
|
|
UseLatestLuxgoPreReleaseVersion: latestLuxdPreReleaseVersion,
|
|
UseLatestLuxgoReleaseVersion: latestLuxdReleaseVersion,
|
|
}
|
|
nodeConfig := make(map[string]interface{})
|
|
if nodeConfigPath != "" {
|
|
var err error
|
|
nodeConfig, err = utils.ReadJSON(nodeConfigPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if partialSync {
|
|
nodeConfig[config.PartialSyncPrimaryNetworkKey] = true
|
|
}
|
|
return node.StartLocalNode(
|
|
app,
|
|
clusterName,
|
|
luxdBinaryPath,
|
|
numNodes,
|
|
nodeConfig,
|
|
connectionSettings,
|
|
nodeSettings,
|
|
luxdVersionSetting,
|
|
network,
|
|
)
|
|
}
|
|
|
|
func localStopNode(_ *cobra.Command, args []string) error {
|
|
if len(args) == 1 {
|
|
clusterName := args[0]
|
|
|
|
// want to be able to stop clusters even if they are only partially operative
|
|
if running, err := localnet.LocalClusterIsPartiallyRunning(app, clusterName); err != nil {
|
|
return err
|
|
} else if !running {
|
|
ux.Logger.PrintToUser("cluster is not running")
|
|
} else {
|
|
if err := localnet.LocalClusterStop(app, clusterName); err != nil {
|
|
return err
|
|
}
|
|
ux.Logger.GreenCheckmarkToUser("luxd stopped")
|
|
}
|
|
return nil
|
|
}
|
|
clusterNames, err := localnet.GetRunningLocalClusters(app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(clusterNames) == 0 {
|
|
ux.Logger.PrintToUser("no clusters to stop")
|
|
return nil
|
|
}
|
|
for _, clusterName := range clusterNames {
|
|
if err := localnet.LocalClusterStop(app, clusterName); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
ux.Logger.GreenCheckmarkToUser("luxd stopped")
|
|
return nil
|
|
}
|
|
|
|
func localDestroyNode(_ *cobra.Command, args []string) error {
|
|
clusterName := args[0]
|
|
if err := localnet.LocalClusterRemove(app, clusterName); err != nil {
|
|
return err
|
|
}
|
|
ux.Logger.GreenCheckmarkToUser("Local node %s cleaned up.", clusterName)
|
|
return nil
|
|
}
|
|
|
|
func localTrack(_ *cobra.Command, args []string) error {
|
|
clusterName := args[0]
|
|
blockchainName := args[1]
|
|
return localnet.LocalClusterTrackSubnet(
|
|
app,
|
|
ux.Logger.PrintToUser,
|
|
clusterName,
|
|
blockchainName,
|
|
)
|
|
}
|
|
|
|
func localStatus(_ *cobra.Command, args []string) error {
|
|
clusterName := ""
|
|
if len(args) > 0 {
|
|
clusterName = args[0]
|
|
}
|
|
if blockchainName != "" && clusterName == "" {
|
|
return fmt.Errorf("--blockchain flag is only supported if clusterName is specified")
|
|
}
|
|
return node.LocalStatus(app, clusterName, blockchainName)
|
|
}
|
|
|
|
func notImplementedForLocal(what string) error {
|
|
ux.Logger.PrintToUser("Unsupported cmd: %s is not supported by local clusters", logging.LightBlue.Wrap(what))
|
|
return nil
|
|
}
|
|
|
|
type NodeLocalValidateFlags struct {
|
|
RPC string
|
|
SigAggFlags flags.SignatureAggregatorFlags
|
|
}
|
|
|
|
func newLocalValidateCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "validate [clusterName]",
|
|
Short: "Validate a specified L1 with an Lux Node set up on local machine (PoS only)",
|
|
Long: `Use Lux Node set up on local machine to set up specified L1 by providing the
|
|
RPC URL of the L1.
|
|
|
|
This command can only be used to validate Proof of Stake L1.`,
|
|
RunE: localValidate,
|
|
PreRunE: cobra.ExactArgs(1),
|
|
}
|
|
flags.AddRPCFlagToCmd(cmd, app, &localValidateFlags.RPC)
|
|
sigAggGroup := flags.AddSignatureAggregatorFlagsToCmd(cmd, &localValidateFlags.SigAggFlags)
|
|
cmd.Flags().StringVar(&blockchainName, "l1", "", "specify the blockchain the node is syncing with")
|
|
cmd.Flags().StringVar(&blockchainName, "blockchain", "", "specify the blockchain the node is syncing with")
|
|
cmd.Flags().Uint64Var(&stakeAmount, "stake-amount", 0, "amount of tokens to stake")
|
|
cmd.Flags().Float64Var(&balanceLUX, "balance", 0, "amount of LUX to increase validator's balance by")
|
|
cmd.Flags().Uint16Var(&delegationFee, "delegation-fee", 100, "delegation fee (in bips)")
|
|
cmd.Flags().StringVar(&remainingBalanceOwnerAddr, "remaining-balance-owner", "", "P-Chain address that will receive any leftover LUX from the validator when it is removed from Subnet")
|
|
cmd.Flags().StringVar(&disableOwnerAddr, "disable-owner", "", "P-Chain address that will able to disable the validator with a P-Chain transaction")
|
|
cmd.Flags().Uint64Var(&minimumStakeDuration, "minimum-stake-duration", constants.PoSL1MinimumStakeDurationSeconds, "minimum stake duration (in seconds)")
|
|
cmd.Flags().StringVar(&rewardsRecipientAddr, "rewards-recipient", "", "EVM address that will receive the validation rewards")
|
|
cmd.Flags().StringVar(&validatorManagerAddress, "validator-manager-address", "", "validator manager address")
|
|
cmd.Flags().BoolVar(&useACP99, "acp99", true, "use ACP99 contracts instead of v1.0.0 for validator managers")
|
|
cmd.SetHelpFunc(flags.WithGroupedHelp([]flags.GroupedFlags{sigAggGroup}))
|
|
return cmd
|
|
}
|
|
|
|
func localValidate(_ *cobra.Command, args []string) error {
|
|
clusterName := ""
|
|
if len(args) > 0 {
|
|
clusterName = args[0]
|
|
}
|
|
|
|
if clusterName == "" {
|
|
return fmt.Errorf("local cluster name cannot be empty")
|
|
}
|
|
|
|
if !localnet.LocalClusterExists(app, clusterName) {
|
|
return fmt.Errorf("local cluster %q not found, please create it first using lux node local start %q", clusterName, clusterName)
|
|
}
|
|
|
|
network, err := networkoptions.GetNetworkFromCmdLineFlags(
|
|
app,
|
|
"",
|
|
globalNetworkFlags,
|
|
true,
|
|
false,
|
|
networkoptions.DefaultSupportedNetworkOptions,
|
|
"",
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// TODO: will estimate fee in subsecuent PR
|
|
fee := uint64(0)
|
|
kc, err := keychain.GetKeychainFromCmdLineFlags(
|
|
app,
|
|
"to pay for transaction fees on P-Chain",
|
|
network,
|
|
keyName,
|
|
useEwoq,
|
|
useLedger,
|
|
ledgerAddresses,
|
|
fee,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// should take input prior to here for stake amount, delegation fee, and min stake duration
|
|
if stakeAmount == 0 {
|
|
stakeAmount, err = app.Prompt.CaptureUint64Compare(
|
|
"Enter the amount of token to stake for each validator",
|
|
[]comparator.Comparator{
|
|
{
|
|
Label: "Positive",
|
|
Type: comparator.MoreThan,
|
|
Value: 0,
|
|
},
|
|
},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if localValidateFlags.RPC == "" {
|
|
localValidateFlags.RPC, err = app.Prompt.CaptureURL("What is the RPC endpoint?", false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
_, blockchainID, err := utils.SplitLuxgoRPCURI(localValidateFlags.RPC)
|
|
// if there is error that means RPC URL did not contain blockchain in it
|
|
// RPC might be in the format of something like https://etna.lux-dev.network
|
|
// We will prompt for blockchainID in that case
|
|
if err != nil {
|
|
blockchainID, err = app.Prompt.CaptureString("What is the Blockchain ID of the L1?")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if validatorManagerAddress == "" {
|
|
validatorManagerAddressAddrFmt, err := app.Prompt.CaptureAddress("What is the address of the Validator Manager?")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
validatorManagerAddress = validatorManagerAddressAddrFmt.String()
|
|
}
|
|
|
|
chainSpec := contract.ChainSpec{
|
|
BlockchainID: blockchainID,
|
|
}
|
|
if balanceLUX == 0 {
|
|
availableBalance, err := utils.GetNetworkBalance(kc.Addresses().List(), network.Endpoint)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
prompt := "How many LUX do you want to each validator to start with?"
|
|
balanceLUX, err = blockchain.PromptValidatorBalance(app, float64(availableBalance)/float64(units.Lux), prompt)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
balance := uint64(balanceLUX * float64(units.Lux))
|
|
|
|
if remainingBalanceOwnerAddr == "" {
|
|
remainingBalanceOwnerAddr, err = blockchain.GetKeyForChangeOwner(app, network)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
remainingBalanceOwnerAddrID, err := address.ParseToIDs([]string{remainingBalanceOwnerAddr})
|
|
if err != nil {
|
|
return fmt.Errorf("failure parsing remaining balanche owner address %s: %w", remainingBalanceOwnerAddr, err)
|
|
}
|
|
remainingBalanceOwners := warpMessage.PChainOwner{
|
|
Threshold: 1,
|
|
Addresses: remainingBalanceOwnerAddrID,
|
|
}
|
|
|
|
if disableOwnerAddr == "" {
|
|
disableOwnerAddr, err = prompts.PromptAddress(
|
|
app.Prompt,
|
|
"be able to disable the validator using P-Chain transactions",
|
|
app.GetKeyDir(),
|
|
app.GetKey,
|
|
"",
|
|
network,
|
|
prompts.PChainFormat,
|
|
"Enter P-Chain address (Example: P-...)",
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
disableOwnerAddrID, err := address.ParseToIDs([]string{disableOwnerAddr})
|
|
if err != nil {
|
|
return fmt.Errorf("failure parsing disable owner address %s: %w", disableOwnerAddr, err)
|
|
}
|
|
disableOwners := warpMessage.PChainOwner{
|
|
Threshold: 1,
|
|
Addresses: disableOwnerAddrID,
|
|
}
|
|
|
|
ux.Logger.PrintToUser("A private key is needed to pay for initialization of the validator's registration (Blockchain gas token).")
|
|
payerPrivateKey, err := prompts.PromptPrivateKey(
|
|
app.Prompt,
|
|
"pay the fee",
|
|
app.GetKeyDir(),
|
|
app.GetKey,
|
|
"",
|
|
"",
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
extraAggregatorPeers, err := blockchain.GetAggregatorExtraPeers(app, clusterName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
aggregatorLogger, err := signatureaggregator.NewSignatureAggregatorLogger(
|
|
localValidateFlags.SigAggFlags.AggregatorLogLevel,
|
|
localValidateFlags.SigAggFlags.AggregatorLogToStdout,
|
|
app.GetAggregatorLogDir(clusterName),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
net, err := localnet.GetLocalCluster(app, clusterName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if useACP99 {
|
|
ux.Logger.PrintToUser(logging.Yellow.Wrap("Validator Manager Protocol: V2"))
|
|
} else {
|
|
ux.Logger.PrintToUser(logging.Yellow.Wrap("Validator Manager Protocol: v1.0.0"))
|
|
}
|
|
|
|
for _, node := range net.Nodes {
|
|
if err = addAsValidator(
|
|
network,
|
|
node.URI,
|
|
chainSpec,
|
|
remainingBalanceOwners, disableOwners,
|
|
extraAggregatorPeers,
|
|
aggregatorLogger,
|
|
kc,
|
|
balance,
|
|
payerPrivateKey,
|
|
validatorManagerAddress,
|
|
useACP99,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
ux.Logger.PrintToUser(" ")
|
|
ux.Logger.GreenCheckmarkToUser("All validators are successfully added to the L1")
|
|
return nil
|
|
}
|
|
|
|
func addAsValidator(
|
|
network models.Network,
|
|
nodeURI string,
|
|
chainSpec contract.ChainSpec,
|
|
remainingBalanceOwners, disableOwners warpMessage.PChainOwner,
|
|
extraAggregatorPeers []info.Peer,
|
|
aggregatorLogger logging.Logger,
|
|
kc *keychain.Keychain,
|
|
balance uint64,
|
|
payerPrivateKey string,
|
|
validatorManagerAddressStr string,
|
|
useACP99 bool,
|
|
) error {
|
|
// get node data
|
|
nodeIDStr, publicKey, pop, err := utils.GetNodeID(nodeURI)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nodeID, err := ids.NodeIDFromString(nodeIDStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if rewardsRecipientAddr == "" {
|
|
rewardsRecipientAddr, err = prompts.PromptAddress(
|
|
app.Prompt,
|
|
"receive the validation rewards",
|
|
app.GetKeyDir(),
|
|
app.GetKey,
|
|
"",
|
|
network,
|
|
prompts.EVMFormat,
|
|
"Address",
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
ux.Logger.PrintToUser(" ")
|
|
ux.Logger.PrintToUser("Adding validator %s", nodeIDStr)
|
|
ux.Logger.PrintToUser(" ")
|
|
|
|
blockchainTimestamp, err := blockchain.GetBlockchainTimestamp(network)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get blockchain timestamp: %w", err)
|
|
}
|
|
expiry := uint64(blockchainTimestamp.Add(constants.DefaultValidationIDExpiryDuration).Unix())
|
|
|
|
blsInfo, err := blockchain.ConvertToBLSProofOfPossession(publicKey, pop)
|
|
if err != nil {
|
|
return fmt.Errorf("failure parsing BLS info: %w", err)
|
|
}
|
|
|
|
if err = signatureaggregator.UpdateSignatureAggregatorPeers(app, network, extraAggregatorPeers, aggregatorLogger); err != nil {
|
|
return err
|
|
}
|
|
aggregatorCtx, aggregatorCancel := sdkutils.GetTimedContext(constants.SignatureAggregatorTimeout)
|
|
defer aggregatorCancel()
|
|
signatureAggregatorEndpoint, err := signatureaggregator.GetSignatureAggregatorEndpoint(app, network)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
signedMessage, validationID, _, err := validatormanager.InitValidatorRegistration(
|
|
aggregatorCtx,
|
|
app,
|
|
network,
|
|
localValidateFlags.RPC,
|
|
chainSpec,
|
|
false,
|
|
"",
|
|
payerPrivateKey,
|
|
nodeID,
|
|
blsInfo.PublicKey[:],
|
|
expiry,
|
|
remainingBalanceOwners,
|
|
disableOwners,
|
|
0,
|
|
aggregatorLogger,
|
|
true,
|
|
delegationFee,
|
|
time.Duration(minimumStakeDuration)*time.Second,
|
|
common.HexToAddress(rewardsRecipientAddr),
|
|
validatorManagerAddressStr,
|
|
useACP99,
|
|
"",
|
|
signatureAggregatorEndpoint,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ux.Logger.PrintToUser("ValidationID: %s", validationID)
|
|
|
|
deployer := subnet.NewPublicDeployer(app, kc, network)
|
|
txID, _, err := deployer.RegisterL1Validator(balance, blsInfo, signedMessage)
|
|
if err != nil {
|
|
if !strings.Contains(err.Error(), "warp message already issued for validationID") {
|
|
return err
|
|
}
|
|
ux.Logger.PrintToUser(logging.LightBlue.Wrap("The Validation ID was already registered on the P-Chain. Proceeding to the next step"))
|
|
} else {
|
|
ux.Logger.PrintToUser("RegisterL1ValidatorTx ID: %s", txID)
|
|
if err := blockchain.UpdatePChainHeight(
|
|
"Waiting for P-Chain to update validator information ...",
|
|
); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
aggregatorCtx, aggregatorCancel = sdkutils.GetTimedContext(constants.SignatureAggregatorTimeout)
|
|
defer aggregatorCancel()
|
|
if _, err := validatormanager.FinishValidatorRegistration(
|
|
aggregatorCtx,
|
|
app,
|
|
network,
|
|
localValidateFlags.RPC,
|
|
chainSpec,
|
|
false,
|
|
"",
|
|
payerPrivateKey,
|
|
validationID,
|
|
aggregatorLogger,
|
|
validatorManagerAddress,
|
|
signatureAggregatorEndpoint,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
validatorWeight, err := getPoSValidatorWeight(network, chainSpec, nodeID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ux.Logger.PrintToUser(" NodeID: %s", nodeID)
|
|
ux.Logger.PrintToUser(" Network: %s", network.Name())
|
|
ux.Logger.PrintToUser(" Weight: %d", validatorWeight)
|
|
ux.Logger.PrintToUser(" Balance: %.5f LUX", float64(balance)/float64(units.Lux))
|
|
ux.Logger.GreenCheckmarkToUser("Validator %s successfully added to the L1", nodeIDStr)
|
|
return nil
|
|
}
|
|
|
|
func getPoSValidatorWeight(network models.Network, chainSpec contract.ChainSpec, nodeID ids.NodeID) (uint64, error) {
|
|
pClient := platformvm.NewClient(network.Endpoint)
|
|
ctx, cancel := utils.GetAPIContext()
|
|
defer cancel()
|
|
subnetID, err := contract.GetSubnetID(
|
|
app,
|
|
network,
|
|
chainSpec,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
validatorsList, err := pClient.GetValidatorsAt(ctx, subnetID, api.ProposedHeight)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return validatorsList[nodeID].Weight, nil
|
|
}
|