Skip to content

Commit

Permalink
add types to interact with different chains (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
n0cte authored Oct 24, 2023
1 parent a76763a commit 018bcae
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 130 deletions.
26 changes: 26 additions & 0 deletions chain/avalanche/clientc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package avalanche

import (
"context"

"github.com/strangelove-ventures/interchaintest/v7/ibc"
)

type CChainClient struct {
}

func NewCChainClient(rpcHost string, pk string) (ibc.AvalancheSubnetClient, error) {
return new(CChainClient), nil
}

func (cchain *CChainClient) SendFunds(ctx context.Context, keyName string, amount ibc.WalletAmount) error {
panic("not implemented")
}

func (cchain *CChainClient) Height(ctx context.Context) (uint64, error) {
panic("not implemented")
}

func (cchain *CChainClient) GetBalance(ctx context.Context, address string, denom string) (int64, error) {
panic("not implemented")
}
27 changes: 27 additions & 0 deletions chain/avalanche/clientp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package avalanche

import (
"context"

"github.com/strangelove-ventures/interchaintest/v7/ibc"
)

type PChainClient struct {
}

func NewPChainClient(rpcHost string, pk string) (ibc.AvalancheSubnetClient, error) {
return new(PChainClient), nil
}

func (pchain *PChainClient) SendFunds(ctx context.Context, keyName string, amount ibc.WalletAmount) error {
panic("not implemented")
}

func (pchain *PChainClient) Height(ctx context.Context) (uint64, error) {
//platformvm.NewClient(fmt.Sprintf("http://127.0.0.1:%s", n.RPCPort())).GetHeight(ctx)
panic("not implemented")
}

func (pchain *PChainClient) GetBalance(ctx context.Context, address string, denom string) (int64, error) {
panic("not implemented")
}
26 changes: 26 additions & 0 deletions chain/avalanche/clientx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package avalanche

import (
"context"

"github.com/strangelove-ventures/interchaintest/v7/ibc"
)

type XChainClient struct {
}

func NewXChainClient(rpcHost string, pk string) (ibc.AvalancheSubnetClient, error) {
return new(XChainClient), nil
}

func (xchain *XChainClient) SendFunds(ctx context.Context, keyName string, amount ibc.WalletAmount) error {
panic("not implemented")
}

func (xchain *XChainClient) Height(ctx context.Context) (uint64, error) {
panic("not implemented")
}

func (xchain *XChainClient) GetBalance(ctx context.Context, address string, denom string) (int64, error) {
panic("not implemented")
}
186 changes: 73 additions & 113 deletions chain/avalanche/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
"math/big"
"path/filepath"
"strconv"
"strings"
Expand All @@ -26,10 +25,6 @@ import (
"github.com/docker/docker/api/types/volume"
dockerclient "github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
ethcommon "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"go.uber.org/zap"

"github.com/strangelove-ventures/interchaintest/v7/chain/avalanche/lib"
Expand Down Expand Up @@ -72,10 +67,11 @@ type (
}

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

subnet ids.ID
chain ids.ID
Expand Down Expand Up @@ -260,6 +256,31 @@ func (n *AvalancheNode) GRPCPort() string {
panic(errors.New("doesn't support grpc"))
}

func (n *AvalancheNode) chainClient(id string) (ibc.AvalancheSubnetClient, error) {
addr := fmt.Sprintf("http://127.0.0.1:%s", n.RPCPort())
strpk := n.options.Credentials.PK.String()
switch id {
case "x", "p", "c":
host := fmt.Sprintf("%s/ext/bc/%s", addr, strings.ToUpper(id))
if id == "x" {
return NewXChainClient(host, strpk)
} else if id == "p" {
return NewPChainClient(host, strpk)
} else {
return NewCChainClient(host, strpk)
}
default:
subnetID, err := strconv.Atoi(id)
if err != nil || subnetID >= len(n.options.Subnets) {
return nil, fmt.Errorf("not valid subnet id: %w", err)
}
return n.options.Subnets[subnetID].SCFactory(
fmt.Sprintf("%s/ext/bc/%s", addr, n.options.Subnets[subnetID].chain),
strpk,
)
}
}

func (n *AvalancheNode) CreateKey(ctx context.Context, keyName string) error {
// ToDo: create key
// https://github.com/ava-labs/avalanche-docs/blob/c136e8752af23db5214ff82c2153aac55542781b/docs/quickstart/fund-a-local-test-network.md
Expand All @@ -281,125 +302,64 @@ func (n *AvalancheNode) GetAddress(ctx context.Context, keyName string) ([]byte,
}

func (n *AvalancheNode) SendFunds(ctx context.Context, keyName string, amount ibc.WalletAmount) error {
// ToDo: send some amount to keyName from rootAddress
// https://github.com/ava-labs/avalanche-docs/blob/c136e8752af23db5214ff82c2153aac55542781b/docs/quickstart/fund-a-local-test-network.md
// https://github.com/ava-labs/avalanche-docs/blob/c136e8752af23db5214ff82c2153aac55542781b/docs/quickstart/cross-chain-transfers.md
// IF allocated chain subnet config:
// - Blockchain Handlers: /ext/bc/[chainID]
// - VM Handlers: /ext/vm/[vmID]
// panic("ToDo: implement me")

rawSubnet, ok := ctx.Value("subnet").(string)
rawSubnetID, ok := ctx.Value("subnet").(string)
rawSubnetID = strings.ToLower(rawSubnetID)
if !ok {
return fmt.Errorf("can't read subnet from context")
}

subnet, err := strconv.Atoi(rawSubnet)
if err != nil {
return fmt.Errorf("can't parse subnet idx from context: %w", err)
}

chain := n.options.Subnets[subnet].chain
rpcUrl := fmt.Sprintf("http://127.0.0.1:%s/ext/bc/%s/rpc", n.RPCPort(), chain)

client, err := ethclient.Dial(rpcUrl)
if err != nil {
return fmt.Errorf("can't create client for subnet[%s]: %w", chain, err)
}

chainID, err := client.NetworkID(context.Background())
if err != nil {
return fmt.Errorf("can't connect to subnet[%s][%s]: %w", chain, rpcUrl, err)
}

n.logger.Info(
"connected to subnet",
zap.String("subnet", chain.String()),
zap.Uint64("chainID", chainID.Uint64()),
)

privateKey, err := crypto.HexToECDSA(keyName)
if err != nil {
return fmt.Errorf("can't parse private key: %s", err)
}

senderAddr := crypto.PubkeyToAddress(privateKey.PublicKey)

senderNonce, err := client.PendingNonceAt(ctx, senderAddr)
if err != nil {
return fmt.Errorf("can't get nonce: %w", err)
}

gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
return fmt.Errorf("can't get gas price: %w", err)
if strings.HasPrefix(amount.Address, "X-") {
rawSubnetID = "x"
} else if strings.HasPrefix(amount.Address, "P-") {
rawSubnetID = "p"
} else if strings.HasPrefix(amount.Address, "0x") {
rawSubnetID = "c"
} else {
return fmt.Errorf("address have uknown format: %s", amount.Address)
}
rawSubnetID = "x"
}

toAddress := ethcommon.HexToAddress(amount.Address)

utx := ethtypes.NewTransaction(senderNonce, toAddress, big.NewInt(amount.Amount), 21000, gasPrice, nil)

signedTx, err := ethtypes.SignTx(utx, ethtypes.NewEIP155Signer(chainID), privateKey)
n.logger.Info("create client", zap.String("subnet", rawSubnetID))
client, err := n.chainClient(rawSubnetID)
if err != nil {
return fmt.Errorf("can't sign transaction: %w", err)
return fmt.Errorf("subnet client creation error: %w", err)
}

err = client.SendTransaction(ctx, signedTx)
if err != nil {
return fmt.Errorf("can't send EVM tx: %w", err)
}

n.logger.Info(
"successfully sent EVM tx to subnet",
zap.Any("hash", signedTx.Hash()),
)

return nil
return client.SendFunds(ctx, keyName, amount)
}

func (n *AvalancheNode) SendIBCTransfer(ctx context.Context, channelID, keyName string, amount ibc.WalletAmount, options ibc.TransferOptions) (ibc.Tx, error) {
return ibc.Tx{}, errors.New("not yet implemented")
}

func (n *AvalancheNode) Height(ctx context.Context) (uint64, error) {
rawSubnet, ok := ctx.Value("subnet").(string)
// we have subnet passed via context
if ok {
subnet, err := strconv.Atoi(rawSubnet)
if err != nil {
return 0, fmt.Errorf("can't parse subnet idx from context: %w", err)
}

chain := n.options.Subnets[subnet].chain
rpcUrl := fmt.Sprintf("http://127.0.0.1:%s/ext/bc/%s/rpc", n.RPCPort(), chain)

client, err := ethclient.Dial(rpcUrl)
if err != nil {
return 0, fmt.Errorf("can't create client for subnet[%s]: %w", chain, err)
}

return client.BlockNumber(ctx)
rawSubnetID, ok := ctx.Value("subnet").(string)
rawSubnetID = strings.ToLower(rawSubnetID)
if !ok {
return platformvm.NewClient(fmt.Sprintf("http://127.0.0.1:%s", n.RPCPort())).GetHeight(ctx)
}

return platformvm.NewClient(fmt.Sprintf("http://127.0.0.1:%s", n.RPCPort())).GetHeight(ctx)
client, err := n.chainClient(rawSubnetID)
if err != nil {
return 0, fmt.Errorf("subnet client creation error: %w", err)
}
return client.Height(ctx)
}

func (n *AvalancheNode) GetBalance(ctx context.Context, address string, denom string) (int64, error) {
if strings.HasPrefix(address, "X-") {
// ToDo: call /ext/bc/X (method avm.getBalance)
// https://github.com/ava-labs/avalanche-docs/blob/c136e8752af23db5214ff82c2153aac55542781b/docs/quickstart/fund-a-local-test-network.md#check-x-chain-balance
panic("ToDo: implement me")
} else if strings.HasPrefix(address, "P-") {
// ToDo: call /ext/bc/P (method platform.getBalance)
// https://github.com/ava-labs/avalanche-docs/blob/c136e8752af23db5214ff82c2153aac55542781b/docs/quickstart/fund-a-local-test-network.md#check-p-chain-balance
panic("ToDo: implement me")
} else if strings.HasPrefix(address, "0x") {
// ToDo: call /ext/bc/C/rpc (method eth_getBalance)
// https://github.com/ava-labs/avalanche-docs/blob/c136e8752af23db5214ff82c2153aac55542781b/docs/quickstart/fund-a-local-test-network.md#check-the-c-chain-balance
panic("ToDo: implement me")
}
// 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)
rawSubnetID, ok := ctx.Value("subnet").(string)
rawSubnetID = strings.ToLower(rawSubnetID)
if !ok {
if strings.HasPrefix(address, "X-") {
rawSubnetID = "x"
} else if strings.HasPrefix(address, "P-") {
rawSubnetID = "p"
} else if strings.HasPrefix(address, "0x") {
rawSubnetID = "c"
} else {
return 0, fmt.Errorf("address have uknown format: %s", address)
}
}
client, err := n.chainClient(rawSubnetID)
if err != nil {
return 0, fmt.Errorf("subnet client creation error: %w", err)
}
return client.GetBalance(ctx, address, denom)
}

func (n *AvalancheNode) IP() string {
Expand Down
1 change: 1 addition & 0 deletions chain/avalanche/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ func (c *AvalancheChain) Initialize(ctx context.Context, testName string, cli *c
subnetOpts[i].Name = c.cfg.AvalancheSubnets[i].Name
subnetOpts[i].VM = c.cfg.AvalancheSubnets[i].VM
subnetOpts[i].Genesis = c.cfg.AvalancheSubnets[i].Genesis
subnetOpts[i].SCFactory = c.cfg.AvalancheSubnets[i].SubnetClientFactory
vmName := make([]byte, 32)
copy(vmName[:], []byte(c.cfg.AvalancheSubnets[i].Name))
subnetOpts[i].VmID, err = ids.ToID(vmName)
Expand Down
15 changes: 5 additions & 10 deletions examples/avalanche/avalanche_chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,11 @@ import (
"golang.org/x/sync/errgroup"

interchaintest "github.com/strangelove-ventures/interchaintest/v7"
subnetevm "github.com/strangelove-ventures/interchaintest/v7/examples/avalanche/subnet-evm"
"github.com/strangelove-ventures/interchaintest/v7/ibc"
"github.com/strangelove-ventures/interchaintest/v7/testutil"
)

//go:embed subnet-evm/srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy
var subnetevmVM []byte

//go:embed subnet-evm/genesis.json
var subnetevmGenesis []byte

func TestAvalancheChainStart(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
Expand All @@ -39,9 +34,10 @@ func TestAvalancheChainStart(t *testing.T) {
ChainID: "neto-123123",
AvalancheSubnets: []ibc.AvalancheSubnetConfig{
{
Name: "subnetevm",
VM: subnetevmVM,
Genesis: subnetevmGenesis,
Name: "subnetevm",
VM: subnetevm.VM,
Genesis: subnetevm.Genesis,
SubnetClientFactory: subnetevm.NewSubnetEvmClient,
},
},
},
Expand Down Expand Up @@ -79,7 +75,6 @@ func TestAvalancheChainStart(t *testing.T) {
Address: "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FD",
Amount: 2000000,
})

})
eg.Go(func() error {
return testutil.WaitForBlocks(subnetCtx, 1, chain)
Expand Down
Loading

0 comments on commit 018bcae

Please sign in to comment.