diff --git a/avalanche/log.go b/avalanche/log.go index db44b8a..0b0b42a 100644 --- a/avalanche/log.go +++ b/avalanche/log.go @@ -9,10 +9,6 @@ import ( "os" ) -// -// Public constants -// - const ( // LevelNull sets a logger to show no messages at all. LevelNull Level = 0 diff --git a/examples/node.go b/examples/node.go index c500619..ed1a457 100644 --- a/examples/node.go +++ b/examples/node.go @@ -102,15 +102,6 @@ func CreateNodes() { } } - // examle of how to reconfigure the created nodes to track a subnet - subnetIDsToValidate := []string{"xxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyzzzzzzzzzzzzzzz"} - for _, h := range hosts { - fmt.Println("Reconfiguring node %s to track subnet %s", h.NodeID, subnetIDsToValidate) - if err := h.SyncSubnets(subnetIDsToValidate); err != nil { - panic(err) - } - } - // Create a monitoring node. // Monitoring node enables you to have a centralized Grafana Dashboard where you can view // metrics relevant to any Validator & API nodes that the monitoring node is linked to as well diff --git a/examples/node_validate_primary.go b/examples/node_validate_primary.go new file mode 100644 index 0000000..449c91e --- /dev/null +++ b/examples/node_validate_primary.go @@ -0,0 +1,78 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package examples + +import ( + "context" + "fmt" + "time" + + "github.com/ava-labs/avalanche-tooling-sdk-go/constants" + "github.com/ava-labs/avalanche-tooling-sdk-go/keychain" + "github.com/ava-labs/avalanche-tooling-sdk-go/validator" + "github.com/ava-labs/avalanche-tooling-sdk-go/wallet" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" + + "github.com/ava-labs/avalanche-tooling-sdk-go/avalanche" + "github.com/ava-labs/avalanche-tooling-sdk-go/node" +) + +func ValidatePrimaryNetwork() { + // We are using existing host + node := node.Node{ + // NodeID is Avalanche Node ID of the node + NodeID: "NODE_ID", + // IP address of the node + IP: "NODE_IP_ADDRESS", + // SSH configuration for the node + SSHConfig: node.SSHConfig{ + User: constants.RemoteHostUser, + PrivateKeyPath: "NODE_KEYPAIR_PRIVATE_KEY_PATH", + }, + // Role of the node can be Validator, API, AWMRelayer, Loadtest, or Monitor + Roles: []node.SupportedRole{node.Validator}, + } + + nodeID, err := ids.NodeIDFromString(node.NodeID) + if err != nil { + panic(err) + } + + validatorParams := validator.PrimaryNetworkValidatorParams{ + NodeID: nodeID, + // Validate Primary Network for 48 hours + Duration: 48 * time.Hour, + // Stake 2 AVAX + StakeAmount: 2 * units.Avax, + } + + // Key that will be used for paying the transaction fee of AddValidator Tx + network := avalanche.FujiNetwork() + keychain, err := keychain.NewKeychain(network, "PRIVATE_KEY_FILEPATH", nil) + if err != nil { + panic(err) + } + + wallet, err := wallet.New( + context.Background(), + &primary.WalletConfig{ + URI: network.Endpoint, + AVAXKeychain: keychain.Keychain, + EthKeychain: secp256k1fx.NewKeychain(), + PChainTxsToFetch: nil, + }, + ) + if err != nil { + panic(err) + } + + txID, err := node.ValidatePrimaryNetwork(avalanche.FujiNetwork(), validatorParams, wallet) + if err != nil { + panic(err) + } + fmt.Printf("obtained tx id %s", txID.String()) +} diff --git a/examples/subnet.go b/examples/subnet.go index f197b3c..4a055ab 100644 --- a/examples/subnet.go +++ b/examples/subnet.go @@ -67,7 +67,7 @@ func DeploySubnet() { // can be committed on chain subnetAuthKeys := keychain.Addresses().List() threshold := 1 - newSubnet.SetSubnetCreateParams(controlKeys, uint32(threshold)) + newSubnet.SetSubnetControlParams(controlKeys, uint32(threshold)) wallet, _ := wallet.New( context.Background(), @@ -86,7 +86,7 @@ func DeploySubnet() { // we need to wait to allow the transaction to reach other nodes in Fuji time.Sleep(2 * time.Second) - newSubnet.SetBlockchainCreateParams(subnetAuthKeys) + newSubnet.SetSubnetAuthKeys(subnetAuthKeys) deployChainTx, _ := newSubnet.CreateBlockchainTx(wallet) // since we are using the fee paying key as control key too, we can commit the transaction // on chain immediately since the number of signatures has been reached @@ -139,7 +139,7 @@ func DeploySubnetWithLedger() { controlKeys := addressesIDs subnetAuthKeys := addressesIDs threshold := 1 - newSubnet.SetSubnetCreateParams(controlKeys, uint32(threshold)) + newSubnet.SetSubnetControlParams(controlKeys, uint32(threshold)) // Pay and Sign CreateSubnet Tx with fee paying key A using Ledger deploySubnetTx, _ := newSubnet.CreateSubnetTx(walletA) @@ -149,7 +149,7 @@ func DeploySubnetWithLedger() { // we need to wait to allow the transaction to reach other nodes in Fuji time.Sleep(2 * time.Second) - newSubnet.SetBlockchainCreateParams(subnetAuthKeys) + newSubnet.SetSubnetAuthKeys(subnetAuthKeys) // Pay and sign CreateChain Tx with fee paying key A using Ledger deployChainTx, _ := newSubnet.CreateBlockchainTx(walletA) diff --git a/examples/subnet_ add_validator.go b/examples/subnet_ add_validator.go new file mode 100644 index 0000000..1cfba70 --- /dev/null +++ b/examples/subnet_ add_validator.go @@ -0,0 +1,125 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package examples + +import ( + "context" + "fmt" + "github.com/ava-labs/avalanche-tooling-sdk-go/avalanche" + "github.com/ava-labs/avalanche-tooling-sdk-go/constants" + "github.com/ava-labs/avalanche-tooling-sdk-go/keychain" + "github.com/ava-labs/avalanche-tooling-sdk-go/node" + "github.com/ava-labs/avalanche-tooling-sdk-go/subnet" + "github.com/ava-labs/avalanche-tooling-sdk-go/validator" + "github.com/ava-labs/avalanche-tooling-sdk-go/wallet" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" + "time" +) + +func AddSubnetValidator() { + // We are using existing Subnet that we have already deployed on Fuji + subnetParams := subnet.SubnetParams{ + GenesisFilePath: "GENESIS_FILE_PATH", + Name: "SUBNET_NAME", + } + + newSubnet, err := subnet.New(&subnetParams) + if err != nil { + panic(err) + } + + subnetID, err := ids.FromString("SUBNET_ID") + if err != nil { + panic(err) + } + + // Genesis doesn't contain the deployed Subnet's SubnetID, we need to first set the Subnet ID + newSubnet.SetSubnetID(subnetID) + + // We are using existing host + node := node.Node{ + // NodeID is Avalanche Node ID of the node + NodeID: "NODE_ID", + // IP address of the node + IP: "NODE_IP_ADDRESS", + // SSH configuration for the node + SSHConfig: node.SSHConfig{ + User: constants.RemoteHostUser, + PrivateKeyPath: "NODE_KEYPAIR_PRIVATE_KEY_PATH", + }, + // Role is the role that we expect the host to be (Validator, API, AWMRelayer, Loadtest or + // Monitor) + Roles: []node.SupportedRole{node.Validator}, + } + + // Here we are assuming that the node is currently validating the Primary Network, which is + // a requirement before the node can start validating a Subnet. + // To have a node validate the Primary Network, call node.ValidatePrimaryNetwork + // Now we are calling the node to start tracking the Subnet + subnetIDsToValidate := []string{newSubnet.SubnetID.String()} + if err := node.SyncSubnets(subnetIDsToValidate); err != nil { + panic(err) + } + + // Node is now tracking the Subnet + + // Key that will be used for paying the transaction fees of Subnet AddValidator Tx + // + // In our example, this Key is also the control Key to the Subnet, so we are going to use + // this key to also sign the Subnet AddValidator tx + network := avalanche.FujiNetwork() + keychain, err := keychain.NewKeychain(network, "PRIVATE_KEY_FILEPATH", nil) + if err != nil { + panic(err) + } + + wallet, err := wallet.New( + context.Background(), + &primary.WalletConfig{ + URI: network.Endpoint, + AVAXKeychain: keychain.Keychain, + EthKeychain: secp256k1fx.NewKeychain(), + PChainTxsToFetch: set.Of(subnetID), + }, + ) + if err != nil { + panic(err) + } + + nodeID, err := ids.NodeIDFromString(node.NodeID) + if err != nil { + panic(err) + } + + validatorParams := validator.SubnetValidatorParams{ + NodeID: nodeID, + // Validate Subnet for 48 hours + Duration: 48 * time.Hour, + Weight: 20, + } + + // We need to set Subnet Auth Keys for this transaction since Subnet AddValidator is + // a Subnet-changing transaction + // + // In this example, the example Subnet was created with only 1 key as control key with a threshold of 1 + // and the control key is the key contained in the keychain object, so we are going to use the + // key contained in the keychain object as the Subnet Auth Key for Subnet AddValidator tx + subnetAuthKeys := keychain.Addresses().List() + newSubnet.SetSubnetAuthKeys(subnetAuthKeys) + + addValidatorTx, err := newSubnet.AddValidator(wallet, validatorParams) + if err != nil { + panic(err) + } + + // Since it has the required signatures, we will now commit the transaction on chain + txID, err := newSubnet.Commit(*addValidatorTx, wallet, true) + if err != nil { + panic(err) + } + fmt.Printf("obtained tx id %s", txID.String()) +} diff --git a/examples/subnet_ multisig.go b/examples/subnet_ multisig.go index 21151b4..1fbc0c6 100644 --- a/examples/subnet_ multisig.go +++ b/examples/subnet_ multisig.go @@ -28,9 +28,9 @@ func DeploySubnetMultiSig() { // Create three keys that will be used as control keys of the subnet // NewKeychain will generate a new key pair in the provided path if no .pk file currently // exists in the provided path - keychainA, _ := keychain.NewKeychain(network, "KEY_PATH_A") - keychainB, _ := keychain.NewKeychain(network, "KEY_PATH_B") - keychainC, _ := keychain.NewKeychain(network, "KEY_PATH_C") + keychainA, _ := keychain.NewKeychain(network, "KEY_PATH_A", nil) + keychainB, _ := keychain.NewKeychain(network, "KEY_PATH_B", nil) + keychainC, _ := keychain.NewKeychain(network, "KEY_PATH_C", nil) // In this example, we are using the fee-paying key generated above also as control key // and subnet auth key @@ -55,7 +55,7 @@ func DeploySubnetMultiSig() { // at least two signatures are required to be able to send the CreateChain transaction on-chain // note that threshold does not apply to CreateSubnet transaction threshold := 2 - newSubnet.SetSubnetCreateParams(controlKeys, uint32(threshold)) + newSubnet.SetSubnetControlParams(controlKeys, uint32(threshold)) // Key A will be used for paying the transaction fees of CreateSubnetTx and CreateChainTx walletA, _ := wallet.New( @@ -75,7 +75,7 @@ func DeploySubnetMultiSig() { // we need to wait to allow the transaction to reach other nodes in Fuji time.Sleep(2 * time.Second) - newSubnet.SetBlockchainCreateParams(subnetAuthKeys) + newSubnet.SetSubnetAuthKeys(subnetAuthKeys) deployChainTx, err := newSubnet.CreateBlockchainTx(walletA) if err != nil { fmt.Errorf("error signing tx walletA: %w", err) diff --git a/node/add_validator_primary.go b/node/add_validator_primary.go index 590baa1..bb4609a 100644 --- a/node/add_validator_primary.go +++ b/node/add_validator_primary.go @@ -7,6 +7,8 @@ import ( "fmt" "time" + "github.com/ava-labs/avalanche-tooling-sdk-go/validator" + remoteconfig "github.com/ava-labs/avalanche-tooling-sdk-go/node/config" "github.com/ava-labs/avalanche-tooling-sdk-go/constants" @@ -25,32 +27,12 @@ import ( "golang.org/x/net/context" ) -type PrimaryNetworkValidatorParams struct { - // NodeID is the unique identifier of the node to be added as a validator on the Primary Network. - NodeID ids.NodeID - - // Duration is how long the node will be staking the Primary Network - // Duration has to be greater than or equal to minimum duration for the specified network - // (Fuji / Mainnet) - Duration time.Duration - - // StakeAmount is the amount of Avalanche tokens (AVAX) to stake in this validator - // StakeAmount is in the amount of nAVAX - // StakeAmount has to be greater than or equal to minimum stake required for the specified network - StakeAmount uint64 - - // DelegationFee is the percent fee this validator will charge when others delegate stake to it - // When DelegationFee is not set, the minimum delegation fee for the specified network will be set - // For more information on delegation fee, please head to https://docs.avax.network/nodes/validate/node-validator#delegation-fee-rate - DelegationFee uint32 -} - // ValidatePrimaryNetwork adds node as primary network validator. // It adds the node in the specified network (Fuji / Mainnet / Devnet) // and uses the wallet provided in the argument to pay for the transaction fee func (h *Node) ValidatePrimaryNetwork( network avalanche.Network, - validatorParams PrimaryNetworkValidatorParams, + validatorParams validator.PrimaryNetworkValidatorParams, wallet wallet.Wallet, ) (ids.ID, error) { if validatorParams.NodeID == ids.EmptyNodeID { diff --git a/node/add_validator_primary_test.go b/node/add_validator_primary_test.go index 8a1940b..b06d08c 100644 --- a/node/add_validator_primary_test.go +++ b/node/add_validator_primary_test.go @@ -9,6 +9,10 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanche-tooling-sdk-go/validator" + "github.com/ava-labs/avalanche-tooling-sdk-go/avalanche" "github.com/ava-labs/avalanche-tooling-sdk-go/constants" "github.com/ava-labs/avalanche-tooling-sdk-go/keychain" @@ -19,13 +23,9 @@ import ( "github.com/ava-labs/avalanchego/wallet/subnet/primary" ) -func TestNodesValidatePrimaryNetwork(_ *testing.T) { - ctx := context.Background() - cp, err := GetDefaultCloudParams(ctx, AWSCloud) - if err != nil { - panic(err) - } - +func TestNodesValidatePrimaryNetwork(t *testing.T) { + require := require.New(t) + // We are using an existing host node := Node{ // NodeID is Avalanche Node ID of the node NodeID: "NODE_ID", @@ -36,20 +36,14 @@ func TestNodesValidatePrimaryNetwork(_ *testing.T) { User: constants.RemoteHostUser, PrivateKeyPath: "NODE_KEYPAIR_PRIVATE_KEY_PATH", }, - // Cloud is the cloud service that the node is on - Cloud: AWSCloud, - // CloudConfig is the cloud specific configuration for the node - CloudConfig: *cp, // Role of the node can be Validator, API, AWMRelayer, Loadtest, or Monitor Roles: []SupportedRole{Validator}, } nodeID, err := ids.NodeIDFromString(node.NodeID) - if err != nil { - panic(err) - } + require.NoError(err) - validator := PrimaryNetworkValidatorParams{ + validator := validator.PrimaryNetworkValidatorParams{ NodeID: nodeID, // Validate Primary Network for 48 hours Duration: 48 * time.Hour, @@ -58,11 +52,8 @@ func TestNodesValidatePrimaryNetwork(_ *testing.T) { } network := avalanche.FujiNetwork() - keychain, err := keychain.NewKeychain(network, "PRIVATE_KEY_FILEPATH", nil) - if err != nil { - panic(err) - } + require.NoError(err) wallet, err := wallet.New( context.Background(), @@ -73,13 +64,10 @@ func TestNodesValidatePrimaryNetwork(_ *testing.T) { PChainTxsToFetch: nil, }, ) - if err != nil { - panic(err) - } + require.NoError(err) txID, err := node.ValidatePrimaryNetwork(avalanche.FujiNetwork(), validator, wallet) - if err != nil { - panic(err) - } + require.NoError(err) + fmt.Printf("obtained tx id %s", txID.String()) } diff --git a/node/create_test.go b/node/create_test.go index 7fc9eaf..9030f14 100644 --- a/node/create_test.go +++ b/node/create_test.go @@ -9,34 +9,34 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + awsAPI "github.com/ava-labs/avalanche-tooling-sdk-go/cloud/aws" "github.com/ava-labs/avalanche-tooling-sdk-go/avalanche" "github.com/ava-labs/avalanche-tooling-sdk-go/utils" ) -func TestCreateNodes(_ *testing.T) { +func TestCreateNodes(t *testing.T) { + require := require.New(t) ctx := context.Background() // Get the default cloud parameters for AWS cp, err := GetDefaultCloudParams(ctx, AWSCloud) - if err != nil { - panic(err) - } + require.NoError(err) securityGroupName := "SECURITY_GROUP_NAME" sgID, err := awsAPI.CreateSecurityGroup(ctx, securityGroupName, cp.AWSConfig.AWSProfile, cp.Region) - if err != nil { - panic(err) - } + require.NoError(err) + // Set the security group we are using when creating our Avalanche Nodes cp.AWSConfig.AWSSecurityGroupID = sgID cp.AWSConfig.AWSSecurityGroupName = securityGroupName keyPairName := "KEY_PAIR_NAME" sshPrivateKeyPath := utils.ExpandHome("PRIVATE_KEY_FILEPATH") - if err := awsAPI.CreateSSHKeyPair(ctx, cp.AWSConfig.AWSProfile, cp.Region, keyPairName, sshPrivateKeyPath); err != nil { - panic(err) - } + err = awsAPI.CreateSSHKeyPair(ctx, cp.AWSConfig.AWSProfile, cp.Region, keyPairName, sshPrivateKeyPath) + require.NoError(err) + // Set the key pair we are using when creating our Avalanche Nodes cp.AWSConfig.AWSKeyPair = keyPairName @@ -64,9 +64,7 @@ func TestCreateNodes(_ *testing.T) { UseStaticIP: false, SSHPrivateKeyPath: sshPrivateKeyPath, }) - if err != nil { - panic(err) - } + require.NoError(err) fmt.Println("Successfully created Avalanche Validators") @@ -80,12 +78,12 @@ func TestCreateNodes(_ *testing.T) { // Wait for the host to be ready (only needs to be done once for newly created nodes) fmt.Println("Waiting for SSH shell") if err := h.WaitForSSHShell(sshTimeout); err != nil { - panic(err) + require.NoError(err) } fmt.Println("SSH shell ready to execute commands") // Run a command on the host if output, err := h.Commandf(nil, sshCommandTimeout, "echo 'Hello, %s!'", "World"); err != nil { - panic(err) + require.NoError(err) } else { fmt.Println(string(output)) } @@ -93,7 +91,7 @@ func TestCreateNodes(_ *testing.T) { time.Sleep(10 * time.Second) // check if avalanchego is running if output, err := h.Commandf(nil, sshCommandTimeout, "docker ps"); err != nil { - panic(err) + require.NoError(err) } else { fmt.Println(string(output)) } @@ -112,30 +110,23 @@ func TestCreateNodes(_ *testing.T) { UseStaticIP: false, SSHPrivateKeyPath: sshPrivateKeyPath, }) - if err != nil { - panic(err) - } + require.NoError(err) fmt.Println("Successfully created monitoring node") fmt.Println("Linking monitoring node with Avalanche Validator nodes ...") // Link the 2 validator nodes previously created with the monitoring host so that // the monitoring host can start tracking the validator nodes metrics and collecting their logs - if err := monitoringHosts[0].MonitorNodes(ctx, hosts, ""); err != nil { - panic(err) - } + err = monitoringHosts[0].MonitorNodes(ctx, hosts, "") + require.NoError(err) fmt.Println("Successfully linked monitoring node with Avalanche Validator nodes") fmt.Println("Terminating all created nodes ...") // Destroy all created nodes for _, h := range hosts { err = h.Destroy(ctx) - if err != nil { - panic(err) - } + require.NoError(err) } err = monitoringHosts[0].Destroy(ctx) - if err != nil { - panic(err) - } + require.NoError(err) fmt.Println("All nodes terminated") } diff --git a/subnet/add_validator_subnet.go b/subnet/add_validator_subnet.go index f18838e..964acd9 100644 --- a/subnet/add_validator_subnet.go +++ b/subnet/add_validator_subnet.go @@ -8,33 +8,27 @@ import ( "fmt" "time" + "github.com/ava-labs/avalanche-tooling-sdk-go/validator" + + "golang.org/x/net/context" + "github.com/ava-labs/avalanche-tooling-sdk-go/multisig" "github.com/ava-labs/avalanche-tooling-sdk-go/wallet" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/vms/platformvm/txs" - "golang.org/x/net/context" ) -type ValidatorParams struct { - NodeID ids.NodeID - - Duration time.Duration - - Weight uint64 -} - var ( ErrEmptyValidatorNodeID = errors.New("validator node id is not provided") ErrEmptyValidatorDuration = errors.New("validator duration is not provided") - ErrEmptyValidatorWeight = errors.New("validator weight is not provided") ErrEmptySubnetID = errors.New("subnet ID is not provided") + ErrEmptySubnetAuth = errors.New("no subnet auth keys is provided") ) // AddValidator adds validator to subnet // Before an Avalanche Node can be added as a validator to a Subnet, the node must already be -// tracking the subnet -// TODO: add more description once node join subnet sdk is done -func (c *Subnet) AddValidator(wallet wallet.Wallet, validatorInput ValidatorParams) (*multisig.Multisig, error) { +// tracking the subnet, which can be done by calling SyncSubnets in node package +func (c *Subnet) AddValidator(wallet wallet.Wallet, validatorInput validator.SubnetValidatorParams) (*multisig.Multisig, error) { if validatorInput.NodeID == ids.EmptyNodeID { return nil, ErrEmptyValidatorNodeID } @@ -42,11 +36,14 @@ func (c *Subnet) AddValidator(wallet wallet.Wallet, validatorInput ValidatorPara return nil, ErrEmptyValidatorDuration } if validatorInput.Weight == 0 { - return nil, ErrEmptyValidatorWeight + validatorInput.Weight = 20 } if c.SubnetID == ids.Empty { return nil, ErrEmptySubnetID } + if len(c.DeployInfo.SubnetAuthKeys) == 0 { + return nil, ErrEmptySubnetAuth + } wallet.SetSubnetAuthMultisig(c.DeployInfo.SubnetAuthKeys) diff --git a/subnet/add_validator_subnet_test.go b/subnet/add_validator_subnet_test.go new file mode 100644 index 0000000..a0e6846 --- /dev/null +++ b/subnet/add_validator_subnet_test.go @@ -0,0 +1,83 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package subnet + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanche-tooling-sdk-go/avalanche" + "github.com/ava-labs/avalanche-tooling-sdk-go/keychain" + "github.com/ava-labs/avalanche-tooling-sdk-go/validator" + "github.com/ava-labs/avalanche-tooling-sdk-go/wallet" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" +) + +func TestValidateSubnet(t *testing.T) { + require := require.New(t) + subnetParams := SubnetParams{ + GenesisFilePath: "GENESIS_FILE_PATH", + Name: "SUBNET_NAME", + } + + newSubnet, err := New(&subnetParams) + require.NoError(err) + + // Genesis doesn't contain the deployed Subnet's SubnetID, we need to first set the Subnet ID + subnetID, err := ids.FromString("SUBNET_ID") + require.NoError(err) + + newSubnet.SetSubnetID(subnetID) + + network := avalanche.FujiNetwork() + keychain, err := keychain.NewKeychain(network, "PRIVATE_KEY_FILEPATH", nil) + require.NoError(err) + + wallet, err := wallet.New( + context.Background(), + &primary.WalletConfig{ + URI: network.Endpoint, + AVAXKeychain: keychain.Keychain, + EthKeychain: secp256k1fx.NewKeychain(), + PChainTxsToFetch: set.Of(subnetID), + }, + ) + require.NoError(err) + + nodeID, err := ids.NodeIDFromString("VALIDATOR_NODEID") + require.NoError(err) + + validator := validator.SubnetValidatorParams{ + NodeID: nodeID, + // Validate Subnet for 48 hours + Duration: 48 * time.Hour, + // Setting weight of subnet validator to 20 (default value) + Weight: 20, + } + + // We need to set Subnet Auth Keys for this transaction since Subnet AddValidator is + // a Subnet-changing transaction + // + // In this example, the example Subnet was created with only 1 key as control key with a threshold of 1 + // and the control key is the key contained in the keychain object, so we are going to use the + // key contained in the keychain object as the Subnet Auth Key for Subnet AddValidator tx + subnetAuthKeys := keychain.Addresses().List() + newSubnet.SetSubnetAuthKeys(subnetAuthKeys) + + addValidatorTx, err := newSubnet.AddValidator(wallet, validator) + require.NoError(err) + + // Since it has the required signatures, we will now commit the transaction on chain + txID, err := newSubnet.Commit(*addValidatorTx, wallet, true) + require.NoError(err) + + fmt.Printf("obtained tx id %s", txID.String()) +} diff --git a/subnet/subnet.go b/subnet/subnet.go index 2a6fbc3..aa359e6 100644 --- a/subnet/subnet.go +++ b/subnet/subnet.go @@ -114,12 +114,18 @@ func (c *Subnet) SetParams(controlKeys []ids.ShortID, subnetAuthKeys []ids.Short } } -func (c *Subnet) SetSubnetCreateParams(controlKeys []ids.ShortID, threshold uint32) { +// SetSubnetControlParams sets: +// - control keys, which are keys that are allowed to make changes to a Subnet +// - threshold, which is the number of keys that need to sign a transaction that changes +// a Subnet +func (c *Subnet) SetSubnetControlParams(controlKeys []ids.ShortID, threshold uint32) { c.DeployInfo.ControlKeys = controlKeys c.DeployInfo.Threshold = threshold } -func (c *Subnet) SetBlockchainCreateParams(subnetAuthKeys []ids.ShortID) { +// SetSubnetAuthKeys sets subnetAuthKeys, which are keys that are being used to sign a transaction +// that changes a Subnet +func (c *Subnet) SetSubnetAuthKeys(subnetAuthKeys []ids.ShortID) { c.DeployInfo.SubnetAuthKeys = subnetAuthKeys } @@ -183,6 +189,10 @@ func New(subnetParams *SubnetParams) (*Subnet, error) { return &subnet, nil } +func (c *Subnet) SetSubnetID(subnetID ids.ID) { + c.SubnetID = subnetID +} + func createEvmGenesis( subnetEVMParams *SubnetEVMParams, ) ([]byte, error) { diff --git a/subnet/subnet_test.go b/subnet/subnet_test.go index e4d4897..ca1359b 100644 --- a/subnet/subnet_test.go +++ b/subnet/subnet_test.go @@ -57,7 +57,7 @@ func TestSubnetDeploy(t *testing.T) { controlKeys := keychain.Addresses().List() subnetAuthKeys := keychain.Addresses().List() threshold := 1 - newSubnet.SetSubnetCreateParams(controlKeys, uint32(threshold)) + newSubnet.SetSubnetControlParams(controlKeys, uint32(threshold)) wallet, err := wallet.New( context.Background(), &primary.WalletConfig{ @@ -74,7 +74,7 @@ func TestSubnetDeploy(t *testing.T) { require.NoError(err) fmt.Printf("subnetID %s \n", subnetID.String()) time.Sleep(2 * time.Second) - newSubnet.SetBlockchainCreateParams(subnetAuthKeys) + newSubnet.SetSubnetAuthKeys(subnetAuthKeys) deployChainTx, err := newSubnet.CreateBlockchainTx(wallet) require.NoError(err) blockchainID, err := newSubnet.Commit(*deployChainTx, wallet, true) @@ -104,7 +104,7 @@ func TestSubnetDeployMultiSig(t *testing.T) { subnetAuthKeys = append(subnetAuthKeys, keychainA.Addresses().List()[0]) subnetAuthKeys = append(subnetAuthKeys, keychainB.Addresses().List()[0]) threshold := 2 - newSubnet.SetSubnetCreateParams(controlKeys, uint32(threshold)) + newSubnet.SetSubnetControlParams(controlKeys, uint32(threshold)) walletA, err := wallet.New( context.Background(), @@ -126,7 +126,7 @@ func TestSubnetDeployMultiSig(t *testing.T) { // we need to wait to allow the transaction to reach other nodes in Fuji time.Sleep(2 * time.Second) - newSubnet.SetBlockchainCreateParams(subnetAuthKeys) + newSubnet.SetSubnetAuthKeys(subnetAuthKeys) // first signature of CreateChainTx using keychain A deployChainTx, err := newSubnet.CreateBlockchainTx(walletA) require.NoError(err) @@ -173,7 +173,7 @@ func TestSubnetDeployLedger(t *testing.T) { subnetAuthKeys := addressesIDs threshold := 1 - newSubnet.SetSubnetCreateParams(controlKeys, uint32(threshold)) + newSubnet.SetSubnetControlParams(controlKeys, uint32(threshold)) walletA, err := wallet.New( context.Background(), @@ -194,7 +194,7 @@ func TestSubnetDeployLedger(t *testing.T) { time.Sleep(2 * time.Second) - newSubnet.SetBlockchainCreateParams(subnetAuthKeys) + newSubnet.SetSubnetAuthKeys(subnetAuthKeys) deployChainTx, err := newSubnet.CreateBlockchainTx(walletA) require.NoError(err) diff --git a/validator/validator.go b/validator/validator.go new file mode 100644 index 0000000..f60c208 --- /dev/null +++ b/validator/validator.go @@ -0,0 +1,42 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package validator + +import ( + "time" + + "github.com/ava-labs/avalanchego/ids" +) + +type PrimaryNetworkValidatorParams struct { + // NodeID is the unique identifier of the node to be added as a validator on the Primary Network. + NodeID ids.NodeID + + // Duration is how long the node will be staking the Primary Network + // Duration has to be greater than or equal to minimum duration for the specified network + // (Fuji / Mainnet) + Duration time.Duration + + // StakeAmount is the amount of Avalanche tokens (AVAX) to stake in this validator, which is + // denominated in nAVAX. StakeAmount has to be greater than or equal to minimum stake required + // for the specified network + StakeAmount uint64 + + // DelegationFee is the percent fee this validator will charge when others delegate stake to it + // When DelegationFee is not set, the minimum delegation fee for the specified network will be set + // For more information on delegation fee, please head to https://docs.avax.network/nodes/validate/node-validator#delegation-fee-rate + DelegationFee uint32 +} + +type SubnetValidatorParams struct { + // NodeID is the unique identifier of the node to be added as a validator on the specified Subnet. + NodeID ids.NodeID + // Duration is how long the node will be staking the Subnet + // Duration has to be less than or equal to the duration that the node will be validating the Primary + // Network + Duration time.Duration + // Weight is the validator's weight when sampling validators. + // Weight for subnet validators is set to 20 by default + Weight uint64 +}