mirror of
https://github.com/luxfi/cli.git
synced 2026-10-30 17:01:20 +00:00
255 lines
7.9 KiB
Go
255 lines
7.9 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
package primarycmd
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"time"
|
|
|
|
"github.com/luxfi/cli/v2/cmd/blockchaincmd"
|
|
"github.com/luxfi/cli/v2/cmd/nodecmd"
|
|
"github.com/luxfi/cli/v2/pkg/application"
|
|
"github.com/luxfi/cli/v2/pkg/cobrautils"
|
|
"github.com/luxfi/cli/v2/pkg/constants"
|
|
"github.com/luxfi/cli/v2/pkg/keychain"
|
|
"github.com/luxfi/cli/v2/pkg/models"
|
|
"github.com/luxfi/cli/v2/pkg/networkoptions"
|
|
"github.com/luxfi/cli/v2/pkg/prompts"
|
|
"github.com/luxfi/cli/v2/pkg/prompts/comparator"
|
|
"github.com/luxfi/cli/v2/pkg/subnet"
|
|
"github.com/luxfi/cli/v2/pkg/ux"
|
|
"github.com/luxfi/ids"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
globalNetworkFlags networkoptions.NetworkFlags
|
|
keyName string
|
|
useLedger bool
|
|
ledgerAddresses []string
|
|
nodeIDStr string
|
|
weight uint64
|
|
delegationFee uint32
|
|
startTimeStr string
|
|
duration time.Duration
|
|
publicKey string
|
|
pop string
|
|
ErrMutuallyExlusiveKeyLedger = errors.New("--key and --ledger,--ledger-addrs are mutually exclusive")
|
|
ErrStoredKeyOnMainnet = errors.New("--key is not available for mainnet operations")
|
|
)
|
|
|
|
type jsonProofOfPossession struct {
|
|
PublicKey string `json:"publicKey"`
|
|
ProofOfPossession string `json:"proofOfPossession"`
|
|
}
|
|
|
|
// lux primary addValidator
|
|
func newAddValidatorCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "addValidator",
|
|
Short: "Add a validator to Primary Network",
|
|
Long: `The primary addValidator command adds a node as a validator
|
|
in the Primary Network`,
|
|
RunE: addValidator,
|
|
Args: cobrautils.ExactArgs(0),
|
|
}
|
|
networkoptions.AddNetworkFlagsToCmd(cmd, &globalNetworkFlags, false, networkoptions.NonLocalSupportedNetworkOptions)
|
|
cmd.Flags().StringVarP(&keyName, "key", "k", "", "select the key to use [testnet only]")
|
|
cmd.Flags().StringVar(&nodeIDStr, "nodeID", "", "set the NodeID of the validator to add")
|
|
cmd.Flags().Uint64Var(&weight, "weight", 0, "set the staking weight of the validator to add")
|
|
cmd.Flags().StringVar(&startTimeStr, "start-time", "", "UTC start time when this validator starts validating, in 'YYYY-MM-DD HH:MM:SS' format")
|
|
cmd.Flags().DurationVar(&duration, "staking-period", 0, "how long this validator will be staking")
|
|
cmd.Flags().BoolVarP(&useLedger, "ledger", "g", false, "use ledger instead of key (always true on mainnet, defaults to false on testnet)")
|
|
cmd.Flags().StringSliceVar(&ledgerAddresses, "ledger-addrs", []string{}, "use the given ledger addresses")
|
|
cmd.Flags().StringVar(&publicKey, "public-key", "", "set the BLS public key of the validator to add")
|
|
cmd.Flags().StringVar(&pop, "proof-of-possession", "", "set the BLS proof of possession of the validator to add")
|
|
cmd.Flags().Uint32Var(&delegationFee, "delegation-fee", 0, "set the delegation fee (20 000 is equivalent to 2%)")
|
|
return cmd
|
|
}
|
|
|
|
func promptProofOfPossession() (jsonProofOfPossession, error) {
|
|
if publicKey != "" {
|
|
err := prompts.ValidateHexa(publicKey)
|
|
if err != nil {
|
|
ux.Logger.PrintToUser("Format error in given public key: %s", err)
|
|
publicKey = ""
|
|
}
|
|
}
|
|
if pop != "" {
|
|
err := prompts.ValidateHexa(pop)
|
|
if err != nil {
|
|
ux.Logger.PrintToUser("Format error in given proof of possession: %s", err)
|
|
pop = ""
|
|
}
|
|
}
|
|
if publicKey == "" || pop == "" {
|
|
ux.Logger.PrintToUser("Next, we need the public key and proof of possession of the node's BLS")
|
|
ux.Logger.PrintToUser("SSH into the node and call info.getNodeID API to get the node's BLS info")
|
|
ux.Logger.PrintToUser("Check https://docs.lux.network/api-reference/info-api#infogetnodeid for instructions on calling info.getNodeID API")
|
|
}
|
|
var err error
|
|
if publicKey == "" {
|
|
txt := "What is the public key of the node's BLS?"
|
|
publicKey, err = app.Prompt.CaptureValidatedString(txt, prompts.ValidateHexa)
|
|
if err != nil {
|
|
return jsonProofOfPossession{}, err
|
|
}
|
|
}
|
|
if pop == "" {
|
|
txt := "What is the proof of possession of the node's BLS?"
|
|
pop, err = app.Prompt.CaptureValidatedString(txt, prompts.ValidateHexa)
|
|
if err != nil {
|
|
return jsonProofOfPossession{}, err
|
|
}
|
|
}
|
|
return jsonProofOfPossession{PublicKey: publicKey, ProofOfPossession: pop}, nil
|
|
}
|
|
|
|
func addValidator(_ *cobra.Command, _ []string) error {
|
|
var (
|
|
nodeID ids.NodeID
|
|
start time.Time
|
|
err error
|
|
)
|
|
|
|
network, err := networkoptions.GetNetworkFromCmdLineFlags(
|
|
app,
|
|
"",
|
|
globalNetworkFlags,
|
|
true,
|
|
false,
|
|
networkoptions.NonLocalSupportedNetworkOptions,
|
|
"",
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(ledgerAddresses) > 0 {
|
|
useLedger = true
|
|
}
|
|
|
|
if useLedger && keyName != "" {
|
|
return ErrMutuallyExlusiveKeyLedger
|
|
}
|
|
|
|
switch network.Kind {
|
|
case models.Testnet:
|
|
if !useLedger && keyName == "" {
|
|
useLedger, keyName, err = prompts.GetKeyOrLedger(app.Prompt, constants.PayTxsFeesMsg, app.GetKeyDir(), false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case models.Mainnet:
|
|
useLedger = true
|
|
if keyName != "" {
|
|
return ErrStoredKeyOnMainnet
|
|
}
|
|
default:
|
|
return errors.New("unsupported network")
|
|
}
|
|
|
|
if nodeIDStr == "" {
|
|
nodeID, err = blockchaincmd.PromptNodeID("add as Primary Network Validator")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
nodeID, err = ids.NodeIDFromString(nodeIDStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
minValStake, err := nodecmd.GetMinStakingAmount(network)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if weight == 0 {
|
|
weight, err = nodecmd.PromptWeightPrimaryNetwork(network)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if weight < minValStake {
|
|
return fmt.Errorf("illegal weight, must be greater than or equal to %d: %d", minValStake, weight)
|
|
}
|
|
|
|
// TODO: will estimate fee in subsecuent PR
|
|
fee := uint64(0)
|
|
kc, err := keychain.GetKeychain(app, false, useLedger, ledgerAddresses, keyName, network, fee)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
network.HandlePublicNetworkSimulation()
|
|
|
|
jsonPop, err := promptProofOfPossession()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
popBytes, err := json.Marshal(jsonPop)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
start, duration, err = nodecmd.GetTimeParametersPrimaryNetwork(network, 0, duration, startTimeStr, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
deployer := subnet.NewPublicDeployer(app, kc, network)
|
|
nodecmd.PrintNodeJoinPrimaryNetworkOutput(nodeID, weight, network, start)
|
|
recipientAddr := kc.Addresses().List()[0]
|
|
if delegationFee == 0 {
|
|
delegationFee, err = getDelegationFeeOption(app, network)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
defaultFee := network.GenesisParams().MinDelegationFee
|
|
if delegationFee < defaultFee {
|
|
return fmt.Errorf("delegation fee has to be larger than %d", defaultFee)
|
|
}
|
|
}
|
|
_, err = deployer.AddPermissionlessValidator(ids.Empty, ids.Empty, nodeID, weight, uint64(start.Unix()), uint64(start.Add(duration).Unix()), recipientAddr, delegationFee, popBytes, nil)
|
|
return err
|
|
}
|
|
|
|
func getDelegationFeeOption(app *application.Lux, network models.Network) (uint32, error) {
|
|
ux.Logger.PrintToUser("What would you like to set the delegation fee to?")
|
|
defaultFee := network.GenesisParams().MinDelegationFee
|
|
defaultOption := fmt.Sprintf("Default Delegation Fee (%d%%)", defaultFee/10000)
|
|
delegationFeePrompt := "Delegation Fee"
|
|
feeOption, err := app.Prompt.CaptureList(
|
|
delegationFeePrompt,
|
|
[]string{defaultOption, "Custom"},
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if feeOption != defaultOption {
|
|
ux.Logger.PrintToUser("Note that 20 000 is equivalent to 2%%")
|
|
delegationFee, err := app.Prompt.CapturePositiveInt(
|
|
delegationFeePrompt,
|
|
[]comparator.Comparator{
|
|
{
|
|
Label: "Min Delegation Fee",
|
|
Type: comparator.MoreThanEq,
|
|
Value: uint64(defaultFee),
|
|
},
|
|
},
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if delegationFee > 0 && delegationFee <= math.MaxUint32 {
|
|
return uint32(delegationFee), nil
|
|
}
|
|
return 0, fmt.Errorf("invalid delegation fee")
|
|
}
|
|
return defaultFee, nil
|
|
}
|