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 avalanche support #1

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4357c91
init avalanche support
n0cte Apr 4, 2023
b73066d
fix port number calculation
n0cte Apr 4, 2023
1d0d7eb
optimize port and certs generation
n0cte Apr 7, 2023
abaae7f
add dummy implementation for SendIBCTransfer
n0cte Apr 7, 2023
232496f
add logic for different internal chains
n0cte Apr 10, 2023
6b80506
add subnet configuration
n0cte Apr 10, 2023
406e1c7
add chain initialization with subnet
n0cte Apr 10, 2023
a9af612
add avalanche utils
n0cte Apr 10, 2023
78d129f
change subnet configuration
n0cte Apr 11, 2023
a57c46a
add containers creation
n0cte Apr 21, 2023
de3e545
change file locations
n0cte Apr 21, 2023
9833ba7
add logic avalanche chain
n0cte Apr 25, 2023
2e8aa73
change port locations
n0cte Apr 25, 2023
378b15c
remove debug log level, use default info level
ramilexe Apr 25, 2023
e72f531
fix staking port
ramilexe Apr 25, 2023
79b4d49
all nodes look to zero node
n0cte Apr 25, 2023
7f2ff5a
specify public ip
n0cte Apr 25, 2023
6033a21
tidy initial avalanche impl
agouin Apr 28, 2023
c9e130c
Merge pull request #2 from strangelove-ventures/andrew/initial_avalan…
ramilexe Apr 28, 2023
1c2f6a6
add permissions for reading files from container
n0cte Apr 28, 2023
f7333ed
add avalanche subnet support (#3)
n0cte May 5, 2023
d73429a
Merge branch 'main' into avalanche-support
ramilexe May 5, 2023
1f83213
go mod tidy
ramilexe May 5, 2023
9682243
Add subnets tracking (#4)
n0cte May 10, 2023
a76763a
fix timeouts
ramilexe May 10, 2023
018bcae
add types to interact with different chains (#5)
n0cte Oct 24, 2023
47b612d
fix genesis construction (#6)
n0cte Oct 31, 2023
33b9b2e
fix eth address calculation (#7)
n0cte Nov 7, 2023
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
152 changes: 139 additions & 13 deletions chain/avalanche/node.go
Original file line number Diff line number Diff line change
@@ -2,15 +2,30 @@ package avalanche

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

"github.com/docker/docker/api/types"
"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/ibc"
"github.com/strangelove-ventures/interchaintest/v7/internal/dockerutil"
"go.uber.org/zap"
)

type (
AvalancheNodeCredentials struct {
PK *secp256k1.PrivateKey
ID ids.NodeID
TLSCert []byte
TLSKey []byte
}
AvalancheNodeBootstrapOpts struct {
ID string
Addr string
@@ -20,24 +35,39 @@ type (
VM []byte
}
AvalancheNodeOpts struct {
NetworkID string
PublicIP string
HttpPort string
StakingPort string
Subnets []AvalancheNodeSubnetOpts
Bootstrap []AvalancheNodeBootstrapOpts
Credentials AvalancheNodeCredentials
Genesis Genesis
ChainID utils.ChainID
}
AvalancheNode struct {
name string
containerLifecycle *dockerutil.ContainerLifecycle
logger *zap.Logger
dockerClient *dockerclient.Client
image ibc.DockerImage
volume types.Volume

networkID string
testName string
containerID int
options AvalancheNodeOpts
}
AvalancheNodes []AvalancheNode
)

func NewAvalancheNode(
ctx context.Context,
networkID string,
testName string,
dockerClient *dockerclient.Client,
image ibc.DockerImage,
containerID int,
log *zap.Logger,
options *AvalancheNodeOpts,
) (*AvalancheNode, error) {
// avalanchego
@@ -58,32 +88,124 @@ func NewAvalancheNode(
// https://github.com/ava-labs/avalanchego/blob/master/genesis/genesis_local.json
//
// Vm ID can be generated as zero-extended in a 32 byte array and encoded in CB58([32]byte(subnet.Name))
return nil, nil
name := fmt.Sprintf(
"av-%s-%d",
testName,
containerID,
)

containerLifecycle := dockerutil.NewContainerLifecycle(log, dockerClient, name)

volume, err := dockerClient.VolumeCreate(ctx, volume.VolumeCreateBody{
Name: name,
Labels: map[string]string{
dockerutil.CleanupLabel: testName,
dockerutil.NodeOwnerLabel: name,
},
})
if err != nil {
return nil, err
}

rawGenesis, err := json.MarshalIndent(options.Genesis, "", " ")
if err != nil {
return nil, err
}

node := AvalancheNode{
name: name,
containerLifecycle: containerLifecycle,
logger: log,
dockerClient: dockerClient,
image: image,
volume: volume,
networkID: networkID,
containerID: containerID,
options: *options,
}

if err := node.WriteFile(ctx, rawGenesis, fmt.Sprintf("%s/genesis.json", node.HomeDir())); err != nil {
return nil, err
}

if err := node.WriteFile(ctx, options.Credentials.TLSCert, fmt.Sprintf("%s/tls.cert", node.HomeDir())); err != nil {
return nil, err
}

if err := node.WriteFile(ctx, options.Credentials.TLSKey, fmt.Sprintf("%s/tls.key", node.HomeDir())); err != nil {
return nil, err
}

ports := nat.PortSet{nat.Port(options.StakingPort): {}}
cmd := []string{
"/bin/avalanchego",
"--db-dir", fmt.Sprintf("%s/db", node.HomeDir()),
"--http-port", options.HttpPort,
"--staking-port", options.StakingPort,
"--network-id", fmt.Sprintf("%s-%d", options.ChainID.Name, options.ChainID.Number),
"--genesis", fmt.Sprintf("%s/genesis.json", node.HomeDir()),
"--staking-tls-cert-file", fmt.Sprintf("%s/tls.cert", node.HomeDir()),
"--staking-tls-key-file", fmt.Sprintf("%s/tls.key", node.HomeDir()),
}
if len(options.Bootstrap) > 0 {
bootstapIps := make([]string, len(options.Bootstrap))
bootstapIds := make([]string, len(options.Bootstrap))
for i := range options.Bootstrap {
bootstapIps[i] = options.Bootstrap[i].Addr
bootstapIds[i] = options.Bootstrap[i].ID
}
cmd = append(
cmd,
"--bootstrap-ips", `"`+strings.Join(bootstapIps, ",")+`"`,
"--bootstrap-ids", `"`+strings.Join(bootstapIds, ",")+`"`,
)
}

if err := node.containerLifecycle.CreateContainer(ctx, testName, networkID, image, ports, node.Bind(), node.HostName(), cmd); err != nil {
return nil, err
}

return &node, nil
}

func (n *AvalancheNode) Exec(ctx context.Context, cmd []string, env []string) (stdout, stderr []byte, err error) {
// ToDo: exec some command to node
panic("ToDo: implement me")
func (n *AvalancheNode) HomeDir() string {
return "/home/heighliner"
}

func (n *AvalancheNode) Bind() []string {
return []string{fmt.Sprintf("%s:%s", n.volume.Name, n.HomeDir())}
}

func (n *AvalancheNode) WriteFile(ctx context.Context, content []byte, relPath string) error {
fw := dockerutil.NewFileWriter(n.logger, n.dockerClient, n.testName)
return fw.WriteFile(ctx, n.volume.Name, relPath, content)
}

func (n *AvalancheNode) Exec(ctx context.Context, cmd []string, env []string) ([]byte, []byte, error) {
job := dockerutil.NewImage(n.logger, n.dockerClient, n.networkID, n.testName, n.image.Repository, n.image.Version)
opts := dockerutil.ContainerOptions{
Binds: n.Bind(),
Env: env,
User: n.image.UidGid,
}
res := job.Run(ctx, cmd, opts)
return res.Stdout, res.Stderr, res.Err
}

func (n *AvalancheNode) NodeId() string {
// Todo: return nodeId, example "NodeID-7Xhw2mDxuDS44j42TCB6U5579esbSt3Lg"
panic("ToDo: implement me")
return n.options.Credentials.ID.String()
}

func (n *AvalancheNode) HostName() string {
// ToDo: docker hostname
panic("ToDo: implement me")
return n.name
}

func (n *AvalancheNode) StackingPort() string {
// ToDo: return --staking-port
panic("ToDo: implement me")
return n.options.StakingPort
}

func (n *AvalancheNode) RPCPort() string {
// ToDo: return --http-port
panic("ToDo: implement me")
return n.options.HttpPort
}

func (n *AvalancheNode) GRPCPort() string {
@@ -145,3 +267,7 @@ func (c AvalancheNode) GetBalance(ctx context.Context, address string, denom str
// if allocated subnet, we must call /ext/bc/[chainID]
return 0, fmt.Errorf("address should be have prefix X, P, 0x. current address: %s", address)
}

func (c AvalancheNode) StartContainer(ctx context.Context) error {
return nil
}
12 changes: 5 additions & 7 deletions chain/avalanche/node_genesis.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
package avalanche

import "github.com/strangelove-ventures/interchaintest/v7/chain/avalanche/utils/ids"

type (
GenesisLockedAmount struct {
Amount uint64 `json:"amount"`
Locktime uint64 `json:"locktime"`
}
GenesisAllocation struct {
ETHAddr ids.ShortID `json:"ethAddr"`
AVAXAddr ids.ShortID `json:"avaxAddr"`
ETHAddr string `json:"ethAddr"`
AVAXAddr string `json:"avaxAddr"`
InitialAmount uint64 `json:"initialAmount"`
UnlockSchedule []GenesisLockedAmount `json:"unlockSchedule"`
}
GenesisStaker struct {
NodeID ids.NodeID `json:"nodeID"`
RewardAddress ids.ShortID `json:"rewardAddress"`
DelegationFee uint32 `json:"delegationFee"`
NodeID string `json:"nodeID"`
RewardAddress string `json:"rewardAddress"`
DelegationFee uint32 `json:"delegationFee"`
}
Genesis struct {
NetworkID uint32 `json:"networkID"`
98 changes: 85 additions & 13 deletions chain/avalanche/package.go
Original file line number Diff line number Diff line change
@@ -8,11 +8,15 @@ import (

"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"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/ibc"
"go.uber.org/zap"
"golang.org/x/sync/errgroup"
)

var _ ibc.Chain = AvalancheChain{}
//var _ ibc.Chain = AvalancheChain{}

type (
AvalancheChain struct {
@@ -51,11 +55,8 @@ func (c AvalancheChain) Config() ibc.ChainConfig {
}

// Initialize initializes node structs so that things like initializing keys can be done before starting the chain
func (c AvalancheChain) Initialize(ctx context.Context, testName string, cli *client.Client, networkID string) error {
count := c.numFullNodes + c.numValidators
c.nodes = make([]AvalancheNode, count)
chainCfg := c.Config()
for _, image := range chainCfg.Images {
func (c *AvalancheChain) Initialize(ctx context.Context, testName string, cli *client.Client, networkID string) error {
for _, image := range c.Config().Images {
rc, err := cli.ImagePull(
ctx,
image.Repository+":"+image.Version,
@@ -72,6 +73,20 @@ func (c AvalancheChain) Initialize(ctx context.Context, testName string, cli *cl
_ = rc.Close()
}
}

rawChainID := c.Config().ChainID
if rawChainID == "" {
rawChainID = "localnet-123456"
}
chainId, err := utils.ParseChainID(rawChainID)
if err != nil {
c.log.Error("Failed to pull image",
zap.Error(err),
zap.String("networkID", networkID),
)
return err
}

var subnetOpts []AvalancheNodeSubnetOpts = nil
if len(c.cfg.AvalancheSubnets) > 0 {
subnetOpts = make([]AvalancheNodeSubnetOpts, len(c.cfg.AvalancheSubnets))
@@ -91,35 +106,92 @@ func (c AvalancheChain) Initialize(ctx context.Context, testName string, cli *cl
}
}
}
for i := 0; i < count*2; i += 2 {

numNodes := c.numValidators + c.numFullNodes
credentials := make([]AvalancheNodeCredentials, numNodes)
keyFactory := secp256k1.Factory{}
for i := 0; i < numNodes; i++ {
key, err := keyFactory.NewPrivateKey()
if err != nil {
return err
}

rawTlsCert, rawTlsKey, err := utils.NewCertAndKeyBytes()
if err != nil {
return err
}

cert, err := utils.NewTLSCertFromBytes(rawTlsCert, rawTlsKey)
if err != nil {
return err
}

credentials[i].PK = key
credentials[i].ID = ids.NodeIDFromCert(cert.Leaf)
credentials[i].TLSCert = rawTlsCert
credentials[i].TLSKey = rawTlsKey
}

allocations := make([]GenesisAllocation, 0, c.numValidators)
stakedFunds := make([]string, 0, c.numValidators)
stakes := make([]GenesisStaker, 0, c.numValidators)
for i := 0; i < c.numValidators; i++ {
allocations = append(allocations, GenesisAllocation{
ETHAddr: "0x" + credentials[i].PK.PublicKey().Address().Hex(),
AVAXAddr: credentials[i].PK.PublicKey().Address().PrefixedString("X-" + chainId.Name),
InitialAmount: 300000000000000000,
})
stakes = append(stakes, GenesisStaker{
NodeID: credentials[i].ID.String(),
RewardAddress: credentials[i].PK.PublicKey().Address().PrefixedString("X-" + chainId.Name),
DelegationFee: 1000000,
})
}
stakedFunds = append(stakedFunds, credentials[0].PK.PublicKey().Address().PrefixedString(fmt.Sprintf("X-%s", chainId.Name)))
genesis := NewGenesis(chainId.Number, allocations, stakedFunds, stakes)

nodes := make([]AvalancheNode, 0, numNodes)
for i := 0; i < numNodes*2; i += 2 {
idx := i / 2

var bootstrapOpt []AvalancheNodeBootstrapOpts = nil
if i > 0 {
n := c.nodes[len(c.nodes)-1]
if idx > 0 {
n := nodes[idx-1]
bootstrapOpt = []AvalancheNodeBootstrapOpts{
{
ID: n.NodeId(),
Addr: fmt.Sprintf("%s:%s", n.HostName(), n.StackingPort()),
},
}
}
n, err := NewAvalancheNode(ctx, testName, cli, chainCfg.Images[0], i/2, &AvalancheNodeOpts{
NetworkID: networkID,
n, err := NewAvalancheNode(ctx, networkID, testName, cli, c.Config().Images[0], idx, c.log, &AvalancheNodeOpts{
HttpPort: fmt.Sprintf("%d", 9650+i),
StakingPort: fmt.Sprintf("%d", 9650+i+1),
Bootstrap: bootstrapOpt,
Subnets: subnetOpts,
Credentials: credentials[idx],
Genesis: genesis,
ChainID: *chainId,
})
if err != nil {
return err
}
c.nodes[i] = *n
nodes = append(nodes, *n)
}
c.nodes = nodes
return nil
}

// Start sets up everything needed (validators, gentx, fullnodes, peering, additional accounts) for chain to start from genesis.
func (c AvalancheChain) Start(testName string, ctx context.Context, additionalGenesisWallets ...ibc.WalletAmount) error {
panic("ToDo: implement me")
eg, egCtx := errgroup.WithContext(ctx)
for i := range c.nodes {
node := c.nodes[i]
eg.Go(func() error {
return node.StartContainer(egCtx)
})
}
return eg.Wait()
}

// Exec runs an arbitrary command using Chain's docker environment.
Loading