Skip to content

Commit

Permalink
add avalanche subnet support (#3)
Browse files Browse the repository at this point in the history
* add waiting bootsrapped state

* add avalanche integration and add subnet creation

* add implementation for subnet chains

* move port binding allocation

* deascrease block number for WaitForBlocks

* dont send avax if subnets list is empty

* revert image pulling
  • Loading branch information
n0cte authored and ramilexe committed Oct 24, 2023
1 parent e55a514 commit f8f49ad
Show file tree
Hide file tree
Showing 50 changed files with 318 additions and 4,887 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package utils
package lib

import (
"errors"
Expand Down
35 changes: 35 additions & 0 deletions chain/avalanche/lib/network.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package lib

import (
"context"
"fmt"
"net"
"time"
)

func IsOpened(host string, port string) bool {
conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, port), time.Second)
if err != nil {
return false
}

if conn != nil {
conn.Close()
return true
}

return false
}

func WaitPort(ctx context.Context, host, port string) error {
var err error
for done := false; !done && err == nil; {
select {
case <-ctx.Done():
err = fmt.Errorf("WaitPort(%s, %s) context closed", host, port)
default:
done = IsOpened(host, port)
}
}
return err
}
203 changes: 186 additions & 17 deletions chain/avalanche/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,37 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"path/filepath"
"strings"

"time"

"github.com/ava-labs/avalanchego/api/info"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/constants"
"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
"github.com/ava-labs/avalanchego/vms/components/avax"
"github.com/ava-labs/avalanchego/vms/platformvm"
"github.com/ava-labs/avalanchego/vms/secp256k1fx"
"github.com/ava-labs/avalanchego/wallet/subnet/primary"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/volume"
dockerclient "github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/strangelove-ventures/interchaintest/v7/chain/avalanche/utils"
"github.com/strangelove-ventures/interchaintest/v7/chain/avalanche/utils/crypto/secp256k1"
"github.com/strangelove-ventures/interchaintest/v7/chain/avalanche/utils/ids"
"github.com/strangelove-ventures/interchaintest/v7/chain/avalanche/lib"
"github.com/strangelove-ventures/interchaintest/v7/ibc"
"github.com/strangelove-ventures/interchaintest/v7/internal/dockerutil"
"go.uber.org/zap"
)

var (
RPCPort = "9650/tcp"
StakingPort = "9651/tcp"
RPCPort = "9650/tcp"
StakingPort = "9651/tcp"
portBindings = nat.PortSet{
nat.Port(RPCPort): {},
nat.Port(StakingPort): {},
}
)

type (
Expand Down Expand Up @@ -52,16 +64,21 @@ type (
}

AvalancheNodeSubnetOpts struct {
Name string
VM []byte
Name string
VmID ids.ID
VM []byte
Genesis []byte

subnet ids.ID
chain ids.ID
}

AvalancheNodeOpts struct {
PublicIP string
Subnets []AvalancheNodeSubnetOpts
Bootstrap []*AvalancheNode
Credentials AvalancheNodeCredentials
ChainID utils.ChainID
ChainID lib.ChainID
}
)

Expand Down Expand Up @@ -142,6 +159,15 @@ func NewAvalancheNode(
return nil, err
}

vmaliases := make(map[ids.ID][]string)
for i := range node.options.Subnets {
vmaliases[node.options.Subnets[i].VmID] = []string{node.options.Subnets[i].Name}
}
vmaliasesData, err := json.MarshalIndent(vmaliases, "", " ")
if err != nil {
return nil, err
}

if err := node.WriteFile(ctx, genesisBz, "genesis.json"); err != nil {
return nil, fmt.Errorf("failed to write genesis file: %w", err)
}
Expand All @@ -154,6 +180,16 @@ func NewAvalancheNode(
return nil, fmt.Errorf("failed to write TLS key: %w", err)
}

if err := node.WriteFile(ctx, vmaliasesData, "configs/vms/aliases.json"); err != nil {
return nil, fmt.Errorf("failed to write TLS key: %w", err)
}

for _, subnet := range node.options.Subnets {
if err := node.WriteFile(ctx, subnet.VM, fmt.Sprintf("plugins/%s", subnet.VmID)); err != nil {
return nil, fmt.Errorf("failed to write vm body [%s]: %w", subnet.Name, err)
}
}

return node, node.CreateContainer(ctx)
}

Expand Down Expand Up @@ -270,7 +306,7 @@ func (n *AvalancheNode) SendIBCTransfer(ctx context.Context, channelID, keyName
}

func (n *AvalancheNode) Height(ctx context.Context) (uint64, error) {
panic("ToDo: implement me")
return platformvm.NewClient(fmt.Sprintf("http://127.0.0.1:%s", n.RPCPort())).GetHeight(ctx)
}

func (n *AvalancheNode) GetBalance(ctx context.Context, address string, denom string) (int64, error) {
Expand Down Expand Up @@ -334,18 +370,12 @@ func (n *AvalancheNode) CreateContainer(ctx context.Context) error {
"--bootstrap-ids", bootstrapIds,
)
}
port1, _ := nat.NewPort("tcp", "9650")
port2, _ := nat.NewPort("tcp", "9651")
ports := nat.PortSet{
port1: {},
port2: {},
}
return n.containerLifecycle.CreateContainerInNetwork(
ctx,
n.testName,
n.networkID,
n.image,
ports,
portBindings,
n.Bind(),
&network.NetworkingConfig{
EndpointsConfig: map[string](*network.EndpointSettings){
Expand All @@ -363,3 +393,142 @@ func (n *AvalancheNode) CreateContainer(ctx context.Context) error {
func (n *AvalancheNode) StartContainer(ctx context.Context, testName string, additionalGenesisWallets []ibc.WalletAmount) error {
return n.containerLifecycle.StartContainer(ctx)
}

func (n *AvalancheNode) StartSubnets(ctx context.Context) error {
if len(n.options.Subnets) == 0 {
return nil
}

kc := secp256k1fx.NewKeychain(n.options.Credentials.PK)
ownerAddr := n.options.Credentials.PK.Address()

wallet, err := primary.NewWalletFromURI(ctx, fmt.Sprintf("http://127.0.0.1:%s", n.RPCPort()), kc)
if err != nil {
return err
}

// Get the P-chain and the X-chain wallets
pWallet := wallet.P()
xWallet := wallet.X()

// Pull out useful constants to use when issuing transactions.
xChainID := xWallet.BlockchainID()
owner := &secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{ownerAddr},
}

// Send AVAX to the P-chain.
exportStartTime := time.Now()
exportTxID, err := xWallet.IssueExportTx(
constants.PlatformChainID,
[]*avax.TransferableOutput{
{
Asset: avax.Asset{
ID: xWallet.AVAXAssetID(),
},
Out: &secp256k1fx.TransferOutput{
Amt: 2 * uint64(len(n.options.Subnets)+1) * pWallet.CreateSubnetTxFee(),
OutputOwners: *owner,
},
},
},
)
if err != nil {
n.logger.Error(
"failed to issue X->P export transaction",
zap.Error(err),
)
return err
}
n.logger.Info(
"issued X->P export",
zap.String("exportTxID", exportTxID.String()),
zap.Float64("duration", time.Since(exportStartTime).Seconds()),
)

// Import AVAX from the X-chain into the P-chain.
importStartTime := time.Now()
importTxID, err := pWallet.IssueImportTx(xChainID, owner)
if err != nil {
n.logger.Error(
"failed to issue X->P import transaction",
zap.Error(err),
)
return err
}
n.logger.Info(
"issued X->P import",
zap.String("importTxID", importTxID.String()),
zap.Float64("duration", time.Since(importStartTime).Seconds()),
)

for i, subnet := range n.options.Subnets {
createSubnetStartTime := time.Now()
createSubnetTxID, err := pWallet.IssueCreateSubnetTx(owner)
if err != nil {
n.logger.Error(
"failed to issue create subnet transaction",
zap.Error(err),
zap.String("name", subnet.Name),
)
return err
}
n.logger.Info(
"issued create subnet transaction",
zap.String("name", subnet.Name),
zap.String("createSubnetTxID", createSubnetTxID.String()),
zap.Float64("duration", time.Since(createSubnetStartTime).Seconds()),
)

createChainStartTime := time.Now()
createChainTxID, err := pWallet.IssueCreateChainTx(createSubnetTxID, subnet.Genesis, subnet.VmID, nil, subnet.Name)
if err != nil {
n.logger.Error(
"failed to issue create chain transaction",
zap.Error(err),
zap.String("name", subnet.Name),
)
return err
}
n.logger.Info(
"created new chain",
zap.String("name", subnet.Name),
zap.String("createChainTxID", createChainTxID.String()),
zap.Float64("duration", time.Since(createChainStartTime).Seconds()),
)

n.options.Subnets[i].subnet = createSubnetTxID
n.options.Subnets[i].chain = createChainTxID
}

return nil
}

func (n *AvalancheNode) Start(ctx context.Context, testName string, additionalGenesisWallets []ibc.WalletAmount) error {
err := n.StartContainer(ctx, testName, additionalGenesisWallets)
if err != nil {
return err
}

err = lib.WaitPort(ctx, "127.0.0.1", n.RPCPort())
if err != nil {
return err
}

infoClient := info.NewClient(fmt.Sprintf("http://127.0.0.1:%s", n.RPCPort()))
for done := false; !done && err == nil; {
select {
case <-ctx.Done():
return fmt.Errorf("context closed")
default:
done, err = infoClient.IsBootstrapped(ctx, "X")
if errors.Is(err, io.EOF) {
err = nil
}
}
time.Sleep(500 * time.Millisecond)
}

return err
}
Loading

0 comments on commit f8f49ad

Please sign in to comment.