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

e2e: Add basic warp test with xsvm #2043

Merged
merged 18 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 2 additions & 12 deletions scripts/tests.e2e.existing.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ if ! [[ "$0" =~ scripts/tests.e2e.existing.sh ]]; then
exit 255
fi

# Ensure an absolute path to avoid dependency on the working directory
# of script execution.
AVALANCHEGO_PATH="$(realpath "${AVALANCHEGO_PATH:-./build/avalanchego}")"
export AVALANCHEGO_PATH

# Provide visual separation between testing and setup/teardown
function print_separator {
printf '%*s\n' "${COLUMNS:-80}" '' | tr ' ' ─
Expand All @@ -30,13 +25,9 @@ function cleanup {
}
trap cleanup EXIT

echo "building e2e.test"
go install -v github.com/onsi/ginkgo/v2/[email protected]
ACK_GINKGO_RC=true ginkgo build ./tests/e2e

print_separator
echo "starting initial test run that should create the reusable network"
ginkgo -v ./tests/e2e/e2e.test -- --reuse-network --ginkgo.focus-file=permissionless_subnets.go
./scripts/tests.e2e.sh --reuse-network --ginkgo.focus-file=xsvm.go

print_separator
echo "determining the network path of the reusable network created by the first test run"
Expand All @@ -45,8 +36,7 @@ INITIAL_NETWORK_DIR="$(realpath "${SYMLINK_PATH}")"

print_separator
echo "starting second test run that should reuse the network created by the first run"
ginkgo -v ./tests/e2e/e2e.test -- --reuse-network --ginkgo.focus-file=permissionless_subnets.go

./scripts/tests.e2e.sh --reuse-network --ginkgo.focus-file=xsvm.go

SUBSEQUENT_NETWORK_DIR="$(realpath "${SYMLINK_PATH}")"
echo "checking that the symlink path remains the same, indicating that the network was reused"
Expand Down
19 changes: 8 additions & 11 deletions scripts/tests.e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ set -euo pipefail
# ./scripts/tests.e2e.sh --ginkgo.label-filter=x # All arguments are supplied to ginkgo
# E2E_SERIAL=1 ./scripts/tests.e2e.sh # Run tests serially
# AVALANCHEGO_PATH=./build/avalanchego ./scripts/tests.e2e.sh # Customization of avalanchego path
# E2E_USE_EXISTING_NETWORK=1 TMPNET_NETWORK_DIR=/path/to ./scripts/tests.e2e.sh # Execute against an existing network
if ! [[ "$0" =~ scripts/tests.e2e.sh ]]; then
echo "must be run from repository root"
exit 255
Expand All @@ -27,16 +26,14 @@ go install -v github.com/onsi/ginkgo/v2/[email protected]
ACK_GINKGO_RC=true ginkgo build ./tests/e2e
./tests/e2e/e2e.test --help

#################################
# Since TMPNET_NETWORK_DIR may be set in the environment (e.g. to configure ginkgo
# or tmpnetctl), configuring the use of an existing network with this script
# requires the extra step of setting E2E_USE_EXISTING_NETWORK=1.
if [[ -n "${E2E_USE_EXISTING_NETWORK:-}" && -n "${TMPNET_NETWORK_DIR:-}" ]]; then
E2E_ARGS="--use-existing-network"
else
AVALANCHEGO_PATH="$(realpath "${AVALANCHEGO_PATH:-./build/avalanchego}")"
E2E_ARGS="--avalanchego-path=${AVALANCHEGO_PATH}"
fi
# Enable subnet testing by building xsvm
./scripts/build_xsvm.sh
echo ""

# Ensure an absolute path to avoid dependency on the working directory
# of script execution.
AVALANCHEGO_PATH="$(realpath "${AVALANCHEGO_PATH:-./build/avalanchego}")"
E2E_ARGS="--avalanchego-path=${AVALANCHEGO_PATH}"

#################################
# Determine ginkgo args
Expand Down
12 changes: 11 additions & 1 deletion tests/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"

"github.com/onsi/gomega"
"github.com/stretchr/testify/require"

// ensure test packages are scanned by ginkgo
_ "github.com/ava-labs/avalanchego/tests/e2e/banff"
Expand All @@ -16,6 +17,7 @@ import (
_ "github.com/ava-labs/avalanchego/tests/e2e/x"
_ "github.com/ava-labs/avalanchego/tests/e2e/x/transfer"

"github.com/ava-labs/avalanchego/tests/e2e/vms"
"github.com/ava-labs/avalanchego/tests/fixture/e2e"
"github.com/ava-labs/avalanchego/tests/fixture/tmpnet"

Expand All @@ -35,10 +37,18 @@ func init() {

var _ = ginkgo.SynchronizedBeforeSuite(func() []byte {
// Run only once in the first ginkgo process

nodes, err := tmpnet.NewNodes(tmpnet.DefaultNodeCount)
require.NoError(ginkgo.GinkgoT(), err)

subnets := vms.XSVMSubnets(nodes...)

return e2e.NewTestEnvironment(
flagVars,
&tmpnet.Network{
Owner: "avalanchego-e2e",
Owner: "avalanchego-e2e",
Nodes: nodes,
Subnets: subnets,
},
).Marshal()
}, func(envBytes []byte) {
Expand Down
179 changes: 179 additions & 0 deletions tests/e2e/vms/xsvm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package vms

import (
"fmt"
"math"
"time"

"github.com/stretchr/testify/require"

"github.com/ava-labs/avalanchego/tests"
"github.com/ava-labs/avalanchego/tests/fixture/e2e"
"github.com/ava-labs/avalanchego/tests/fixture/tmpnet"
"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
"github.com/ava-labs/avalanchego/utils/units"
"github.com/ava-labs/avalanchego/vms/example/xsvm"
"github.com/ava-labs/avalanchego/vms/example/xsvm/api"
"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"

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

var (
subnetAName = "xsvm-a"
subnetBName = "xsvm-b"
)

func XSVMSubnets(nodes ...*tmpnet.Node) []*tmpnet.Subnet {
return []*tmpnet.Subnet{
newXSVMSubnet(subnetAName, nodes...),
newXSVMSubnet(subnetBName, nodes...),
}
}

var _ = ginkgo.Describe("[XSVM]", func() {
require := require.New(ginkgo.GinkgoT())

ginkgo.It("should support transfers between subnets", func() {
network := e2e.Env.GetNetwork()

sourceSubnet := network.GetSubnet(subnetAName)
require.NotNil(sourceSubnet)
destinationSubnet := network.GetSubnet(subnetBName)
require.NotNil(destinationSubnet)

sourceChain := sourceSubnet.Chains[0]
destinationChain := destinationSubnet.Chains[0]

apiNode := network.Nodes[0]
tests.Outf(" issuing transactions on %s (%s)\n", apiNode.NodeID, apiNode.URI)

destinationKey, err := secp256k1.NewPrivateKey()
require.NoError(err)

ginkgo.By("checking that the funded key has sufficient funds for the export")
sourceClient := api.NewClient(apiNode.URI, sourceChain.ChainID.String())
initialSourcedBalance, err := sourceClient.Balance(
e2e.DefaultContext(),
sourceChain.PreFundedKey.Address(),
sourceChain.ChainID,
)
require.NoError(err)
require.GreaterOrEqual(initialSourcedBalance, units.Schmeckle)

ginkgo.By(fmt.Sprintf("exporting from chain %s on subnet %s", sourceChain.ChainID, sourceSubnet.SubnetID))
exportTxStatus, err := export.Export(
e2e.DefaultContext(),
&export.Config{
URI: apiNode.URI,
SourceChainID: sourceChain.ChainID,
DestinationChainID: destinationChain.ChainID,
Amount: units.Schmeckle,
To: destinationKey.Address(),
PrivateKey: sourceChain.PreFundedKey,
},
)
require.NoError(err)
tests.Outf(" issued transaction with ID: %s\n", exportTxStatus.TxID)

ginkgo.By("checking that the export transaction has been accepted on all nodes")
for _, node := range network.Nodes[1:] {
require.NoError(api.WaitForAcceptance(
e2e.DefaultContext(),
api.NewClient(node.URI, sourceChain.ChainID.String()),
sourceChain.PreFundedKey.Address(),
exportTxStatus.Nonce,
))
}

ginkgo.By(fmt.Sprintf("issuing transaction on chain %s on subnet %s to activate snowman++ consensus",
destinationChain.ChainID, destinationSubnet.SubnetID))
recipientKey, err := secp256k1.NewPrivateKey()
require.NoError(err)
transferTxStatus, err := transfer.Transfer(
e2e.DefaultContext(),
&transfer.Config{
URI: apiNode.URI,
ChainID: destinationChain.ChainID,
AssetID: destinationChain.ChainID,
Amount: units.Schmeckle,
To: recipientKey.Address(),
PrivateKey: destinationChain.PreFundedKey,
},
)
require.NoError(err)
tests.Outf(" issued transaction with ID: %s\n", transferTxStatus.TxID)

ginkgo.By(fmt.Sprintf("importing to blockchain %s on subnet %s", destinationChain.ChainID, destinationSubnet.SubnetID))
sourceURIs := make([]string, len(network.Nodes))
for i, node := range network.Nodes {
sourceURIs[i] = node.URI
}
importTxStatus, err := importtx.Import(
e2e.DefaultContext(),
&importtx.Config{
URI: apiNode.URI,
SourceURIs: sourceURIs,
SourceChainID: sourceChain.ChainID.String(),
DestinationChainID: destinationChain.ChainID.String(),
TxID: exportTxStatus.TxID,
PrivateKey: destinationKey,
},
)
require.NoError(err)
tests.Outf(" issued transaction with ID: %s\n", importTxStatus.TxID)

ginkgo.By("checking that the balance of the source key has decreased")
sourceBalance, err := sourceClient.Balance(e2e.DefaultContext(), sourceChain.PreFundedKey.Address(), sourceChain.ChainID)
require.NoError(err)
require.GreaterOrEqual(initialSourcedBalance-units.Schmeckle, sourceBalance)

ginkgo.By("checking that the balance of the destination key is non-zero")
destinationClient := api.NewClient(apiNode.URI, destinationChain.ChainID.String())
destinationBalance, err := destinationClient.Balance(e2e.DefaultContext(), destinationKey.Address(), sourceChain.ChainID)
require.NoError(err)
require.Equal(units.Schmeckle, destinationBalance)
})
})

func newXSVMSubnet(name string, nodes ...*tmpnet.Node) *tmpnet.Subnet {
if len(nodes) == 0 {
panic("a subnet must be validated by at least one node")
}

key, err := secp256k1.NewPrivateKey()
if err != nil {
panic(err)
}

genesisBytes, err := genesis.Codec.Marshal(genesis.CodecVersion, &genesis.Genesis{
Timestamp: time.Now().Unix(),
Allocations: []genesis.Allocation{
{
Address: key.Address(),
Balance: math.MaxUint64,
},
},
})
if err != nil {
panic(err)
}

return &tmpnet.Subnet{
Name: name,
Chains: []*tmpnet.Chain{
{
VMID: xsvm.ID,
Genesis: genesisBytes,
PreFundedKey: key,
},
},
ValidatorIDs: tmpnet.NodesToIDs(nodes...),
}
}
1 change: 1 addition & 0 deletions tests/fixture/tmpnet/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func DefaultFlags() FlagsMap {
config.LogDisplayLevelKey: logging.Off.String(), // Display logging not needed since nodes run headless
config.LogLevelKey: logging.Debug.String(),
config.MinStakeDurationKey: DefaultMinStakeDuration.String(),
config.ProposerVMUseCurrentHeightKey: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the main fix for the e2e test. The reason that we need to issue the transfer tx on the destination chain is to update the pChainHeight on the destination subnet to enable verifying the warp message. However, without the flag set, the P-chain height lags by 2 minutes and at least 16 blocks. During the test, the P-chain has height 9. So, even if we added a 2 minute sleep, it wouldn't advance past height=0 (which is before the subnet validators are added).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the explanation and fix, much apprecaited!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a test-only flag, or would there ever be reason to use it in production?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's valid to use in a production network.. But not ideal

}
}

Expand Down
31 changes: 31 additions & 0 deletions vms/example/xsvm/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package api
import (
"context"
"fmt"
"time"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/constants"
Expand All @@ -16,6 +17,8 @@ import (
"github.com/ava-labs/avalanchego/vms/platformvm/warp"
)

const defaultPollingInterval = 50 * time.Millisecond

// Client defines the xsvm API client.
type Client interface {
Network(
Expand Down Expand Up @@ -241,3 +244,31 @@ func (c *client) Message(
}
return resp.Message, resp.Signature, resp.Message.Initialize()
}

func WaitForAcceptance(
ctx context.Context,
c Client,
address ids.ShortID,
nonce uint64,
options ...rpc.Option,
) error {
ticker := time.NewTicker(defaultPollingInterval)
defer ticker.Stop()
for {
currentNonce, err := c.Nonce(ctx, address, options...)
if err != nil {
return err
}
if currentNonce > nonce {
// The nonce increasing indicates the acceptance of a transaction
// issued with the specified nonce.
return nil
}

select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
}
}
}
Loading
Loading