Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add primary validator example #118

Merged
merged 59 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
c96ce75
add primary validator example
sukantoraymond Aug 8, 2024
235d6d7
remove print
sukantoraymond Aug 8, 2024
8594732
add example of validate primary network
sukantoraymond Aug 9, 2024
53acf86
remove fmt print
sukantoraymond Aug 9, 2024
0ab6af8
add subnet valdiator
sukantoraymond Aug 9, 2024
e4a7022
fix lint
sukantoraymond Aug 10, 2024
d90e2a0
change file name
sukantoraymond Aug 19, 2024
d946844
change validatorparams to primary network validator params
sukantoraymond Aug 19, 2024
bf6aa31
update generate staking files
sukantoraymond Aug 19, 2024
e258762
fix example
sukantoraymond Aug 20, 2024
b85398d
fix example
sukantoraymond Aug 20, 2024
f9e1c49
fix example
sukantoraymond Aug 20, 2024
4e05bf8
fix example
sukantoraymond Aug 20, 2024
df5ece7
Merge branch 'add-primary-validator' into add-subnet-validator-example
sukantoraymond Aug 21, 2024
71868f0
add subnet validator
sukantoraymond Aug 21, 2024
3e16c32
revert SetSubnetAuthMultisig
sukantoraymond Aug 22, 2024
c7f74d4
address comments
sukantoraymond Aug 22, 2024
2041101
remove unused constants
sukantoraymond Aug 22, 2024
424a6f3
update example
sukantoraymond Aug 22, 2024
75133a9
fix lint
sukantoraymond Aug 22, 2024
9a9a9fe
Merge branch 'add-primary-validator' into add-subnet-validator-example
sukantoraymond Aug 22, 2024
53aeb73
fix merge
sukantoraymond Aug 22, 2024
d295cf5
fix merge
sukantoraymond Aug 22, 2024
d0ddb61
fix merge
sukantoraymond Aug 22, 2024
a638db7
move validator
sukantoraymond Aug 22, 2024
87c9443
move subnet validator
sukantoraymond Aug 22, 2024
e59ca3e
update example
sukantoraymond Aug 22, 2024
bab121f
update example
sukantoraymond Aug 22, 2024
5fd58e4
update example
sukantoraymond Aug 22, 2024
2b55216
update example
sukantoraymond Aug 22, 2024
b96e284
update example
sukantoraymond Aug 22, 2024
723aa48
update example
sukantoraymond Aug 22, 2024
dd0fc42
update example
sukantoraymond Aug 22, 2024
2c74d45
update example
sukantoraymond Aug 22, 2024
40738f3
update example
sukantoraymond Aug 22, 2024
5078b2f
update example
sukantoraymond Aug 22, 2024
c5fce65
update example
sukantoraymond Aug 22, 2024
12f0e86
update example
sukantoraymond Aug 22, 2024
fb71900
update example
sukantoraymond Aug 22, 2024
461440f
update example
sukantoraymond Aug 22, 2024
c6f9dd0
update example
sukantoraymond Aug 22, 2024
c1f1730
update example
sukantoraymond Aug 22, 2024
a51d199
revert changes
sukantoraymond Aug 22, 2024
939ca4e
fix lint
sukantoraymond Aug 22, 2024
0adfc47
fix lint
sukantoraymond Aug 22, 2024
efb998b
add subnet validator example
sukantoraymond Aug 22, 2024
ae06017
fix lint
sukantoraymond Aug 22, 2024
b184302
add example
sukantoraymond Aug 22, 2024
16e9f2f
add example
sukantoraymond Aug 22, 2024
df386b9
update example
sukantoraymond Aug 23, 2024
ce57100
update example
sukantoraymond Aug 23, 2024
e17cde5
update example
sukantoraymond Aug 23, 2024
fe59f1e
fix lint
sukantoraymond Aug 23, 2024
69f2cb4
update example
sukantoraymond Aug 23, 2024
5a207c6
update example
sukantoraymond Aug 23, 2024
a3da517
fix lint
sukantoraymond Aug 23, 2024
2aecb5a
Merge pull request #120 from ava-labs/add-subnet-validator-example
sukantoraymond Aug 23, 2024
ef4471b
change validator name
sukantoraymond Aug 23, 2024
a638afe
update readme
sukantoraymond Aug 23, 2024
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
14 changes: 14 additions & 0 deletions avalanche/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
package avalanche

import (
"github.com/ava-labs/avalanche-tooling-sdk-go/utils"
"github.com/ava-labs/avalanchego/genesis"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/constants"
"github.com/ava-labs/avalanchego/vms/platformvm"
)

type NetworkKind int64
Expand Down Expand Up @@ -90,3 +93,14 @@ func (n Network) GenesisParams() *genesis.Params {
}
return nil
}

func (n Network) GetMinStakingAmount() (uint64, error) {
pClient := platformvm.NewClient(n.Endpoint)
ctx, cancel := utils.GetAPIContext()
defer cancel()
minValStake, _, err := pClient.GetMinStake(ctx, ids.Empty)
if err != nil {
return 0, err
}
return minValStake, nil
}
2 changes: 1 addition & 1 deletion cloud/gcp/gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ func (c *GcpCloud) SetupInstances(
}
instances := make([]*compute.Instance, numNodes)
instancesChan := make(chan *compute.Instance, numNodes)
sshKey := fmt.Sprintf("%s:%s", constants.AnsibleSSHUser, strings.TrimSuffix(sshPublicKey, "\n"))
sshKey := fmt.Sprintf("%s:%s", constants.RemoteHostUser, strings.TrimSuffix(sshPublicKey, "\n"))
automaticRestart := true

instancePrefix := utils.RandomString(5)
Expand Down
15 changes: 6 additions & 9 deletions constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,14 @@ const (
SSHFileOpsTimeout = 100 * time.Second
SSHPOSTTimeout = 10 * time.Second
SSHScriptTimeout = 2 * time.Minute
AnsibleSSHUser = "ubuntu"
RemoteHostUser = "ubuntu"

// node
CloudNodeCLIConfigBasePath = "/home/ubuntu/.avalanche-cli/"
CloudNodeConfigBasePath = "/home/ubuntu/.avalanchego/"
CloudNodeSubnetVMBinaryPath = "/home/ubuntu/.avalanchego/plugins/%s"
CloudNodeStakingPath = "/home/ubuntu/.avalanchego/staking/"
CloudNodeConfigPath = "/home/ubuntu/.avalanchego/configs/"
ServicesDir = "services"
DashboardsDir = "dashboards"

CloudNodeCLIConfigBasePath = "/home/ubuntu/.avalanche-cli/"
CloudNodeStakingPath = "/home/ubuntu/.avalanchego/staking/"
CloudNodeConfigPath = "/home/ubuntu/.avalanchego/configs/"
ServicesDir = "services"
DashboardsDir = "dashboards"
// services
ServiceAvalanchego = "avalanchego"
ServicePromtail = "promtail"
Expand Down
150 changes: 150 additions & 0 deletions node/add_validator_primary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package node

import (
"fmt"
"time"

remoteconfig "github.com/ava-labs/avalanche-tooling-sdk-go/node/config"

"github.com/ava-labs/avalanche-tooling-sdk-go/constants"
"github.com/ava-labs/avalanche-tooling-sdk-go/subnet"

"github.com/ava-labs/avalanche-tooling-sdk-go/avalanche"
"github.com/ava-labs/avalanche-tooling-sdk-go/utils"
"github.com/ava-labs/avalanchego/utils/crypto/bls"
"github.com/ava-labs/avalanchego/vms/platformvm/signer"
"github.com/ava-labs/avalanchego/vms/secp256k1fx"
"github.com/ava-labs/avalanchego/wallet/subnet/primary/common"

"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 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,
wallet wallet.Wallet,
) (ids.ID, error) {
if validatorParams.NodeID == ids.EmptyNodeID {
return ids.Empty, subnet.ErrEmptyValidatorNodeID
}

if validatorParams.Duration == 0 {
return ids.Empty, subnet.ErrEmptyValidatorDuration
}

minValStake, err := network.GetMinStakingAmount()
if err != nil {
return ids.Empty, err
}

if validatorParams.StakeAmount < minValStake {
return ids.Empty, fmt.Errorf("invalid weight, must be greater than or equal to %d: %d", minValStake, validatorParams.StakeAmount)
}

if validatorParams.DelegationFee == 0 {
validatorParams.DelegationFee = network.GenesisParams().MinDelegationFee
}

if err = h.GetBLSKeyFromRemoteHost(); err != nil {
return ids.Empty, fmt.Errorf("unable to set BLS key of node from remote host due to %w", err)
}

wallet.SetSubnetAuthMultisig([]ids.ShortID{})

owner := &secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{
wallet.Addresses()[0],
},
}

proofOfPossession := signer.NewProofOfPossession(h.BlsSecretKey)
nodeID, err := ids.NodeIDFromString(h.NodeID)
if err != nil {
return ids.Empty, err
}

unsignedTx, err := wallet.P().Builder().NewAddPermissionlessValidatorTx(
&txs.SubnetValidator{
Validator: txs.Validator{
NodeID: nodeID,
End: uint64(time.Now().Add(validatorParams.Duration).Unix()),
Wght: validatorParams.StakeAmount,
},
Subnet: ids.Empty,
},
proofOfPossession,
wallet.P().Builder().Context().AVAXAssetID,
owner,
owner,
validatorParams.DelegationFee,
)
if err != nil {
return ids.Empty, fmt.Errorf("error building tx: %w", err)
}

tx := txs.Tx{Unsigned: unsignedTx}
if err := wallet.P().Signer().Sign(context.Background(), &tx); err != nil {
return ids.Empty, fmt.Errorf("error signing tx: %w", err)
}

ctx, cancel := utils.GetAPIContext()
defer cancel()
err = wallet.P().IssueTx(
&tx,
common.WithContext(ctx),
)
if err != nil {
if ctx.Err() != nil {
err = fmt.Errorf("timeout issuing/verifying tx with ID %s: %w", tx.ID(), err)
} else {
err = fmt.Errorf("error issuing tx with ID %s: %w", tx.ID(), err)
}
return ids.Empty, err
}

return tx.ID(), nil
}

// GetBLSKeyFromRemoteHost gets BLS information from remote host and sets the BlsSecretKey value in Node object
func (h *Node) GetBLSKeyFromRemoteHost() error {
blsKeyBytes, err := h.ReadFileBytes(remoteconfig.GetRemoteBLSKeyFile(), constants.SSHFileOpsTimeout)
if err != nil {
return err
}
blsSk, err := bls.SecretKeyFromBytes(blsKeyBytes)
if err != nil {
return err
}
h.BlsSecretKey = blsSk
return nil
}
85 changes: 85 additions & 0 deletions node/add_validator_primary_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package node

import (
"context"
"fmt"
"testing"
"time"

"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/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"
)

func TestNodesValidatePrimaryNetwork(_ *testing.T) {
ctx := context.Background()
cp, err := GetDefaultCloudParams(ctx, AWSCloud)
if err != nil {
panic(err)
}

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: SSHConfig{
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)
}

validator := PrimaryNetworkValidatorParams{
sukantoraymond marked this conversation as resolved.
Show resolved Hide resolved
NodeID: nodeID,
// Validate Primary Network for 48 hours
Duration: 48 * time.Hour,
// Stake 2 AVAX
StakeAmount: 2 * units.Avax,
}

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(), validator, wallet)
if err != nil {
panic(err)
}
fmt.Printf("obtained tx id %s", txID.String())
}
4 changes: 4 additions & 0 deletions node/config/avalanche.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ func RenderAvalancheCChainConfig(config AvalancheConfigInputs) ([]byte, error) {
}
}

func GetRemoteBLSKeyFile() string {
return filepath.Join(constants.CloudNodeStakingPath, constants.BLSKeyFileName)
}

func GetRemoteAvalancheNodeConfig() string {
return filepath.Join(constants.CloudNodeConfigPath, "node.json")
}
Expand Down
4 changes: 2 additions & 2 deletions node/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func createCloudInstances(ctx context.Context, cp CloudParams, count int, useSta
Cloud: cp.Cloud(),
CloudConfig: cp,
SSHConfig: SSHConfig{
User: constants.AnsibleSSHUser,
User: constants.RemoteHostUser,
PrivateKeyPath: sshPrivateKeyPath,
},
Roles: nil,
Expand Down Expand Up @@ -233,7 +233,7 @@ func createCloudInstances(ctx context.Context, cp CloudParams, count int, useSta
Cloud: cp.Cloud(),
CloudConfig: cp,
SSHConfig: SSHConfig{
User: constants.AnsibleSSHUser,
User: constants.RemoteHostUser,
PrivateKeyPath: sshPrivateKeyPath,
},
Roles: nil,
Expand Down
6 changes: 6 additions & 0 deletions node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
"sync"
"time"

"github.com/ava-labs/avalanchego/utils/crypto/bls"

"github.com/melbahja/goph"
"golang.org/x/crypto/ssh"

Expand Down Expand Up @@ -75,6 +77,10 @@ type Node struct {

// Logger for node
Logger avalanche.LeveledLogger

// BLS provides a way to aggregate signatures off chain into a single signature that can be efficiently verified on chain.
// For more information about how BLS is used on the P-Chain, please head to https://docs.avax.network/cross-chain/avalanche-warp-messaging/deep-dive#bls-multi-signatures-with-public-key-aggregation
BlsSecretKey *bls.SecretKey
}

// NewNodeConnection creates a new SSH connection to the node
Expand Down
16 changes: 12 additions & 4 deletions node/staking.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,25 @@ import (
"github.com/ava-labs/avalanchego/staking"
)

func (h *Node) ProvideStakingCertAndKey(keyPath string) error {
if nodeID, err := GenerateNodeCertAndKeys(keyPath); err != nil {
// ProvideStakingFiles generates the files needed to validate the primary network:
// - staker.crt, staker.key, more information can be found at https://docs.avax.network/nodes/validate/how-to-stake#secret-management
// - The file containing the node's BLS information: signer.key (more information can be found at https://docs.avax.network/cross-chain/avalanche-warp-messaging/deep-dive#bls-multi-signatures-with-public-key-aggregation)
//
// and stores them in the provided directory in argument in local machine
// and subsequently uploads these files into the remote host in /home/ubuntu/.avalanchego/staking/
// directory
func (h *Node) ProvideStakingFiles(keyPath string) error {
if nodeID, err := GenerateStakingFiles(keyPath); err != nil {
return err
} else {
h.Logger.Infof("Generated Staking Cert and Key for NodeID: %s in folder %s", nodeID.String(), keyPath)
}
return h.RunSSHUploadStakingFiles(keyPath)
}

// GenerateNodeCertAndKeys generates a node certificate and keys and return nodeID
func GenerateNodeCertAndKeys(keyPath string) (ids.NodeID, error) {
// GenerateStakingFiles generates the following files: staker.crt, staker.key and signer.key
// and stores them in the provided directory in argument in local machine
func GenerateStakingFiles(keyPath string) (ids.NodeID, error) {
arturrez marked this conversation as resolved.
Show resolved Hide resolved
if err := os.MkdirAll(keyPath, constants.DefaultPerms755); err != nil {
sukantoraymond marked this conversation as resolved.
Show resolved Hide resolved
return ids.EmptyNodeID, err
}
Expand Down
2 changes: 1 addition & 1 deletion utils/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func GetSCPTargetPath(ip, path string) string {
if ip == "" {
return path
}
return fmt.Sprintf("%s@%s:%s", constants.AnsibleSSHUser, ip, path)
return fmt.Sprintf("%s@%s:%s", constants.RemoteHostUser, ip, path)
}

// SplitSCPPath splits the given path into node and path.
Expand Down
Loading