Skip to content

Commit

Permalink
Merge branch 'main' into ledger
Browse files Browse the repository at this point in the history
Signed-off-by: sukantoraymond <[email protected]>
  • Loading branch information
sukantoraymond authored Jul 30, 2024
2 parents 2180a09 + 7e1eb0d commit 8cac1fb
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 8 deletions.
104 changes: 104 additions & 0 deletions examples/subnet_ multisig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// 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/keychain"
"github.com/ava-labs/avalanche-tooling-sdk-go/subnet"
"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 DeploySubnetMultiSig() {
subnetParams := getDefaultSubnetEVMGenesis()
// Create new Subnet EVM genesis
newSubnet, _ := subnet.New(&subnetParams)

network := avalanche.FujiNetwork()

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

// In this example, we are using the fee-paying key generated above also as control key
// and subnet auth key

// control keys are a list of keys that are permitted to make changes to a Subnet
// such as creating a blockchain in the Subnet and adding validators to the Subnet
controlKeys := []ids.ShortID{}
controlKeys = append(controlKeys, keychainA.Addresses().List()[0])
controlKeys = append(controlKeys, keychainB.Addresses().List()[0])
controlKeys = append(controlKeys, keychainC.Addresses().List()[0])

// subnet auth keys are a subset of control keys
//
// they are the keys that will be used to sign transactions that modify a Subnet
// number of keys in subnetAuthKeys has to be more than or equal to threshold
// all keys in subnetAuthKeys have to sign the transaction before the transaction
// can be committed on chain
subnetAuthKeys := []ids.ShortID{}
// In this example, we are setting Key A and Key B as the subnet auth keys
subnetAuthKeys = append(subnetAuthKeys, keychainA.Addresses().List()[0])
subnetAuthKeys = append(subnetAuthKeys, keychainB.Addresses().List()[0])
// 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))

// Key A will be used for paying the transaction fees of CreateSubnetTx and CreateChainTx
walletA, _ := wallet.New(
context.Background(),
&primary.WalletConfig{
URI: network.Endpoint,
AVAXKeychain: keychainA.Keychain,
EthKeychain: secp256k1fx.NewKeychain(),
PChainTxsToFetch: nil,
},
)

deploySubnetTx, _ := newSubnet.CreateSubnetTx(walletA)
subnetID, _ := newSubnet.Commit(*deploySubnetTx, walletA, true)
fmt.Printf("subnetID %s \n", subnetID.String())

// we need to wait to allow the transaction to reach other nodes in Fuji
time.Sleep(2 * time.Second)

newSubnet.SetBlockchainCreateParams(subnetAuthKeys)
deployChainTx, err := newSubnet.CreateBlockchainTx(walletA)
if err != nil {
fmt.Errorf("error signing tx walletA: %w", err)
}

// we need to include subnetID in PChainTxsToFetch when creating second wallet
// so that the wallet can fetch the CreateSubnet P-chain transaction to be able to
// generate transactions.
walletB, _ := wallet.New(
context.Background(),
&primary.WalletConfig{
URI: network.Endpoint,
AVAXKeychain: keychainB.Keychain,
EthKeychain: secp256k1fx.NewKeychain(),
PChainTxsToFetch: set.Of(subnetID),
},
)

// sign with second wallet so that we have 2/2 threshold reached
if err := walletB.P().Signer().Sign(context.Background(), deployChainTx.PChainTx); err != nil {
fmt.Errorf("error signing tx walletB: %w", err)
}

// since we have two signatures, we can now commit the transaction on chain
blockchainID, _ := newSubnet.Commit(*deployChainTx, walletA, true)
fmt.Printf("blockchainID %s \n", blockchainID.String())
}
71 changes: 71 additions & 0 deletions subnet/add_validator_subnet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.

Check failure on line 1 in subnet/add_validator_subnet.go

View workflow job for this annotation

GitHub Actions / Lint

: # github.com/ava-labs/avalanche-tooling-sdk-go/subnet [github.com/ava-labs/avalanche-tooling-sdk-go/subnet.test]
// See the file LICENSE for licensing terms.

package subnet

import (
"errors"
"fmt"
"time"

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

// 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) {
if validatorInput.NodeID == ids.EmptyNodeID {
return nil, ErrEmptyValidatorNodeID
}
if validatorInput.Duration == 0 {
return nil, ErrEmptyValidatorDuration
}
if validatorInput.Weight == 0 {
return nil, ErrEmptyValidatorWeight
}
if c.SubnetID == ids.Empty {
return nil, ErrEmptySubnetID
}

wallet.SetSubnetAuthMultisig(c.DeployInfo.SubnetAuthKeys)

validator := &txs.SubnetValidator{
Validator: txs.Validator{
NodeID: validatorInput.NodeID,
End: uint64(time.Now().Add(validatorInput.Duration).Unix()),
Wght: validatorInput.Weight,
},
Subnet: c.SubnetID,
}

unsignedTx, err := wallet.P().Builder().NewAddSubnetValidatorTx(validator)
if err != nil {
return nil, fmt.Errorf("error building tx: %w", err)
}
tx := txs.Tx{Unsigned: unsignedTx}
if err := wallet.P().Signer().Sign(context.Background(), &tx); err != nil {
return nil, fmt.Errorf("error signing tx: %w", err)
}
return multisig.New(&tx), nil
}
99 changes: 91 additions & 8 deletions subnet/subnet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/stretchr/testify/require"

"github.com/ava-labs/avalanchego/utils/formatting/address"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/set"

"github.com/ava-labs/avalanche-tooling-sdk-go/avalanche"
Expand Down Expand Up @@ -43,16 +44,21 @@ func getDefaultSubnetEVMGenesis() SubnetParams {
}
}

func TestSubnetDeploy(_ *testing.T) {
func TestSubnetDeploy(t *testing.T) {
require := require.New(t)
subnetParams := getDefaultSubnetEVMGenesis()
newSubnet, _ := New(&subnetParams)
newSubnet, err := New(&subnetParams)
require.NoError(err)
network := avalanche.FujiNetwork()
keychain, _ := keychain.NewKeychain(network, "KEY_PATH", nil)

keychain, err := keychain.NewKeychain(network, "KEY_PATH", nil)
require.NoError(err)

controlKeys := keychain.Addresses().List()
subnetAuthKeys := keychain.Addresses().List()
threshold := 1
newSubnet.SetSubnetCreateParams(controlKeys, uint32(threshold))
wallet, _ := wallet.New(
wallet, err := wallet.New(
context.Background(),
&primary.WalletConfig{
URI: network.Endpoint,
Expand All @@ -61,13 +67,90 @@ func TestSubnetDeploy(_ *testing.T) {
PChainTxsToFetch: nil,
},
)
deploySubnetTx, _ := newSubnet.CreateSubnetTx(wallet)
subnetID, _ := newSubnet.Commit(*deploySubnetTx, wallet, true)
require.NoError(err)
deploySubnetTx, err := newSubnet.CreateSubnetTx(wallet)
require.NoError(err)
subnetID, err := newSubnet.Commit(*deploySubnetTx, wallet, true)
require.NoError(err)
fmt.Printf("subnetID %s \n", subnetID.String())
time.Sleep(2 * time.Second)
newSubnet.SetBlockchainCreateParams(subnetAuthKeys)
deployChainTx, _ := newSubnet.CreateBlockchainTx(wallet)
blockchainID, _ := newSubnet.Commit(*deployChainTx, wallet, true)
deployChainTx, err := newSubnet.CreateBlockchainTx(wallet)
require.NoError(err)
blockchainID, err := newSubnet.Commit(*deployChainTx, wallet, true)
require.NoError(err)
fmt.Printf("blockchainID %s \n", blockchainID.String())
}

func TestSubnetDeployMultiSig(t *testing.T) {
require := require.New(t)
subnetParams := getDefaultSubnetEVMGenesis()
newSubnet, _ := New(&subnetParams)
network := avalanche.FujiNetwork()

keychainA, err := keychain.NewKeychain(network, "KEY_PATH_A")

Check failure on line 91 in subnet/subnet_test.go

View workflow job for this annotation

GitHub Actions / Lint

not enough arguments in call to keychain.NewKeychain
require.NoError(err)
keychainB, err := keychain.NewKeychain(network, "KEY_PATH_B")

Check failure on line 93 in subnet/subnet_test.go

View workflow job for this annotation

GitHub Actions / Lint

not enough arguments in call to keychain.NewKeychain
require.NoError(err)
keychainC, err := keychain.NewKeychain(network, "KEY_PATH_C")

Check failure on line 95 in subnet/subnet_test.go

View workflow job for this annotation

GitHub Actions / Lint

not enough arguments in call to keychain.NewKeychain
require.NoError(err)

controlKeys := []ids.ShortID{}
controlKeys = append(controlKeys, keychainA.Addresses().List()[0])
controlKeys = append(controlKeys, keychainB.Addresses().List()[0])
controlKeys = append(controlKeys, keychainC.Addresses().List()[0])

subnetAuthKeys := []ids.ShortID{}
subnetAuthKeys = append(subnetAuthKeys, keychainA.Addresses().List()[0])
subnetAuthKeys = append(subnetAuthKeys, keychainB.Addresses().List()[0])
threshold := 2
newSubnet.SetSubnetCreateParams(controlKeys, uint32(threshold))

walletA, err := wallet.New(
context.Background(),
&primary.WalletConfig{
URI: network.Endpoint,
AVAXKeychain: keychainA.Keychain,
EthKeychain: secp256k1fx.NewKeychain(),
PChainTxsToFetch: nil,
},
)
require.NoError(err)

deploySubnetTx, err := newSubnet.CreateSubnetTx(walletA)
require.NoError(err)
subnetID, err := newSubnet.Commit(*deploySubnetTx, walletA, true)
require.NoError(err)
fmt.Printf("subnetID %s \n", subnetID.String())

// we need to wait to allow the transaction to reach other nodes in Fuji
time.Sleep(2 * time.Second)

newSubnet.SetBlockchainCreateParams(subnetAuthKeys)
// first signature of CreateChainTx using keychain A
deployChainTx, err := newSubnet.CreateBlockchainTx(walletA)
require.NoError(err)

// include subnetID in PChainTxsToFetch when creating second wallet
walletB, err := wallet.New(
context.Background(),
&primary.WalletConfig{
URI: network.Endpoint,
AVAXKeychain: keychainB.Keychain,
EthKeychain: secp256k1fx.NewKeychain(),
PChainTxsToFetch: set.Of(subnetID),
},
)
require.NoError(err)

// second signature using keychain B
err = walletB.P().Signer().Sign(context.Background(), deployChainTx.PChainTx)
require.NoError(err)

// 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
blockchainID, err := newSubnet.Commit(*deployChainTx, walletA, true)
require.NoError(err)
fmt.Printf("blockchainID %s \n", blockchainID.String())
}

Expand Down

0 comments on commit 8cac1fb

Please sign in to comment.