Skip to content

Commit

Permalink
e2e: Add warp test with xsvm
Browse files Browse the repository at this point in the history
  • Loading branch information
marun committed Sep 19, 2023
1 parent 2f8eb4d commit 53de98f
Show file tree
Hide file tree
Showing 7 changed files with 608 additions and 30 deletions.
3 changes: 3 additions & 0 deletions scripts/tests.e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ go install -v github.com/onsi/ginkgo/v2/[email protected]
ACK_GINKGO_RC=true ginkgo build ./tests/e2e
./tests/e2e/e2e.test --help

# Enable subnet testing by building xsvm
./scripts/build_xsvm.sh

#################################
E2E_USE_PERSISTENT_NETWORK="${E2E_USE_PERSISTENT_NETWORK:-}"
TESTNETCTL_NETWORK_DIR="${TESTNETCTL_NETWORK_DIR:-}"
Expand Down
18 changes: 10 additions & 8 deletions tests/e2e/e2e.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,14 +192,7 @@ func AddEphemeralNode(network testnet.Network, flags testnet.FlagsMap) testnet.N
node, err := network.AddEphemeralNode(ginkgo.GinkgoWriter, flags)
require.NoError(err)

// Ensure node is stopped on teardown. It's configuration is not removed to enable
// collection in CI to aid in troubleshooting failures.
ginkgo.DeferCleanup(func() {
tests.Outf("shutting down ephemeral node %q\n", node.GetID())
ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancel()
require.NoError(node.Stop(ctx))
})
RegisterNodeforCleanup(node)

return node
}
Expand Down Expand Up @@ -254,3 +247,12 @@ func WithSuggestedGasPrice(ethClient ethclient.Client) common.Option {
baseFee := SuggestGasPrice(ethClient)
return common.WithBaseFee(baseFee)
}

func RegisterNodeforCleanup(node testnet.Node) {
ginkgo.DeferCleanup(func() {
tests.Outf("shutting down ephemeral node %q\n", node.GetID())
ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancel()
require.NoError(ginkgo.GinkgoT(), node.Stop(ctx))
})
}
3 changes: 1 addition & 2 deletions tests/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,10 @@ var _ = ginkgo.SynchronizedBeforeSuite(func() []byte {
// Load or create a test network
var network *local.LocalNetwork
if len(persistentNetworkDir) > 0 {
tests.Outf("{{yellow}}Using a pre-existing network configured at %s{{/}}\n", persistentNetworkDir)

var err error
network, err = local.ReadNetwork(persistentNetworkDir)
require.NoError(err)
tests.Outf("{{yellow}}Using a pre-existing network configured at %s{{/}}\n", network.Dir)
} else {
tests.Outf("{{magenta}}Starting network with %q{{/}}\n", avalancheGoExecPath)

Expand Down
143 changes: 143 additions & 0 deletions tests/e2e/p/warp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package p

import (
"errors"
"fmt"
"math"
"os"
"path/filepath"

ginkgo "github.com/onsi/ginkgo/v2"

"github.com/stretchr/testify/require"

"github.com/ava-labs/avalanchego/config"
"github.com/ava-labs/avalanchego/tests/e2e"
"github.com/ava-labs/avalanchego/tests/fixture/testnet"
"github.com/ava-labs/avalanchego/utils/units"
"github.com/ava-labs/avalanchego/vms/example/xsvm/cmd/issue/export"
"github.com/ava-labs/avalanchego/vms/example/xsvm/cmd/issue/importtx"
"github.com/ava-labs/avalanchego/vms/example/xsvm/cmd/issue/transfer"
"github.com/ava-labs/avalanchego/vms/example/xsvm/genesis"
)

var _ = e2e.DescribePChain("[Warp]", func() {
require := require.New(ginkgo.GinkgoT())

ginkgo.It("should support transfers between subnets", func() {
// TODO(marun) make the plugin path configurable
pluginDir := filepath.Join(os.Getenv("GOPATH"), "src/github.com/ava-labs/avalanchego/build/plugins")
if fileInfo, err := os.Stat(pluginDir); errors.Is(err, os.ErrNotExist) || !fileInfo.IsDir() {
ginkgo.Skip(fmt.Sprintf("invalid plugin dir %s", pluginDir))
}

ginkgo.By("creating wallet with a funded key to create subnets with")
nodeURI := e2e.Env.GetRandomNodeURI()
keychain := e2e.Env.NewKeychain(1)
privateKey := keychain.Keys[0]
baseWallet := e2e.Env.NewWallet(keychain, nodeURI)
pWallet := baseWallet.P()

ginkgo.By("Defining the configuration for an xsvm subnet")
genesisBytes, err := genesis.Codec.Marshal(genesis.Version, &genesis.Genesis{
Timestamp: 0,
Allocations: []genesis.Allocation{
{
Address: privateKey.Address(),
Balance: math.MaxUint64,
},
},
})
require.NoError(err)
subnetSpec := testnet.SubnetSpec{
Blockchains: []testnet.BlockchainSpec{
{
VMName: "xsvm",
Genesis: genesisBytes,
},
},
Nodes: []testnet.NodeSpec{
{
Flags: testnet.FlagsMap{
config.PluginDirKey: pluginDir,
},
Count: testnet.DefaultNodeCount,
},
},
}

// TODO(marun) Simplify this call for e2e
ginkgo.By("creating 2 xsvm subnets")
network := e2e.Env.GetNetwork()
subnets, err := testnet.CreateSubnets(
ginkgo.GinkgoWriter,
e2e.DefaultTimeout,
pWallet,
privateKey.Address(),
network,
e2e.RegisterNodeforCleanup,
subnetSpec,
subnetSpec,
)
require.NoError(err)

sourceSubnet := subnets[0]
sourceChainID := sourceSubnet.BlockchainIDs[0]
destinationSubnet := subnets[1]
destinationChainID := destinationSubnet.BlockchainIDs[0]

ginkgo.By(fmt.Sprintf("exporting from blockchain %s on subnet %s", sourceChainID, sourceSubnet.ID))
exportTxStatus, err := export.Export(
e2e.DefaultContext(),
&export.Config{
URI: sourceSubnet.Nodes[0].GetProcessContext().URI,
SourceChainID: sourceChainID,
DestinationChainID: destinationChainID,
Amount: units.Schmeckle,
To: privateKey.Address(),
PrivateKey: privateKey,
},
)
require.NoError(err)

ginkgo.By(fmt.Sprintf("issuing transactions on chain %s on subnet %s to activate snowman++ consensus",
destinationChainID, destinationSubnet.ID))
for i := 0; i < 3; i++ {
_, err = transfer.Transfer(
e2e.DefaultContext(),
&transfer.Config{
URI: destinationSubnet.Nodes[0].GetProcessContext().URI,
ChainID: destinationChainID,
AssetID: destinationChainID,
Amount: units.Schmeckle,
To: privateKey.Address(),
PrivateKey: privateKey,
},
)
require.NoError(err)
}

ginkgo.By(fmt.Sprintf("importing to blockchain %s on subnet %s", sourceChainID, sourceSubnet.ID))
sourceURIs := make([]string, len(sourceSubnet.Nodes))
for i, node := range sourceSubnet.Nodes {
sourceURIs[i] = node.GetProcessContext().URI
}
_, err = importtx.Import(
e2e.DefaultContext(),
&importtx.Config{
URI: destinationSubnet.Nodes[0].GetProcessContext().URI,
SourceURIs: sourceURIs,
SourceChainID: sourceChainID.String(),
DestinationChainID: destinationChainID.String(),
TxID: exportTxStatus.TxID,
PrivateKey: privateKey,
},
)
require.NoError(err)

// TODO(marun) Verify the balances on both chains
})
})
11 changes: 10 additions & 1 deletion tests/fixture/testnet/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ func (f FlagsMap) Write(path string, description string) error {
return nil
}

// Return a deep copy of the flags map.
func (f FlagsMap) Copy() FlagsMap {
newMap := make(FlagsMap, len(f))
for k, v := range f {
newMap[k] = v
}
return newMap
}

// Utility function simplifying construction of a FlagsMap from a file.
func ReadFlagsMap(path string, description string) (*FlagsMap, error) {
bytes, err := os.ReadFile(path)
Expand All @@ -120,7 +129,7 @@ func DefaultJSONMarshal(v interface{}) ([]byte, error) {
// common to all nodes in a given network.
type NetworkConfig struct {
Genesis *genesis.UnparsedConfig
CChainConfig FlagsMap
ChainConfigs map[string]FlagsMap
DefaultFlags FlagsMap
FundedKeys []*secp256k1.PrivateKey
}
Expand Down
91 changes: 72 additions & 19 deletions tests/fixture/testnet/local/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ const (
networkHealthCheckInterval = 200 * time.Millisecond

defaultEphemeralDirName = "ephemeral"

defaultChainConfigFilename = "config.json"
)

var (
Expand Down Expand Up @@ -110,6 +112,9 @@ func (ln *LocalNetwork) GetNodes() []testnet.Node {
func (ln *LocalNetwork) AddEphemeralNode(w io.Writer, flags testnet.FlagsMap) (testnet.Node, error) {
if flags == nil {
flags = testnet.FlagsMap{}
} else {
// Avoid modifying the input flags map
flags = flags.Copy()
}
return ln.AddLocalNode(w, &LocalNode{
NodeConfig: testnet.NodeConfig{
Expand Down Expand Up @@ -202,7 +207,19 @@ func StartNetwork(

// Read a network from the provided directory.
func ReadNetwork(dir string) (*LocalNetwork, error) {
network := &LocalNetwork{Dir: dir}
// Ensure a real and absolute network dir so that node
// configuration that embeds the network path will continue to
// work regardless of symlink and working directory changes.
absDir, err := filepath.Abs(dir)
if err != nil {
return nil, err
}
realDir, err := filepath.EvalSymlinks(absDir)
if err != nil {
return nil, err
}

network := &LocalNetwork{Dir: realDir}
if err := network.ReadAll(); err != nil {
return nil, fmt.Errorf("failed to read local network: %w", err)
}
Expand Down Expand Up @@ -269,8 +286,11 @@ func (ln *LocalNetwork) PopulateLocalNetworkConfig(networkID uint32, nodeCount i
return err
}

if ln.CChainConfig == nil {
ln.CChainConfig = LocalCChainConfig()
if _, ok := ln.ChainConfigs["C"]; !ok {
if ln.ChainConfigs == nil {
ln.ChainConfigs = map[string]testnet.FlagsMap{}
}
ln.ChainConfigs["C"] = LocalCChainConfig()
}

// Default flags need to be set in advance of node config
Expand Down Expand Up @@ -474,26 +494,59 @@ func (ln *LocalNetwork) GetChainConfigDir() string {
return filepath.Join(ln.Dir, "chains")
}

func (ln *LocalNetwork) GetCChainConfigPath() string {
return filepath.Join(ln.GetChainConfigDir(), "C", "config.json")
}

func (ln *LocalNetwork) ReadCChainConfig() error {
chainConfig, err := testnet.ReadFlagsMap(ln.GetCChainConfigPath(), "C-Chain config")
func (ln *LocalNetwork) ReadChainConfigs() error {
baseChainConfigDir := ln.GetChainConfigDir()
entries, err := os.ReadDir(baseChainConfigDir)
if err != nil {
return err
return fmt.Errorf("failed to read chain config dir: %w", err)
}

// Clear the map of data that may end up stale (e.g. if a given
// chain is in the map but no longer exists on disk)
ln.ChainConfigs = map[string]testnet.FlagsMap{}

for _, entry := range entries {
if !entry.IsDir() {
// Chain config files are expected to be nested under a
// directory with the name of the chain alias.
continue
}
chainAlias := entry.Name()
configPath := filepath.Join(baseChainConfigDir, chainAlias, defaultChainConfigFilename)
if _, err := os.Stat(configPath); os.IsNotExist(err) {
// No config file present
continue
}
chainConfig, err := testnet.ReadFlagsMap(configPath, fmt.Sprintf("%s chain config", chainAlias))
if err != nil {
return err
}
ln.ChainConfigs[chainAlias] = *chainConfig
}
ln.CChainConfig = *chainConfig

return nil
}

func (ln *LocalNetwork) WriteCChainConfig() error {
path := ln.GetCChainConfigPath()
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, perms.ReadWriteExecute); err != nil {
return fmt.Errorf("failed to create C-Chain config dir: %w", err)
func (ln *LocalNetwork) WriteChainConfigs() error {
baseChainConfigDir := ln.GetChainConfigDir()

for chainAlias, chainConfig := range ln.ChainConfigs {
// Create the directory
chainConfigDir := filepath.Join(baseChainConfigDir, chainAlias)
if err := os.MkdirAll(chainConfigDir, perms.ReadWriteExecute); err != nil {
return fmt.Errorf("failed to create %s chain config dir: %w", chainAlias, err)
}

// Write the file
path := filepath.Join(chainConfigDir, defaultChainConfigFilename)
if err := chainConfig.Write(path, fmt.Sprintf("%s chain config", chainAlias)); err != nil {
return err
}
}
return ln.CChainConfig.Write(path, "C-Chain config")

// TODO(marun) Ensure the removal of chain aliases that aren't present in the map

return nil
}

// Used to marshal/unmarshal persistent local network defaults.
Expand Down Expand Up @@ -571,7 +624,7 @@ func (ln *LocalNetwork) WriteAll() error {
if err := ln.WriteGenesis(); err != nil {
return err
}
if err := ln.WriteCChainConfig(); err != nil {
if err := ln.WriteChainConfigs(); err != nil {
return err
}
if err := ln.WriteDefaults(); err != nil {
Expand All @@ -588,7 +641,7 @@ func (ln *LocalNetwork) ReadConfig() error {
if err := ln.ReadGenesis(); err != nil {
return err
}
if err := ln.ReadCChainConfig(); err != nil {
if err := ln.ReadChainConfigs(); err != nil {
return err
}
return ln.ReadDefaults()
Expand Down
Loading

0 comments on commit 53de98f

Please sign in to comment.