Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 35 additions & 18 deletions cmd/blockchaincmd/add_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
package blockchaincmd

import (
_ "embed"
"errors"
"fmt"
"strings"
"time"

"github.com/ava-labs/avalanche-cli/cmd/flags"
flagsmeta "github.com/ava-labs/avalanche-cli/cmd/flags-meta"
"github.com/ava-labs/avalanche-cli/pkg/blockchain"
"github.com/ava-labs/avalanche-cli/pkg/cobrautils"
"github.com/ava-labs/avalanche-cli/pkg/constants"
Expand Down Expand Up @@ -64,9 +66,9 @@ var (
createLocalValidator bool
externalValidatorManagerOwner bool
validatorManagerOwner string
httpPort uint32
stakingPort uint32
addValidatorFlags BlockchainAddValidatorFlags
// httpPort uint32
stakingPort uint32
addValidatorFlags BlockchainAddValidatorFlags
)

type BlockchainAddValidatorFlags struct {
Expand All @@ -78,33 +80,45 @@ const (
validatorWeightFlag = "weight"
)

//go:embed add_validator.json
var flagsConfigString string
var addValidatorConfigs *flagsmeta.JSONFlags

// avalanche blockchain addValidator
func newAddValidatorCmd() *cobra.Command {
// Load command meta and flags info from JSON
addValidatorConfigs, err := flagsmeta.LoadCommandFlagsFromJSON(flagsConfigString)
if err != nil {
panic(fmt.Errorf("failed to load command meta and flags info: %w", err))
}

cmdUse, _ := addValidatorConfigs.GetCommandMeta("use")
cmdShort, _ := addValidatorConfigs.GetCommandMeta("short")
cmdLong, _ := addValidatorConfigs.GetCommandMeta("long")

cmd := &cobra.Command{
Use: "addValidator [blockchainName]",
Short: "Add a validator to an L1",
Long: `The blockchain addValidator command adds a node as a validator to
an L1 of the user provided deployed network. If the network is proof of
authority, the owner of the validator manager contract must sign the
transaction. If the network is proof of stake, the node must stake the L1's
staking token. Both processes will issue a RegisterL1ValidatorTx on the P-Chain.

This command currently only works on Blockchains deployed to either the Fuji
Testnet or Mainnet.`,
RunE: addValidator,
Args: cobrautils.MaximumNArgs(1),
Use: cmdUse,
Short: cmdShort,
Long: cmdLong,
RunE: addValidator,
Args: cobrautils.MaximumNArgs(1),
}

if err := addValidatorConfigs.RegisterToCOBRA(cmd); err != nil {
panic(fmt.Errorf("failed to register command flags: %w", err))
}

networkoptions.AddNetworkFlagsToCmd(cmd, &globalNetworkFlags, true, networkoptions.DefaultSupportedNetworkOptions)
flags.AddRPCFlagToCmd(cmd, app, &addValidatorFlags.RPC)
flags.AddSignatureAggregatorFlagsToCmd(cmd, &addValidatorFlags.SigAggFlags)
cmd.Flags().StringVarP(&keyName, "key", "k", "", "select the key to use [fuji/devnet only]")
// cmd.Flags().StringVarP(&keyName, "key", "k", "", "select the key to use [fuji/devnet only]")
cmd.Flags().Float64Var(
&balanceAVAX,
"balance",
0,
"set the AVAX balance of the validator that will be used for continuous fee on P-Chain",
)
cmd.Flags().BoolVarP(&useEwoq, "ewoq", "e", false, "use ewoq key [fuji/devnet only]")
// cmd.Flags().BoolVarP(&useEwoq, "ewoq", "e", false, "use ewoq key [fuji/devnet only]")
cmd.Flags().BoolVarP(&useLedger, "ledger", "g", false, "use ledger instead of key (always true on mainnet, defaults to false on fuji/devnet)")
cmd.Flags().StringSliceVar(&ledgerAddresses, "ledger-addrs", []string{}, "use the given ledger addresses")
cmd.Flags().StringVar(&nodeIDStr, "node-id", "", "node-id of the validator to add")
Expand All @@ -129,7 +143,7 @@ Testnet or Mainnet.`,
cmd.Flags().StringVar(&validatorManagerOwner, "validator-manager-owner", "", "force using this address to issue transactions to the validator manager")
cmd.Flags().BoolVar(&externalValidatorManagerOwner, "external-evm-signature", false, "set this value to true when signing validator manager tx outside of cli (for multisig or ledger)")
cmd.Flags().StringVar(&initiateTxHash, "initiate-tx-hash", "", "initiate tx is already issued, with the given hash")
cmd.Flags().Uint32Var(&httpPort, "http-port", 0, "http port for node")
// cmd.Flags().Uint32Var(&httpPort, "http-port", 0, "http port for node")
cmd.Flags().Uint32Var(&stakingPort, "staking-port", 0, "staking port for node")

return cmd
Expand Down Expand Up @@ -201,6 +215,8 @@ func addValidator(cmd *cobra.Command, args []string) error {

// TODO: will estimate fee in subsecuent PR
fee := uint64(0)
useEwoq, _ := flagsmeta.GetValue[bool](addValidatorConfigs, "ewoq")
keyName, _ := flagsmeta.GetValue[string](addValidatorConfigs, "keyName")
kc, err := keychain.GetKeychainFromCmdLineFlags(
app,
"to pay for transaction fees on P-Chain",
Expand Down Expand Up @@ -274,6 +290,7 @@ func addValidator(cmd *cobra.Command, args []string) error {
return fmt.Errorf("too many local clusters running for network %s and blockchain %s", network.Name(), blockchainName)
}
localValidatorClusterName = targetClusters[0]
httpPort, _ := flagsmeta.GetValue[uint32](addValidatorConfigs, "httpPort")
node, err := localnet.AddNodeToLocalCluster(app, ux.Logger.PrintToUser, localValidatorClusterName, httpPort, stakingPort)
if err != nil {
return err
Expand Down
32 changes: 32 additions & 0 deletions cmd/blockchaincmd/add_validator.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"command": {
"use": "addValidator [blockchainName]",
"short": "Add a validator to an L1",
"long": "The blockchain addValidator command adds a node as a validator to an L1 of the user provided deployed network. If the network is proof of authority, the owner of the validator manager contract must sign the transaction. If the network is proof of stake, the node must stake the L1's staking token. Both processes will issue a RegisterL1ValidatorTx on the P-Chain.\nThis command currently only works on Blockchains deployed to either the Fuji Testnet or Mainnet."
},
"flags": [
{
"var": "keyName",
"type": "string",
"name": "key",
"shorthand": "k",
"default": "",
"usage": "select the key to use [fuji/devnet only]"
},
{
"var": "useEwoq",
"type": "bool",
"name": "ewoq",
"shorthand": "e",
"default": false,
"usage": "use ewoq key [fuji/devnet only]"
},
{
"var": "httpPort",
"type": "uint32",
"name": "http-port",
"default": 0,
"usage": "http port for node"
}
]
}
97 changes: 97 additions & 0 deletions cmd/flags-meta/flags-loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (C) 2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package flagsmeta

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

"github.com/spf13/cobra"
)

type JSONFlags struct {
// loaded from JSON
flagsMap map[string]any
// for cobra to capture values
cobraStore map[string]any
}

func LoadCommandFlagsFromJSON(jsonStr string) (*JSONFlags, error) {
flagsMap := make(map[string]any)
err := json.Unmarshal([]byte(jsonStr), &flagsMap)
if err != nil {
return nil, err
}

cobraStore := make(map[string]any)
return &JSONFlags{
flagsMap,
cobraStore,
}, nil
}

// Load command meta info
func (j *JSONFlags) GetCommandMeta(propName string) (string, error) {
cmdMetaMap := j.flagsMap["command"].(map[string]any)
if val, ok := cmdMetaMap[propName].(string); ok {
return val, nil
}
return "", fmt.Errorf("failed to return command meta value: %s", propName)
}

// Load flags config value with given flag name
func GetValue[T any](j *JSONFlags, flagName string) (T, error) {
if value, ok := j.cobraStore[flagName]; ok {
if confValue, ok := value.(*T); ok {
return *confValue, nil
}
}
return *new(T), errors.New("failed to return value")
}

// register flags to cobra command
func (j *JSONFlags) RegisterToCOBRA(
cmd *cobra.Command,
) error {
for _, flag := range j.flagsMap["flags"].([]any) {
flagMap := flag.(map[string]any)
switch flagMap["type"] {
case "bool":
var bval bool
j.cobraStore[flagMap["var"].(string)] = &bval
cmd.Flags().BoolVarP(
&bval,
flagMap["name"].(string),
flagMap["shorthand"].(string),
flagMap["default"].(bool),
flagMap["usage"].(string),
)
// fmt.Printf("registering bool flag: %s\n", flagMap["name"].(string))
case "string":
var sval string
j.cobraStore[flagMap["var"].(string)] = &sval
cmd.Flags().StringVarP(
&sval,
flagMap["name"].(string),
flagMap["shorthand"].(string),
flagMap["default"].(string),
flagMap["usage"].(string),
)
// fmt.Printf("registering string flag: %s\n", flagMap["name"].(string))
case "uint32":
var u32val uint32
j.cobraStore[flagMap["var"].(string)] = &u32val
cmd.Flags().Uint32Var(
&u32val,
flagMap["name"].(string),
uint32(flagMap["default"].(float64 /* JSON number type*/)),
flagMap["usage"].(string),
)
// fmt.Printf("registering uint32 flag: %s\n", flagMap["name"].(string))
default:
panic(fmt.Sprintf("unsupported flag type: %T", flag))
}
}
return nil
}
Loading