From 8d9d7d7a57dd9dee100b04564cd2273e123430da Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Tue, 29 Aug 2023 10:26:09 -0700 Subject: [PATCH 01/11] e2e: Increase logging detail to improve traceability --- tests/e2e/banff/suites.go | 2 +- tests/e2e/c/interchain_workflow.go | 59 ++++++----- tests/e2e/e2e.go | 82 ++++++++++++--- tests/e2e/p/permissionless_subnets.go | 133 +++++++++++++------------ tests/e2e/p/workflow.go | 2 +- tests/e2e/x/interchain_workflow.go | 75 +++++++------- tests/e2e/x/transfer/virtuous.go | 6 +- tests/fixture/testnet/local/config.go | 2 +- tests/fixture/testnet/local/network.go | 6 +- 9 files changed, 219 insertions(+), 148 deletions(-) diff --git a/tests/e2e/banff/suites.go b/tests/e2e/banff/suites.go index a19d3c1eb511..0ebb54123cbf 100644 --- a/tests/e2e/banff/suites.go +++ b/tests/e2e/banff/suites.go @@ -29,7 +29,7 @@ var _ = ginkgo.Describe("[Banff]", func() { ), func() { keychain := e2e.Env.NewKeychain(1) - wallet := e2e.Env.NewWallet(keychain) + wallet := e2e.Env.NewWallet(keychain, e2e.Env.GetRandomNodeURI()) // Get the P-chain and the X-chain wallets pWallet := wallet.P() diff --git a/tests/e2e/c/interchain_workflow.go b/tests/e2e/c/interchain_workflow.go index c5afe5659682..abf15137beb5 100644 --- a/tests/e2e/c/interchain_workflow.go +++ b/tests/e2e/c/interchain_workflow.go @@ -32,6 +32,11 @@ var _ = e2e.DescribeCChain("[Interchain Workflow]", func() { ) ginkgo.It("should ensure that funds can be transferred from the C-Chain to the X-Chain and the P-Chain", func() { + // Select a random node URI to use for both the eth client and + // the wallet to avoid having to verify that all nodes are at + // the same height before initializing the wallet. + nodeURI := e2e.Env.GetRandomNodeURI() + ginkgo.By("allocating a pre-funded key to send from and a recipient key to deliver to") senderKey := e2e.Env.AllocateFundedKey() senderEthAddress := evm.GetEthAddress(senderKey) @@ -40,11 +45,7 @@ var _ = e2e.DescribeCChain("[Interchain Workflow]", func() { require.NoError(err) recipientEthAddress := evm.GetEthAddress(recipientKey) - // Select a random node URI to use for both the eth client and - // the wallet to avoid having to verify that all nodes are at - // the same height before initializing the wallet. - nodeURI := e2e.Env.GetRandomNodeURI() - ethClient := e2e.Env.NewEthClientForURI(nodeURI) + ethClient := e2e.Env.NewEthClient(nodeURI) ginkgo.By("sending funds from one address to another on the C-Chain", func() { // Create transaction @@ -68,7 +69,7 @@ var _ = e2e.DescribeCChain("[Interchain Workflow]", func() { signedTx, err := types.SignTx(tx, signer, senderKey.ToECDSA()) require.NoError(err) - require.NoError(ethClient.SendTransaction(e2e.DefaultContext(), signedTx)) + _ = e2e.SendEthTransaction(ethClient, signedTx) ginkgo.By("waiting for the C-Chain recipient address to have received the sent funds") e2e.Eventually(func() bool { @@ -83,7 +84,7 @@ var _ = e2e.DescribeCChain("[Interchain Workflow]", func() { // matches on-chain state. ginkgo.By("initializing a keychain and associated wallet") keychain := secp256k1fx.NewKeychain(senderKey, recipientKey) - baseWallet := e2e.Env.NewWalletForURI(keychain, nodeURI) + baseWallet := e2e.Env.NewWallet(keychain, nodeURI) xWallet := baseWallet.X() cWallet := baseWallet.C() pWallet := baseWallet.P() @@ -111,21 +112,23 @@ var _ = e2e.DescribeCChain("[Interchain Workflow]", func() { } ginkgo.By("exporting AVAX from the C-Chain to the X-Chain", func() { - _, err := cWallet.IssueExportTx( - xWallet.BlockchainID(), - exportOutputs, - e2e.WithDefaultContext(), + e2e.LogTxAndCheck( + cWallet.IssueExportTx( + xWallet.BlockchainID(), + exportOutputs, + e2e.WithDefaultContext(), + ), ) - require.NoError(err) }) ginkgo.By("importing AVAX from the C-Chain to the X-Chain", func() { - _, err := xWallet.IssueImportTx( - cWallet.BlockchainID(), - &recipientOwner, - e2e.WithDefaultContext(), + e2e.LogTxAndCheck( + xWallet.IssueImportTx( + cWallet.BlockchainID(), + &recipientOwner, + e2e.WithDefaultContext(), + ), ) - require.NoError(err) }) ginkgo.By("checking that the recipient address has received imported funds on the X-Chain", func() { @@ -137,21 +140,23 @@ var _ = e2e.DescribeCChain("[Interchain Workflow]", func() { }) ginkgo.By("exporting AVAX from the C-Chain to the P-Chain", func() { - _, err := cWallet.IssueExportTx( - constants.PlatformChainID, - exportOutputs, - e2e.WithDefaultContext(), + e2e.LogTxAndCheck( + cWallet.IssueExportTx( + constants.PlatformChainID, + exportOutputs, + e2e.WithDefaultContext(), + ), ) - require.NoError(err) }) ginkgo.By("importing AVAX from the C-Chain to the P-Chain", func() { - _, err = pWallet.IssueImportTx( - cWallet.BlockchainID(), - &recipientOwner, - e2e.WithDefaultContext(), + e2e.LogTxAndCheck( + pWallet.IssueImportTx( + cWallet.BlockchainID(), + &recipientOwner, + e2e.WithDefaultContext(), + ), ) - require.NoError(err) }) ginkgo.By("checking that the recipient address has received imported funds on the P-Chain", func() { diff --git a/tests/e2e/e2e.go b/tests/e2e/e2e.go index faeda0f7554e..819db34f6a44 100644 --- a/tests/e2e/e2e.go +++ b/tests/e2e/e2e.go @@ -7,6 +7,7 @@ package e2e import ( "context" "encoding/json" + "errors" "fmt" "math/rand" "strings" @@ -16,8 +17,13 @@ import ( "github.com/stretchr/testify/require" + "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/ethclient" + "github.com/ava-labs/coreth/interfaces" + "golang.org/x/exp/maps" + + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/tests" "github.com/ava-labs/avalanchego/tests/fixture" "github.com/ava-labs/avalanchego/tests/fixture/testnet" @@ -58,7 +64,7 @@ type TestEnvironment struct { // The directory where the test network configuration is stored NetworkDir string // URIs used to access the API endpoints of nodes of the network - URIs []string + URIs map[ids.NodeID]string // The URI used to access the http server that allocates test data TestDataServerURI string @@ -78,7 +84,11 @@ func InitTestEnvironment(envBytes []byte) { // nodes. func (te *TestEnvironment) GetRandomNodeURI() string { r := rand.New(rand.NewSource(time.Now().Unix())) //#nosec G404 - return te.URIs[r.Intn(len(te.URIs))] + nodeIDs := maps.Keys(te.URIs) + nodeID := nodeIDs[r.Intn(len(nodeIDs))] + uri := te.URIs[nodeID] + tests.Outf("{{blue}} targeting node %s with URI: %s{{/}}\n", nodeID, uri) + return uri } // Retrieve the network to target for testing. @@ -92,6 +102,7 @@ func (te *TestEnvironment) GetNetwork() testnet.Network { func (te *TestEnvironment) AllocateFundedKeys(count int) []*secp256k1.PrivateKey { keys, err := fixture.AllocateFundedKeys(te.TestDataServerURI, count) te.require.NoError(err) + tests.Outf("{{blue}} allocated funded key(s): %+v{{/}}\n", keys) return keys } @@ -102,19 +113,14 @@ func (te *TestEnvironment) AllocateFundedKey() *secp256k1.PrivateKey { // Create a new keychain with the specified number of test keys. func (te *TestEnvironment) NewKeychain(count int) *secp256k1fx.Keychain { - tests.Outf("{{blue}} initializing keychain with %d keys {{/}}\n", count) keys := te.AllocateFundedKeys(count) + tests.Outf("{{blue}} initializing keychain with %d key(s) {{/}}\n", count) return secp256k1fx.NewKeychain(keys...) } -// Create a new wallet for the provided keychain against a random node URI. -func (te *TestEnvironment) NewWallet(keychain *secp256k1fx.Keychain) primary.Wallet { - return te.NewWalletForURI(keychain, te.GetRandomNodeURI()) -} - // Create a new wallet for the provided keychain against the specified node URI. -func (te *TestEnvironment) NewWalletForURI(keychain *secp256k1fx.Keychain, uri string) primary.Wallet { - tests.Outf("{{blue}} initializing a new wallet {{/}}\n") +func (te *TestEnvironment) NewWallet(keychain *secp256k1fx.Keychain, uri string) primary.Wallet { + tests.Outf("{{blue}} initializing a new wallet for URI: %s {{/}}\n", uri) wallet, err := primary.MakeWallet(DefaultContext(), &primary.WalletConfig{ URI: uri, AVAXKeychain: keychain, @@ -124,13 +130,9 @@ func (te *TestEnvironment) NewWalletForURI(keychain *secp256k1fx.Keychain, uri s return wallet } -// Create a new eth client targeting a random node. -func (te *TestEnvironment) NewEthClient() ethclient.Client { - return te.NewEthClientForURI(te.GetRandomNodeURI()) -} - // Create a new eth client targeting the specified node URI. -func (te *TestEnvironment) NewEthClientForURI(nodeURI string) ethclient.Client { +func (te *TestEnvironment) NewEthClient(nodeURI string) ethclient.Client { + tests.Outf("{{blue}} initializing a new eth client for URI: %s {{/}}\n", nodeURI) nodeAddress := strings.Split(nodeURI, "//")[1] uri := fmt.Sprintf("ws://%s/ext/bc/C/ws", nodeAddress) client, err := ethclient.Dial(uri) @@ -198,3 +200,51 @@ func WaitForHealthy(node testnet.Node) { defer cancel() require.NoError(ginkgo.GinkgoT(), testnet.WaitForHealthy(ctx, node)) } + +// Interface to allow logging any type of transaction that has an ID method. +type LoggableTx interface { + ID() ids.ID +} + +// Ensures transaction id is logged if the transaction is non-nil. Should be +// called before checking for an error to ensure traceability in the event +// of a transaction being submitted but failing to be accepted. +func LogTx(tx LoggableTx) { + if tx != nil { + tests.Outf(" tx id: %s\n", tx.ID()) + } +} + +// Ensures transaction id is logged if the transaction is non-nil and check +// if an error occurred. +func LogTxAndCheck(tx LoggableTx, err error) { + LogTx(tx) + require.NoError(ginkgo.GinkgoT(), err) +} + +// Sends an eth transaction, waits for the transaction receipt to be issued +// and checks that the receipt indicates success. +func SendEthTransaction(ethClient ethclient.Client, signedTx *types.Transaction) *types.Receipt { + require := require.New(ginkgo.GinkgoT()) + + txID := signedTx.Hash() + tests.Outf(" eth tx id: %s\n", txID) + + require.NoError(ethClient.SendTransaction(DefaultContext(), signedTx)) + + // Wait for the receipt + var receipt *types.Receipt + Eventually(func() bool { + var err error + receipt, err = ethClient.TransactionReceipt(DefaultContext(), txID) + if errors.Is(err, interfaces.NotFound) { + return false // Transaction is still pending + } + require.NoError(err) + return true + }, DefaultTimeout, DefaultPollingInterval, "failed to see transaction acceptance before timeout") + + // Retrieve the contract address + require.Equal(receipt.Status, types.ReceiptStatusSuccessful) + return receipt +} diff --git a/tests/e2e/p/permissionless_subnets.go b/tests/e2e/p/permissionless_subnets.go index 3074d1a55ac6..09c9b7c6fea6 100644 --- a/tests/e2e/p/permissionless_subnets.go +++ b/tests/e2e/p/permissionless_subnets.go @@ -35,10 +35,11 @@ var _ = e2e.DescribePChain("[Permissionless Subnets]", func() { "permissionless-subnets", ), func() { + nodeURI := e2e.Env.GetRandomNodeURI() + keychain := e2e.Env.NewKeychain(1) - baseWallet := e2e.Env.NewWallet(keychain) + baseWallet := e2e.Env.NewWallet(keychain, nodeURI) - nodeURI := e2e.Env.GetRandomNodeURI() pWallet := baseWallet.P() xWallet := baseWallet.X() xChainID := xWallet.BlockchainID() @@ -69,7 +70,7 @@ var _ = e2e.DescribePChain("[Permissionless Subnets]", func() { common.WithContext(ctx), ) cancel() - + e2e.LogTx(subnetTx) subnetID = subnetTx.ID() gomega.Expect(subnetID, err).Should(gomega.Not(gomega.Equal(constants.PrimaryNetworkID))) }) @@ -92,108 +93,114 @@ var _ = e2e.DescribePChain("[Permissionless Subnets]", func() { common.WithContext(ctx), ) cancel() + e2e.LogTx(subnetAssetTx) gomega.Expect(err).Should(gomega.BeNil()) subnetAssetID = subnetAssetTx.ID() }) ginkgo.By(fmt.Sprintf("Send 100 MegaAvax of asset %s to the P-chain", subnetAssetID), func() { ctx, cancel := context.WithTimeout(context.Background(), e2e.DefaultTimeout) - _, err := xWallet.IssueExportTx( - constants.PlatformChainID, - []*avax.TransferableOutput{ - { - Asset: avax.Asset{ - ID: subnetAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 100 * units.MegaAvax, - OutputOwners: *owner, + e2e.LogTxAndCheck( + xWallet.IssueExportTx( + constants.PlatformChainID, + []*avax.TransferableOutput{ + { + Asset: avax.Asset{ + ID: subnetAssetID, + }, + Out: &secp256k1fx.TransferOutput{ + Amt: 100 * units.MegaAvax, + OutputOwners: *owner, + }, }, }, - }, - common.WithContext(ctx), + common.WithContext(ctx), + ), ) cancel() - gomega.Expect(err).Should(gomega.BeNil()) }) ginkgo.By(fmt.Sprintf("Import the 100 MegaAvax of asset %s from the X-chain into the P-chain", subnetAssetID), func() { ctx, cancel := context.WithTimeout(context.Background(), e2e.DefaultTimeout) - _, err := pWallet.IssueImportTx( + tx, err := pWallet.IssueImportTx( xChainID, owner, common.WithContext(ctx), ) cancel() + e2e.LogTx(tx) gomega.Expect(err).Should(gomega.BeNil()) }) ginkgo.By("make subnet permissionless", func() { ctx, cancel := context.WithTimeout(context.Background(), e2e.DefaultTimeout) - _, err := pWallet.IssueTransformSubnetTx( - subnetID, - subnetAssetID, - 50*units.MegaAvax, - 100*units.MegaAvax, - reward.PercentDenominator, - reward.PercentDenominator, - 1, - 100*units.MegaAvax, - time.Second, - 365*24*time.Hour, - 0, - 1, - 5, - .80*reward.PercentDenominator, - common.WithContext(ctx), + e2e.LogTxAndCheck( + pWallet.IssueTransformSubnetTx( + subnetID, + subnetAssetID, + 50*units.MegaAvax, + 100*units.MegaAvax, + reward.PercentDenominator, + reward.PercentDenominator, + 1, + 100*units.MegaAvax, + time.Second, + 365*24*time.Hour, + 0, + 1, + 5, + .80*reward.PercentDenominator, + common.WithContext(ctx), + ), ) cancel() - gomega.Expect(err).Should(gomega.BeNil()) }) validatorStartTime := time.Now().Add(time.Minute) ginkgo.By("add permissionless validator", func() { ctx, cancel := context.WithTimeout(context.Background(), e2e.DefaultTimeout) - _, err := pWallet.IssueAddPermissionlessValidatorTx( - &txs.SubnetValidator{ - Validator: txs.Validator{ - NodeID: validatorID, - Start: uint64(validatorStartTime.Unix()), - End: uint64(validatorStartTime.Add(5 * time.Second).Unix()), - Wght: 25 * units.MegaAvax, + e2e.LogTxAndCheck( + pWallet.IssueAddPermissionlessValidatorTx( + &txs.SubnetValidator{ + Validator: txs.Validator{ + NodeID: validatorID, + Start: uint64(validatorStartTime.Unix()), + End: uint64(validatorStartTime.Add(5 * time.Second).Unix()), + Wght: 25 * units.MegaAvax, + }, + Subnet: subnetID, }, - Subnet: subnetID, - }, - &signer.Empty{}, - subnetAssetID, - &secp256k1fx.OutputOwners{}, - &secp256k1fx.OutputOwners{}, - reward.PercentDenominator, - common.WithContext(ctx), + &signer.Empty{}, + subnetAssetID, + &secp256k1fx.OutputOwners{}, + &secp256k1fx.OutputOwners{}, + reward.PercentDenominator, + common.WithContext(ctx), + ), ) cancel() - gomega.Expect(err).Should(gomega.BeNil()) }) delegatorStartTime := validatorStartTime ginkgo.By("add permissionless delegator", func() { ctx, cancel := context.WithTimeout(context.Background(), e2e.DefaultTimeout) - _, err := pWallet.IssueAddPermissionlessDelegatorTx( - &txs.SubnetValidator{ - Validator: txs.Validator{ - NodeID: validatorID, - Start: uint64(delegatorStartTime.Unix()), - End: uint64(delegatorStartTime.Add(5 * time.Second).Unix()), - Wght: 25 * units.MegaAvax, + e2e.LogTxAndCheck( + pWallet.IssueAddPermissionlessDelegatorTx( + &txs.SubnetValidator{ + Validator: txs.Validator{ + NodeID: validatorID, + Start: uint64(delegatorStartTime.Unix()), + End: uint64(delegatorStartTime.Add(5 * time.Second).Unix()), + Wght: 25 * units.MegaAvax, + }, + Subnet: subnetID, }, - Subnet: subnetID, - }, - subnetAssetID, - &secp256k1fx.OutputOwners{}, - common.WithContext(ctx), + subnetAssetID, + &secp256k1fx.OutputOwners{}, + common.WithContext(ctx), + ), ) cancel() - gomega.Expect(err).Should(gomega.BeNil()) }) }) }) diff --git a/tests/e2e/p/workflow.go b/tests/e2e/p/workflow.go index 422428dfe5ec..31908ece07a0 100644 --- a/tests/e2e/p/workflow.go +++ b/tests/e2e/p/workflow.go @@ -44,7 +44,7 @@ var _ = e2e.DescribePChain("[Workflow]", func() { func() { nodeURI := e2e.Env.GetRandomNodeURI() keychain := e2e.Env.NewKeychain(2) - baseWallet := e2e.Env.NewWallet(keychain) + baseWallet := e2e.Env.NewWallet(keychain, nodeURI) pWallet := baseWallet.P() avaxAssetID := baseWallet.P().AVAXAssetID() diff --git a/tests/e2e/x/interchain_workflow.go b/tests/e2e/x/interchain_workflow.go index fb0c438139ef..182c451734bd 100644 --- a/tests/e2e/x/interchain_workflow.go +++ b/tests/e2e/x/interchain_workflow.go @@ -29,13 +29,15 @@ var _ = e2e.DescribeXChain("[Interchain Workflow]", func() { const transferAmount = 10 * units.Avax ginkgo.It("should ensure that funds can be transferred from the X-Chain to the C-Chain and the P-Chain", func() { + nodeURI := e2e.Env.GetRandomNodeURI() + ginkgo.By("creating wallet with a funded key to send from and recipient key to deliver to") factory := secp256k1.Factory{} recipientKey, err := factory.NewPrivateKey() require.NoError(err) keychain := e2e.Env.NewKeychain(1) keychain.Add(recipientKey) - baseWallet := e2e.Env.NewWallet(keychain) + baseWallet := e2e.Env.NewWallet(keychain, nodeURI) xWallet := baseWallet.X() cWallet := baseWallet.C() pWallet := baseWallet.P() @@ -69,19 +71,20 @@ var _ = e2e.DescribeXChain("[Interchain Workflow]", func() { } ginkgo.By("sending funds from one address to another on the X-Chain", func() { - _, err = xWallet.IssueBaseTx( - []*avax.TransferableOutput{{ - Asset: avax.Asset{ - ID: avaxAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: transferAmount, - OutputOwners: recipientOwner, - }, - }}, - e2e.WithDefaultContext(), + e2e.LogTxAndCheck( + xWallet.IssueBaseTx( + []*avax.TransferableOutput{{ + Asset: avax.Asset{ + ID: avaxAssetID, + }, + Out: &secp256k1fx.TransferOutput{ + Amt: transferAmount, + OutputOwners: recipientOwner, + }, + }}, + e2e.WithDefaultContext(), + ), ) - require.NoError(err) }) ginkgo.By("checking that the X-Chain recipient address has received the sent funds", func() { @@ -93,25 +96,27 @@ var _ = e2e.DescribeXChain("[Interchain Workflow]", func() { }) ginkgo.By("exporting AVAX from the X-Chain to the C-Chain", func() { - _, err := xWallet.IssueExportTx( - cWallet.BlockchainID(), - exportOutputs, - e2e.WithDefaultContext(), + e2e.LogTxAndCheck( + xWallet.IssueExportTx( + cWallet.BlockchainID(), + exportOutputs, + e2e.WithDefaultContext(), + ), ) - require.NoError(err) }) ginkgo.By("importing AVAX from the X-Chain to the C-Chain", func() { - _, err := cWallet.IssueImportTx( - xWallet.BlockchainID(), - recipientEthAddress, - e2e.WithDefaultContext(), + e2e.LogTxAndCheck( + cWallet.IssueImportTx( + xWallet.BlockchainID(), + recipientEthAddress, + e2e.WithDefaultContext(), + ), ) - require.NoError(err) }) ginkgo.By("checking that the recipient address has received imported funds on the C-Chain") - ethClient := e2e.Env.NewEthClient() + ethClient := e2e.Env.NewEthClient(nodeURI) e2e.Eventually(func() bool { balance, err := ethClient.BalanceAt(e2e.DefaultContext(), recipientEthAddress, nil) require.NoError(err) @@ -119,21 +124,23 @@ var _ = e2e.DescribeXChain("[Interchain Workflow]", func() { }, e2e.DefaultTimeout, e2e.DefaultPollingInterval, "failed to see recipient address funded before timeout") ginkgo.By("exporting AVAX from the X-Chain to the P-Chain", func() { - _, err := xWallet.IssueExportTx( - constants.PlatformChainID, - exportOutputs, - e2e.WithDefaultContext(), + e2e.LogTxAndCheck( + xWallet.IssueExportTx( + constants.PlatformChainID, + exportOutputs, + e2e.WithDefaultContext(), + ), ) - require.NoError(err) }) ginkgo.By("importing AVAX from the X-Chain to the P-Chain", func() { - _, err := pWallet.IssueImportTx( - xWallet.BlockchainID(), - &recipientOwner, - e2e.WithDefaultContext(), + e2e.LogTxAndCheck( + pWallet.IssueImportTx( + xWallet.BlockchainID(), + &recipientOwner, + e2e.WithDefaultContext(), + ), ) - require.NoError(err) }) ginkgo.By("checking that the recipient address has received imported funds on the P-Chain", func() { diff --git a/tests/e2e/x/transfer/virtuous.go b/tests/e2e/x/transfer/virtuous.go index 38ceb858bcbd..b838084dba19 100644 --- a/tests/e2e/x/transfer/virtuous.go +++ b/tests/e2e/x/transfer/virtuous.go @@ -14,6 +14,8 @@ import ( ginkgo "github.com/onsi/ginkgo/v2" + "golang.org/x/exp/maps" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/choices" "github.com/ava-labs/avalanchego/tests" @@ -44,7 +46,7 @@ var _ = e2e.DescribeXChainSerial("[Virtuous Transfer Tx AVAX]", func() { "virtuous-transfer-tx-avax", ), func() { - rpcEps := e2e.Env.URIs + rpcEps := maps.Values(e2e.Env.URIs) // Waiting for ongoing blocks to have completed before starting this // test avoids the case of a previous test having initiated block @@ -84,7 +86,7 @@ var _ = e2e.DescribeXChainSerial("[Virtuous Transfer Tx AVAX]", func() { } keychain := secp256k1fx.NewKeychain(testKeys...) - baseWallet := e2e.Env.NewWallet(keychain) + baseWallet := e2e.Env.NewWallet(keychain, e2e.Env.GetRandomNodeURI()) avaxAssetID := baseWallet.X().AVAXAssetID() wallets := make([]primary.Wallet, len(testKeys)) diff --git a/tests/fixture/testnet/local/config.go b/tests/fixture/testnet/local/config.go index 19652e76a313..c9ab3d5b2420 100644 --- a/tests/fixture/testnet/local/config.go +++ b/tests/fixture/testnet/local/config.go @@ -46,6 +46,6 @@ func LocalCChainConfig() testnet.FlagsMap { // values will be used. Available C-Chain configuration options are // defined in the `github.com/ava-labs/coreth/evm` package. return testnet.FlagsMap{ - "log-level": "debug", + "log-level": "trace", } } diff --git a/tests/fixture/testnet/local/network.go b/tests/fixture/testnet/local/network.go index 5261a01476bb..01a5ca2634ce 100644 --- a/tests/fixture/testnet/local/network.go +++ b/tests/fixture/testnet/local/network.go @@ -409,14 +409,14 @@ func (ln *LocalNetwork) WaitForHealthy(ctx context.Context, w io.Writer) error { // Retrieve API URIs for all nodes in the network. Assumes nodes have // been loaded. -func (ln *LocalNetwork) GetURIs() []string { - uris := make([]string, 0, len(ln.Nodes)) +func (ln *LocalNetwork) GetURIs() map[ids.NodeID]string { + uris := make(map[ids.NodeID]string, len(ln.Nodes)) for _, node := range ln.Nodes { // Only append URIs that are not empty. A node may have an // empty URI if it was not running at the time // node.ReadProcessContext() was called. if len(node.URI) > 0 { - uris = append(uris, node.URI) + uris[node.NodeID] = node.URI } } return uris From eefce0a4fea65e4964e2098e3c35806cc37af7f6 Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Fri, 1 Sep 2023 08:59:49 -0700 Subject: [PATCH 02/11] fixup: Double suggested gas price for C-Chain tests to ensure acceptance --- tests/e2e/c/interchain_workflow.go | 5 +++-- tests/e2e/e2e.go | 19 +++++++++++++++++++ tests/e2e/x/interchain_workflow.go | 5 ++++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/tests/e2e/c/interchain_workflow.go b/tests/e2e/c/interchain_workflow.go index abf15137beb5..3b8f17890342 100644 --- a/tests/e2e/c/interchain_workflow.go +++ b/tests/e2e/c/interchain_workflow.go @@ -51,8 +51,7 @@ var _ = e2e.DescribeCChain("[Interchain Workflow]", func() { // Create transaction acceptedNonce, err := ethClient.AcceptedNonceAt(e2e.DefaultContext(), senderEthAddress) require.NoError(err) - gasPrice, err := ethClient.SuggestGasPrice(e2e.DefaultContext()) - require.NoError(err) + gasPrice := e2e.SuggestGasPrice(ethClient) tx := types.NewTransaction( acceptedNonce, recipientEthAddress, @@ -117,6 +116,7 @@ var _ = e2e.DescribeCChain("[Interchain Workflow]", func() { xWallet.BlockchainID(), exportOutputs, e2e.WithDefaultContext(), + e2e.WithSuggestedGasPrice(ethClient), ), ) }) @@ -145,6 +145,7 @@ var _ = e2e.DescribeCChain("[Interchain Workflow]", func() { constants.PlatformChainID, exportOutputs, e2e.WithDefaultContext(), + e2e.WithSuggestedGasPrice(ethClient), ), ) }) diff --git a/tests/e2e/e2e.go b/tests/e2e/e2e.go index 819db34f6a44..9771875a3013 100644 --- a/tests/e2e/e2e.go +++ b/tests/e2e/e2e.go @@ -9,6 +9,7 @@ import ( "encoding/json" "errors" "fmt" + "math/big" "math/rand" "strings" "time" @@ -248,3 +249,21 @@ func SendEthTransaction(ethClient ethclient.Client, signedTx *types.Transaction) require.Equal(receipt.Status, types.ReceiptStatusSuccessful) return receipt } + +// Determines the suggested gas price for the configured client that will +// maximize the chances of transaction acceptance. +func SuggestGasPrice(ethClient ethclient.Client) *big.Int { + gasPrice, err := ethClient.SuggestGasPrice(DefaultContext()) + require.NoError(ginkgo.GinkgoT(), err) + // Double the suggested gas price to maximize the chances of + // acceptance. Maybe this can be revisited pending resolution of + // https://github.com/ava-labs/coreth/issues/314. + gasPrice.Add(gasPrice, gasPrice) + return gasPrice +} + +// Helper simplifying use via an option of a gas price appropriate for testing. +func WithSuggestedGasPrice(ethClient ethclient.Client) common.Option { + baseFee := SuggestGasPrice(ethClient) + return common.WithBaseFee(baseFee) +} diff --git a/tests/e2e/x/interchain_workflow.go b/tests/e2e/x/interchain_workflow.go index 182c451734bd..9b941a6cbf20 100644 --- a/tests/e2e/x/interchain_workflow.go +++ b/tests/e2e/x/interchain_workflow.go @@ -105,18 +105,21 @@ var _ = e2e.DescribeXChain("[Interchain Workflow]", func() { ) }) + ginkgo.By("initializing a new eth client") + ethClient := e2e.Env.NewEthClient(nodeURI) + ginkgo.By("importing AVAX from the X-Chain to the C-Chain", func() { e2e.LogTxAndCheck( cWallet.IssueImportTx( xWallet.BlockchainID(), recipientEthAddress, e2e.WithDefaultContext(), + e2e.WithSuggestedGasPrice(ethClient), ), ) }) ginkgo.By("checking that the recipient address has received imported funds on the C-Chain") - ethClient := e2e.Env.NewEthClient(nodeURI) e2e.Eventually(func() bool { balance, err := ethClient.BalanceAt(e2e.DefaultContext(), recipientEthAddress, nil) require.NoError(err) From 9739f74704abfe3c8c7faeb5e9ac9481750643dc Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Fri, 1 Sep 2023 09:23:36 -0700 Subject: [PATCH 03/11] fixup: Remove unnecessary logging on keychain creation --- tests/e2e/c/interchain_workflow.go | 4 ++-- tests/e2e/e2e.go | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/e2e/c/interchain_workflow.go b/tests/e2e/c/interchain_workflow.go index 3b8f17890342..025acb6f2682 100644 --- a/tests/e2e/c/interchain_workflow.go +++ b/tests/e2e/c/interchain_workflow.go @@ -32,10 +32,12 @@ var _ = e2e.DescribeCChain("[Interchain Workflow]", func() { ) ginkgo.It("should ensure that funds can be transferred from the C-Chain to the X-Chain and the P-Chain", func() { + ginkgo.By("initializing a new eth client") // Select a random node URI to use for both the eth client and // the wallet to avoid having to verify that all nodes are at // the same height before initializing the wallet. nodeURI := e2e.Env.GetRandomNodeURI() + ethClient := e2e.Env.NewEthClient(nodeURI) ginkgo.By("allocating a pre-funded key to send from and a recipient key to deliver to") senderKey := e2e.Env.AllocateFundedKey() @@ -45,8 +47,6 @@ var _ = e2e.DescribeCChain("[Interchain Workflow]", func() { require.NoError(err) recipientEthAddress := evm.GetEthAddress(recipientKey) - ethClient := e2e.Env.NewEthClient(nodeURI) - ginkgo.By("sending funds from one address to another on the C-Chain", func() { // Create transaction acceptedNonce, err := ethClient.AcceptedNonceAt(e2e.DefaultContext(), senderEthAddress) diff --git a/tests/e2e/e2e.go b/tests/e2e/e2e.go index 9771875a3013..888c23a93d6a 100644 --- a/tests/e2e/e2e.go +++ b/tests/e2e/e2e.go @@ -115,7 +115,6 @@ func (te *TestEnvironment) AllocateFundedKey() *secp256k1.PrivateKey { // Create a new keychain with the specified number of test keys. func (te *TestEnvironment) NewKeychain(count int) *secp256k1fx.Keychain { keys := te.AllocateFundedKeys(count) - tests.Outf("{{blue}} initializing keychain with %d key(s) {{/}}\n", count) return secp256k1fx.NewKeychain(keys...) } From 35b1f75c6cf87ae11a8a99a5e975e2708ca4c65c Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Fri, 1 Sep 2023 10:53:17 -0700 Subject: [PATCH 04/11] fixup: Remove use of explicit transaction logging --- tests/e2e/c/interchain_workflow.go | 48 +++++----- tests/e2e/e2e.go | 21 ----- tests/e2e/p/permissionless_subnets.go | 128 ++++++++++++-------------- tests/e2e/x/interchain_workflow.go | 71 +++++++------- 4 files changed, 116 insertions(+), 152 deletions(-) diff --git a/tests/e2e/c/interchain_workflow.go b/tests/e2e/c/interchain_workflow.go index 025acb6f2682..ac6ecd90b0d0 100644 --- a/tests/e2e/c/interchain_workflow.go +++ b/tests/e2e/c/interchain_workflow.go @@ -111,24 +111,22 @@ var _ = e2e.DescribeCChain("[Interchain Workflow]", func() { } ginkgo.By("exporting AVAX from the C-Chain to the X-Chain", func() { - e2e.LogTxAndCheck( - cWallet.IssueExportTx( - xWallet.BlockchainID(), - exportOutputs, - e2e.WithDefaultContext(), - e2e.WithSuggestedGasPrice(ethClient), - ), + _, err := cWallet.IssueExportTx( + xWallet.BlockchainID(), + exportOutputs, + e2e.WithDefaultContext(), + e2e.WithSuggestedGasPrice(ethClient), ) + require.NoError(err) }) ginkgo.By("importing AVAX from the C-Chain to the X-Chain", func() { - e2e.LogTxAndCheck( - xWallet.IssueImportTx( - cWallet.BlockchainID(), - &recipientOwner, - e2e.WithDefaultContext(), - ), + _, err := xWallet.IssueImportTx( + cWallet.BlockchainID(), + &recipientOwner, + e2e.WithDefaultContext(), ) + require.NoError(err) }) ginkgo.By("checking that the recipient address has received imported funds on the X-Chain", func() { @@ -140,24 +138,22 @@ var _ = e2e.DescribeCChain("[Interchain Workflow]", func() { }) ginkgo.By("exporting AVAX from the C-Chain to the P-Chain", func() { - e2e.LogTxAndCheck( - cWallet.IssueExportTx( - constants.PlatformChainID, - exportOutputs, - e2e.WithDefaultContext(), - e2e.WithSuggestedGasPrice(ethClient), - ), + _, err := cWallet.IssueExportTx( + constants.PlatformChainID, + exportOutputs, + e2e.WithDefaultContext(), + e2e.WithSuggestedGasPrice(ethClient), ) + require.NoError(err) }) ginkgo.By("importing AVAX from the C-Chain to the P-Chain", func() { - e2e.LogTxAndCheck( - pWallet.IssueImportTx( - cWallet.BlockchainID(), - &recipientOwner, - e2e.WithDefaultContext(), - ), + _, err = pWallet.IssueImportTx( + cWallet.BlockchainID(), + &recipientOwner, + e2e.WithDefaultContext(), ) + require.NoError(err) }) ginkgo.By("checking that the recipient address has received imported funds on the P-Chain", func() { diff --git a/tests/e2e/e2e.go b/tests/e2e/e2e.go index 888c23a93d6a..2447b30a433e 100644 --- a/tests/e2e/e2e.go +++ b/tests/e2e/e2e.go @@ -201,27 +201,6 @@ func WaitForHealthy(node testnet.Node) { require.NoError(ginkgo.GinkgoT(), testnet.WaitForHealthy(ctx, node)) } -// Interface to allow logging any type of transaction that has an ID method. -type LoggableTx interface { - ID() ids.ID -} - -// Ensures transaction id is logged if the transaction is non-nil. Should be -// called before checking for an error to ensure traceability in the event -// of a transaction being submitted but failing to be accepted. -func LogTx(tx LoggableTx) { - if tx != nil { - tests.Outf(" tx id: %s\n", tx.ID()) - } -} - -// Ensures transaction id is logged if the transaction is non-nil and check -// if an error occurred. -func LogTxAndCheck(tx LoggableTx, err error) { - LogTx(tx) - require.NoError(ginkgo.GinkgoT(), err) -} - // Sends an eth transaction, waits for the transaction receipt to be issued // and checks that the receipt indicates success. func SendEthTransaction(ethClient ethclient.Client, signedTx *types.Transaction) *types.Receipt { diff --git a/tests/e2e/p/permissionless_subnets.go b/tests/e2e/p/permissionless_subnets.go index 09c9b7c6fea6..f732e7e16b79 100644 --- a/tests/e2e/p/permissionless_subnets.go +++ b/tests/e2e/p/permissionless_subnets.go @@ -70,7 +70,7 @@ var _ = e2e.DescribePChain("[Permissionless Subnets]", func() { common.WithContext(ctx), ) cancel() - e2e.LogTx(subnetTx) + subnetID = subnetTx.ID() gomega.Expect(subnetID, err).Should(gomega.Not(gomega.Equal(constants.PrimaryNetworkID))) }) @@ -93,114 +93,108 @@ var _ = e2e.DescribePChain("[Permissionless Subnets]", func() { common.WithContext(ctx), ) cancel() - e2e.LogTx(subnetAssetTx) gomega.Expect(err).Should(gomega.BeNil()) subnetAssetID = subnetAssetTx.ID() }) ginkgo.By(fmt.Sprintf("Send 100 MegaAvax of asset %s to the P-chain", subnetAssetID), func() { ctx, cancel := context.WithTimeout(context.Background(), e2e.DefaultTimeout) - e2e.LogTxAndCheck( - xWallet.IssueExportTx( - constants.PlatformChainID, - []*avax.TransferableOutput{ - { - Asset: avax.Asset{ - ID: subnetAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: 100 * units.MegaAvax, - OutputOwners: *owner, - }, + _, err := xWallet.IssueExportTx( + constants.PlatformChainID, + []*avax.TransferableOutput{ + { + Asset: avax.Asset{ + ID: subnetAssetID, + }, + Out: &secp256k1fx.TransferOutput{ + Amt: 100 * units.MegaAvax, + OutputOwners: *owner, }, }, - common.WithContext(ctx), - ), + }, + common.WithContext(ctx), ) cancel() + gomega.Expect(err).Should(gomega.BeNil()) }) ginkgo.By(fmt.Sprintf("Import the 100 MegaAvax of asset %s from the X-chain into the P-chain", subnetAssetID), func() { ctx, cancel := context.WithTimeout(context.Background(), e2e.DefaultTimeout) - tx, err := pWallet.IssueImportTx( + _, err := pWallet.IssueImportTx( xChainID, owner, common.WithContext(ctx), ) cancel() - e2e.LogTx(tx) gomega.Expect(err).Should(gomega.BeNil()) }) ginkgo.By("make subnet permissionless", func() { ctx, cancel := context.WithTimeout(context.Background(), e2e.DefaultTimeout) - e2e.LogTxAndCheck( - pWallet.IssueTransformSubnetTx( - subnetID, - subnetAssetID, - 50*units.MegaAvax, - 100*units.MegaAvax, - reward.PercentDenominator, - reward.PercentDenominator, - 1, - 100*units.MegaAvax, - time.Second, - 365*24*time.Hour, - 0, - 1, - 5, - .80*reward.PercentDenominator, - common.WithContext(ctx), - ), + _, err := pWallet.IssueTransformSubnetTx( + subnetID, + subnetAssetID, + 50*units.MegaAvax, + 100*units.MegaAvax, + reward.PercentDenominator, + reward.PercentDenominator, + 1, + 100*units.MegaAvax, + time.Second, + 365*24*time.Hour, + 0, + 1, + 5, + .80*reward.PercentDenominator, + common.WithContext(ctx), ) cancel() + gomega.Expect(err).Should(gomega.BeNil()) }) validatorStartTime := time.Now().Add(time.Minute) ginkgo.By("add permissionless validator", func() { ctx, cancel := context.WithTimeout(context.Background(), e2e.DefaultTimeout) - e2e.LogTxAndCheck( - pWallet.IssueAddPermissionlessValidatorTx( - &txs.SubnetValidator{ - Validator: txs.Validator{ - NodeID: validatorID, - Start: uint64(validatorStartTime.Unix()), - End: uint64(validatorStartTime.Add(5 * time.Second).Unix()), - Wght: 25 * units.MegaAvax, - }, - Subnet: subnetID, + _, err := pWallet.IssueAddPermissionlessValidatorTx( + &txs.SubnetValidator{ + Validator: txs.Validator{ + NodeID: validatorID, + Start: uint64(validatorStartTime.Unix()), + End: uint64(validatorStartTime.Add(5 * time.Second).Unix()), + Wght: 25 * units.MegaAvax, }, - &signer.Empty{}, - subnetAssetID, - &secp256k1fx.OutputOwners{}, - &secp256k1fx.OutputOwners{}, - reward.PercentDenominator, - common.WithContext(ctx), - ), + Subnet: subnetID, + }, + &signer.Empty{}, + subnetAssetID, + &secp256k1fx.OutputOwners{}, + &secp256k1fx.OutputOwners{}, + reward.PercentDenominator, + common.WithContext(ctx), ) cancel() + gomega.Expect(err).Should(gomega.BeNil()) }) delegatorStartTime := validatorStartTime ginkgo.By("add permissionless delegator", func() { ctx, cancel := context.WithTimeout(context.Background(), e2e.DefaultTimeout) - e2e.LogTxAndCheck( - pWallet.IssueAddPermissionlessDelegatorTx( - &txs.SubnetValidator{ - Validator: txs.Validator{ - NodeID: validatorID, - Start: uint64(delegatorStartTime.Unix()), - End: uint64(delegatorStartTime.Add(5 * time.Second).Unix()), - Wght: 25 * units.MegaAvax, - }, - Subnet: subnetID, + _, err := pWallet.IssueAddPermissionlessDelegatorTx( + &txs.SubnetValidator{ + Validator: txs.Validator{ + NodeID: validatorID, + Start: uint64(delegatorStartTime.Unix()), + End: uint64(delegatorStartTime.Add(5 * time.Second).Unix()), + Wght: 25 * units.MegaAvax, }, - subnetAssetID, - &secp256k1fx.OutputOwners{}, - common.WithContext(ctx), - ), + Subnet: subnetID, + }, + subnetAssetID, + &secp256k1fx.OutputOwners{}, + common.WithContext(ctx), ) cancel() + gomega.Expect(err).Should(gomega.BeNil()) }) }) }) diff --git a/tests/e2e/x/interchain_workflow.go b/tests/e2e/x/interchain_workflow.go index 9b941a6cbf20..4b1c4199f718 100644 --- a/tests/e2e/x/interchain_workflow.go +++ b/tests/e2e/x/interchain_workflow.go @@ -71,20 +71,19 @@ var _ = e2e.DescribeXChain("[Interchain Workflow]", func() { } ginkgo.By("sending funds from one address to another on the X-Chain", func() { - e2e.LogTxAndCheck( - xWallet.IssueBaseTx( - []*avax.TransferableOutput{{ - Asset: avax.Asset{ - ID: avaxAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: transferAmount, - OutputOwners: recipientOwner, - }, - }}, - e2e.WithDefaultContext(), - ), + _, err = xWallet.IssueBaseTx( + []*avax.TransferableOutput{{ + Asset: avax.Asset{ + ID: avaxAssetID, + }, + Out: &secp256k1fx.TransferOutput{ + Amt: transferAmount, + OutputOwners: recipientOwner, + }, + }}, + e2e.WithDefaultContext(), ) + require.NoError(err) }) ginkgo.By("checking that the X-Chain recipient address has received the sent funds", func() { @@ -96,27 +95,25 @@ var _ = e2e.DescribeXChain("[Interchain Workflow]", func() { }) ginkgo.By("exporting AVAX from the X-Chain to the C-Chain", func() { - e2e.LogTxAndCheck( - xWallet.IssueExportTx( - cWallet.BlockchainID(), - exportOutputs, - e2e.WithDefaultContext(), - ), + _, err := xWallet.IssueExportTx( + cWallet.BlockchainID(), + exportOutputs, + e2e.WithDefaultContext(), ) + require.NoError(err) }) ginkgo.By("initializing a new eth client") ethClient := e2e.Env.NewEthClient(nodeURI) ginkgo.By("importing AVAX from the X-Chain to the C-Chain", func() { - e2e.LogTxAndCheck( - cWallet.IssueImportTx( - xWallet.BlockchainID(), - recipientEthAddress, - e2e.WithDefaultContext(), - e2e.WithSuggestedGasPrice(ethClient), - ), + _, err := cWallet.IssueImportTx( + xWallet.BlockchainID(), + recipientEthAddress, + e2e.WithDefaultContext(), + e2e.WithSuggestedGasPrice(ethClient), ) + require.NoError(err) }) ginkgo.By("checking that the recipient address has received imported funds on the C-Chain") @@ -127,23 +124,21 @@ var _ = e2e.DescribeXChain("[Interchain Workflow]", func() { }, e2e.DefaultTimeout, e2e.DefaultPollingInterval, "failed to see recipient address funded before timeout") ginkgo.By("exporting AVAX from the X-Chain to the P-Chain", func() { - e2e.LogTxAndCheck( - xWallet.IssueExportTx( - constants.PlatformChainID, - exportOutputs, - e2e.WithDefaultContext(), - ), + _, err := xWallet.IssueExportTx( + constants.PlatformChainID, + exportOutputs, + e2e.WithDefaultContext(), ) + require.NoError(err) }) ginkgo.By("importing AVAX from the X-Chain to the P-Chain", func() { - e2e.LogTxAndCheck( - pWallet.IssueImportTx( - xWallet.BlockchainID(), - &recipientOwner, - e2e.WithDefaultContext(), - ), + _, err := pWallet.IssueImportTx( + xWallet.BlockchainID(), + &recipientOwner, + e2e.WithDefaultContext(), ) + require.NoError(err) }) ginkgo.By("checking that the recipient address has received imported funds on the P-Chain", func() { From f231bbad9276df2a52aa812c00e48da2ea2c2fc1 Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Fri, 1 Sep 2023 11:16:04 -0700 Subject: [PATCH 05/11] fixup: Add optional transaction logging to the wallet --- tests/e2e/e2e.go | 11 +++++++++-- wallet/chain/c/wallet.go | 4 ++++ wallet/chain/p/wallet.go | 4 ++++ wallet/chain/x/wallet.go | 4 ++++ wallet/subnet/primary/common/options.go | 14 ++++++++++++++ 5 files changed, 35 insertions(+), 2 deletions(-) diff --git a/tests/e2e/e2e.go b/tests/e2e/e2e.go index 2447b30a433e..6e83d67cdb35 100644 --- a/tests/e2e/e2e.go +++ b/tests/e2e/e2e.go @@ -121,13 +121,20 @@ func (te *TestEnvironment) NewKeychain(count int) *secp256k1fx.Keychain { // Create a new wallet for the provided keychain against the specified node URI. func (te *TestEnvironment) NewWallet(keychain *secp256k1fx.Keychain, uri string) primary.Wallet { tests.Outf("{{blue}} initializing a new wallet for URI: %s {{/}}\n", uri) - wallet, err := primary.MakeWallet(DefaultContext(), &primary.WalletConfig{ + baseWallet, err := primary.MakeWallet(DefaultContext(), &primary.WalletConfig{ URI: uri, AVAXKeychain: keychain, EthKeychain: keychain, }) te.require.NoError(err) - return wallet + return primary.NewWalletWithOptions( + baseWallet, + common.WithLogTxFunc( + func(id ids.ID) { + tests.Outf(" tx id: %s\n", id) + }, + ), + ) } // Create a new eth client targeting the specified node URI. diff --git a/wallet/chain/c/wallet.go b/wallet/chain/c/wallet.go index b8e74dc24047..93608c27a001 100644 --- a/wallet/chain/c/wallet.go +++ b/wallet/chain/c/wallet.go @@ -159,6 +159,10 @@ func (w *wallet) IssueAtomicTx( return err } + if logFunc := ops.LogTxIDFunc(); logFunc != nil { + logFunc(txID) + } + if ops.AssumeDecided() { return w.Backend.AcceptAtomicTx(ctx, tx) } diff --git a/wallet/chain/p/wallet.go b/wallet/chain/p/wallet.go index ec180cc90963..dbec1f04fc52 100644 --- a/wallet/chain/p/wallet.go +++ b/wallet/chain/p/wallet.go @@ -489,6 +489,10 @@ func (w *wallet) IssueTx( return err } + if logFunc := ops.LogTxIDFunc(); logFunc != nil { + logFunc(txID) + } + if ops.AssumeDecided() { return w.Backend.AcceptTx(ctx, tx) } diff --git a/wallet/chain/x/wallet.go b/wallet/chain/x/wallet.go index f44bdad8b31b..410055fd3ebe 100644 --- a/wallet/chain/x/wallet.go +++ b/wallet/chain/x/wallet.go @@ -305,6 +305,10 @@ func (w *wallet) IssueTx( return err } + if logFunc := ops.LogTxIDFunc(); logFunc != nil { + logFunc(txID) + } + if ops.AssumeDecided() { return w.Backend.AcceptTx(ctx, tx) } diff --git a/wallet/subnet/primary/common/options.go b/wallet/subnet/primary/common/options.go index 0776ad7ebbf1..58cba343ea38 100644 --- a/wallet/subnet/primary/common/options.go +++ b/wallet/subnet/primary/common/options.go @@ -17,6 +17,8 @@ import ( const defaultPollFrequency = 100 * time.Millisecond +type LogTxIDFunc func(ids.ID) + type Option func(*Options) type Options struct { @@ -43,6 +45,8 @@ type Options struct { pollFrequencySet bool pollFrequency time.Duration + + logTxIDFunc LogTxIDFunc } func NewOptions(ops []Option) *Options { @@ -126,6 +130,10 @@ func (o *Options) PollFrequency() time.Duration { return defaultPollFrequency } +func (o *Options) LogTxIDFunc() LogTxIDFunc { + return o.logTxIDFunc +} + func WithContext(ctx context.Context) Option { return func(o *Options) { o.ctx = ctx @@ -189,3 +197,9 @@ func WithPollFrequency(pollFrequency time.Duration) Option { o.pollFrequency = pollFrequency } } + +func WithLogTxFunc(logFunc LogTxIDFunc) Option { + return func(o *Options) { + o.logTxIDFunc = logFunc + } +} From 3a17974546bd8780a9d1316d23980433fa6e7cf9 Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Tue, 5 Sep 2023 11:52:12 -0700 Subject: [PATCH 06/11] fixup: Switch to returning struct with node ID and URI --- tests/e2e/e2e.go | 26 +++++++++++--------------- tests/e2e/p/permissionless_subnets.go | 2 +- tests/e2e/p/workflow.go | 4 ++-- tests/e2e/static-handlers/suites.go | 4 ++-- tests/e2e/x/transfer/virtuous.go | 7 ++++--- tests/fixture/testnet/config.go | 6 ++++++ tests/fixture/testnet/local/network.go | 13 ++++++++----- 7 files changed, 34 insertions(+), 28 deletions(-) diff --git a/tests/e2e/e2e.go b/tests/e2e/e2e.go index 6e83d67cdb35..30398526559a 100644 --- a/tests/e2e/e2e.go +++ b/tests/e2e/e2e.go @@ -22,8 +22,6 @@ import ( "github.com/ava-labs/coreth/ethclient" "github.com/ava-labs/coreth/interfaces" - "golang.org/x/exp/maps" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/tests" "github.com/ava-labs/avalanchego/tests/fixture" @@ -65,7 +63,7 @@ type TestEnvironment struct { // The directory where the test network configuration is stored NetworkDir string // URIs used to access the API endpoints of nodes of the network - URIs map[ids.NodeID]string + URIs []testnet.NodeURI // The URI used to access the http server that allocates test data TestDataServerURI string @@ -83,13 +81,11 @@ func InitTestEnvironment(envBytes []byte) { // Retrieve a random URI to naively attempt to spread API load across // nodes. -func (te *TestEnvironment) GetRandomNodeURI() string { +func (te *TestEnvironment) GetRandomNodeURI() testnet.NodeURI { r := rand.New(rand.NewSource(time.Now().Unix())) //#nosec G404 - nodeIDs := maps.Keys(te.URIs) - nodeID := nodeIDs[r.Intn(len(nodeIDs))] - uri := te.URIs[nodeID] - tests.Outf("{{blue}} targeting node %s with URI: %s{{/}}\n", nodeID, uri) - return uri + nodeURI := te.URIs[r.Intn(len(te.URIs))] + tests.Outf("{{blue}} targeting node %s with URI: %s{{/}}\n", nodeURI.NodeID, nodeURI.URI) + return nodeURI } // Retrieve the network to target for testing. @@ -119,10 +115,10 @@ func (te *TestEnvironment) NewKeychain(count int) *secp256k1fx.Keychain { } // Create a new wallet for the provided keychain against the specified node URI. -func (te *TestEnvironment) NewWallet(keychain *secp256k1fx.Keychain, uri string) primary.Wallet { - tests.Outf("{{blue}} initializing a new wallet for URI: %s {{/}}\n", uri) +func (te *TestEnvironment) NewWallet(keychain *secp256k1fx.Keychain, nodeURI testnet.NodeURI) primary.Wallet { + tests.Outf("{{blue}} initializing a new wallet for node %s with URI: %s {{/}}\n", nodeURI.NodeID, nodeURI.URI) baseWallet, err := primary.MakeWallet(DefaultContext(), &primary.WalletConfig{ - URI: uri, + URI: nodeURI.URI, AVAXKeychain: keychain, EthKeychain: keychain, }) @@ -138,9 +134,9 @@ func (te *TestEnvironment) NewWallet(keychain *secp256k1fx.Keychain, uri string) } // Create a new eth client targeting the specified node URI. -func (te *TestEnvironment) NewEthClient(nodeURI string) ethclient.Client { - tests.Outf("{{blue}} initializing a new eth client for URI: %s {{/}}\n", nodeURI) - nodeAddress := strings.Split(nodeURI, "//")[1] +func (te *TestEnvironment) NewEthClient(nodeURI testnet.NodeURI) ethclient.Client { + tests.Outf("{{blue}} initializing a new eth client for node %s with URI: %s {{/}}\n", nodeURI.NodeID, nodeURI.URI) + nodeAddress := strings.Split(nodeURI.URI, "//")[1] uri := fmt.Sprintf("ws://%s/ext/bc/C/ws", nodeAddress) client, err := ethclient.Dial(uri) te.require.NoError(err) diff --git a/tests/e2e/p/permissionless_subnets.go b/tests/e2e/p/permissionless_subnets.go index f732e7e16b79..6eb71ec53138 100644 --- a/tests/e2e/p/permissionless_subnets.go +++ b/tests/e2e/p/permissionless_subnets.go @@ -46,7 +46,7 @@ var _ = e2e.DescribePChain("[Permissionless Subnets]", func() { var validatorID ids.NodeID ginkgo.By("retrieving the node ID of a primary network validator", func() { - pChainClient := platformvm.NewClient(nodeURI) + pChainClient := platformvm.NewClient(nodeURI.URI) ctx, cancel := context.WithTimeout(context.Background(), e2e.DefaultTimeout) validatorIDs, err := pChainClient.SampleValidators(ctx, constants.PrimaryNetworkID, 1) cancel() diff --git a/tests/e2e/p/workflow.go b/tests/e2e/p/workflow.go index 31908ece07a0..2b67cda351bc 100644 --- a/tests/e2e/p/workflow.go +++ b/tests/e2e/p/workflow.go @@ -49,7 +49,7 @@ var _ = e2e.DescribePChain("[Workflow]", func() { pWallet := baseWallet.P() avaxAssetID := baseWallet.P().AVAXAssetID() xWallet := baseWallet.X() - pChainClient := platformvm.NewClient(nodeURI) + pChainClient := platformvm.NewClient(nodeURI.URI) tests.Outf("{{blue}} fetching minimal stake amounts {{/}}\n") ctx, cancel := context.WithTimeout(context.Background(), e2e.DefaultWalletCreationTimeout) @@ -60,7 +60,7 @@ var _ = e2e.DescribePChain("[Workflow]", func() { tests.Outf("{{green}} minimal delegator stake: %d {{/}}\n", minDelStake) tests.Outf("{{blue}} fetching tx fee {{/}}\n") - infoClient := info.NewClient(nodeURI) + infoClient := info.NewClient(nodeURI.URI) ctx, cancel = context.WithTimeout(context.Background(), e2e.DefaultWalletCreationTimeout) fees, err := infoClient.GetTxFee(ctx) cancel() diff --git a/tests/e2e/static-handlers/suites.go b/tests/e2e/static-handlers/suites.go index 2d7e75c02531..a87159f833d1 100644 --- a/tests/e2e/static-handlers/suites.go +++ b/tests/e2e/static-handlers/suites.go @@ -110,7 +110,7 @@ var _ = ginkgo.Describe("[StaticHandlers]", func() { }, }, } - staticClient := avm.NewStaticClient(e2e.Env.GetRandomNodeURI()) + staticClient := avm.NewStaticClient(e2e.Env.GetRandomNodeURI().URI) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) resp, err := staticClient.BuildGenesis(ctx, &avmArgs) cancel() @@ -181,7 +181,7 @@ var _ = ginkgo.Describe("[StaticHandlers]", func() { Encoding: formatting.Hex, } - staticClient := api.NewStaticClient(e2e.Env.GetRandomNodeURI()) + staticClient := api.NewStaticClient(e2e.Env.GetRandomNodeURI().URI) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) resp, err := staticClient.BuildGenesis(ctx, &buildGenesisArgs) cancel() diff --git a/tests/e2e/x/transfer/virtuous.go b/tests/e2e/x/transfer/virtuous.go index b838084dba19..6812b0a04a97 100644 --- a/tests/e2e/x/transfer/virtuous.go +++ b/tests/e2e/x/transfer/virtuous.go @@ -14,8 +14,6 @@ import ( ginkgo "github.com/onsi/ginkgo/v2" - "golang.org/x/exp/maps" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/choices" "github.com/ava-labs/avalanchego/tests" @@ -46,7 +44,10 @@ var _ = e2e.DescribeXChainSerial("[Virtuous Transfer Tx AVAX]", func() { "virtuous-transfer-tx-avax", ), func() { - rpcEps := maps.Values(e2e.Env.URIs) + rpcEps := make([]string, len(e2e.Env.URIs)) + for i, nodeURI := range e2e.Env.URIs { + rpcEps[i] = nodeURI.URI + } // Waiting for ongoing blocks to have completed before starting this // test avoids the case of a previous test having initiated block diff --git a/tests/fixture/testnet/config.go b/tests/fixture/testnet/config.go index 5b64bba99893..74c4e09692f6 100644 --- a/tests/fixture/testnet/config.go +++ b/tests/fixture/testnet/config.go @@ -157,6 +157,12 @@ func (c *NetworkConfig) EnsureGenesis(networkID uint32, validatorIDs []ids.NodeI return nil } +// NodeURI associates a node ID with its API URI. +type NodeURI struct { + NodeID ids.NodeID + URI string +} + // NodeConfig defines configuration for an AvalancheGo node. type NodeConfig struct { NodeID ids.NodeID diff --git a/tests/fixture/testnet/local/network.go b/tests/fixture/testnet/local/network.go index 01a5ca2634ce..1c5d7874576d 100644 --- a/tests/fixture/testnet/local/network.go +++ b/tests/fixture/testnet/local/network.go @@ -407,16 +407,19 @@ func (ln *LocalNetwork) WaitForHealthy(ctx context.Context, w io.Writer) error { return nil } -// Retrieve API URIs for all nodes in the network. Assumes nodes have -// been loaded. -func (ln *LocalNetwork) GetURIs() map[ids.NodeID]string { - uris := make(map[ids.NodeID]string, len(ln.Nodes)) +// Retrieve API URIs for all running primary validator nodes. URIs for +// ephemeral nodes are not returned. +func (ln *LocalNetwork) GetURIs() []testnet.NodeURI { + uris := make([]testnet.NodeURI, 0, len(ln.Nodes)) for _, node := range ln.Nodes { // Only append URIs that are not empty. A node may have an // empty URI if it was not running at the time // node.ReadProcessContext() was called. if len(node.URI) > 0 { - uris[node.NodeID] = node.URI + uris = append(uris, testnet.NodeURI{ + NodeID: node.NodeID, + URI: node.URI, + }) } } return uris From 80410bfc4932a6f48fbf8653c920d2789a427a49 Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Tue, 5 Sep 2023 12:44:33 -0700 Subject: [PATCH 07/11] fixup: Ensure test output is output to ginkgo writer instead of stdout This ensures that log output is produced inline ginkgo step logging when tests are executed in parallel. --- tests/colors.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/colors.go b/tests/colors.go index 4c50eb3a5bd0..8b69fc36cb95 100644 --- a/tests/colors.go +++ b/tests/colors.go @@ -4,7 +4,7 @@ package tests import ( - "fmt" + ginkgo "github.com/onsi/ginkgo/v2" "github.com/onsi/ginkgo/v2/formatter" ) @@ -20,5 +20,5 @@ import ( // for an exhaustive list of color options. func Outf(format string, args ...interface{}) { s := formatter.F(format, args...) - fmt.Fprint(formatter.ColorableStdOut, s) + ginkgo.GinkgoWriter.Print(s) } From cfa503e43ee4887d581006af6298547006c7efa0 Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Tue, 5 Sep 2023 13:07:55 -0700 Subject: [PATCH 08/11] fixup: Rename LogTxIDFunc to PostIssuanceFunc --- tests/e2e/e2e.go | 2 +- wallet/chain/c/wallet.go | 4 ++-- wallet/chain/p/wallet.go | 4 ++-- wallet/chain/x/wallet.go | 4 ++-- wallet/subnet/primary/common/options.go | 14 ++++++++------ 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/tests/e2e/e2e.go b/tests/e2e/e2e.go index 30398526559a..be7d8334fbe0 100644 --- a/tests/e2e/e2e.go +++ b/tests/e2e/e2e.go @@ -125,7 +125,7 @@ func (te *TestEnvironment) NewWallet(keychain *secp256k1fx.Keychain, nodeURI tes te.require.NoError(err) return primary.NewWalletWithOptions( baseWallet, - common.WithLogTxFunc( + common.WithPostIssuanceFunc( func(id ids.ID) { tests.Outf(" tx id: %s\n", id) }, diff --git a/wallet/chain/c/wallet.go b/wallet/chain/c/wallet.go index 93608c27a001..fb1a83d53dad 100644 --- a/wallet/chain/c/wallet.go +++ b/wallet/chain/c/wallet.go @@ -159,8 +159,8 @@ func (w *wallet) IssueAtomicTx( return err } - if logFunc := ops.LogTxIDFunc(); logFunc != nil { - logFunc(txID) + if f := ops.PostIssuanceFunc(); f != nil { + f(txID) } if ops.AssumeDecided() { diff --git a/wallet/chain/p/wallet.go b/wallet/chain/p/wallet.go index dbec1f04fc52..8bdcb52bf2e3 100644 --- a/wallet/chain/p/wallet.go +++ b/wallet/chain/p/wallet.go @@ -489,8 +489,8 @@ func (w *wallet) IssueTx( return err } - if logFunc := ops.LogTxIDFunc(); logFunc != nil { - logFunc(txID) + if f := ops.PostIssuanceFunc(); f != nil { + f(txID) } if ops.AssumeDecided() { diff --git a/wallet/chain/x/wallet.go b/wallet/chain/x/wallet.go index 410055fd3ebe..4e187d58220f 100644 --- a/wallet/chain/x/wallet.go +++ b/wallet/chain/x/wallet.go @@ -305,8 +305,8 @@ func (w *wallet) IssueTx( return err } - if logFunc := ops.LogTxIDFunc(); logFunc != nil { - logFunc(txID) + if f := ops.PostIssuanceFunc(); f != nil { + f(txID) } if ops.AssumeDecided() { diff --git a/wallet/subnet/primary/common/options.go b/wallet/subnet/primary/common/options.go index 58cba343ea38..0c15be3c8b6b 100644 --- a/wallet/subnet/primary/common/options.go +++ b/wallet/subnet/primary/common/options.go @@ -17,7 +17,9 @@ import ( const defaultPollFrequency = 100 * time.Millisecond -type LogTxIDFunc func(ids.ID) +// Signature of the function that will be called after a transaction +// has been issued with the ID of the issued transaction. +type PostIssuanceFunc func(ids.ID) type Option func(*Options) @@ -46,7 +48,7 @@ type Options struct { pollFrequencySet bool pollFrequency time.Duration - logTxIDFunc LogTxIDFunc + postIssuanceFunc PostIssuanceFunc } func NewOptions(ops []Option) *Options { @@ -130,8 +132,8 @@ func (o *Options) PollFrequency() time.Duration { return defaultPollFrequency } -func (o *Options) LogTxIDFunc() LogTxIDFunc { - return o.logTxIDFunc +func (o *Options) PostIssuanceFunc() PostIssuanceFunc { + return o.postIssuanceFunc } func WithContext(ctx context.Context) Option { @@ -198,8 +200,8 @@ func WithPollFrequency(pollFrequency time.Duration) Option { } } -func WithLogTxFunc(logFunc LogTxIDFunc) Option { +func WithPostIssuanceFunc(f PostIssuanceFunc) Option { return func(o *Options) { - o.logTxIDFunc = logFunc + o.postIssuanceFunc = f } } From 47fd30bd9156ea1fb597727505eca73764f98a6e Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Tue, 5 Sep 2023 13:11:37 -0700 Subject: [PATCH 09/11] fixup: Improve tx logging messages --- tests/e2e/e2e.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/e2e.go b/tests/e2e/e2e.go index be7d8334fbe0..01cf072a569a 100644 --- a/tests/e2e/e2e.go +++ b/tests/e2e/e2e.go @@ -127,7 +127,7 @@ func (te *TestEnvironment) NewWallet(keychain *secp256k1fx.Keychain, nodeURI tes baseWallet, common.WithPostIssuanceFunc( func(id ids.ID) { - tests.Outf(" tx id: %s\n", id) + tests.Outf(" issued transaction with ID: %s\n", id) }, ), ) @@ -210,7 +210,7 @@ func SendEthTransaction(ethClient ethclient.Client, signedTx *types.Transaction) require := require.New(ginkgo.GinkgoT()) txID := signedTx.Hash() - tests.Outf(" eth tx id: %s\n", txID) + tests.Outf(" sending eth transaction with ID: %s\n", txID) require.NoError(ethClient.SendTransaction(DefaultContext(), signedTx)) From 92c12f43f35a6476f5261416bf3f03c185641a66 Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Tue, 5 Sep 2023 13:20:01 -0700 Subject: [PATCH 10/11] fixup: Add documentation for use of GinkgoWriter in tests.Outf --- tests/colors.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/colors.go b/tests/colors.go index 8b69fc36cb95..3aa935a5fce9 100644 --- a/tests/colors.go +++ b/tests/colors.go @@ -20,5 +20,11 @@ import ( // for an exhaustive list of color options. func Outf(format string, args ...interface{}) { s := formatter.F(format, args...) + // Use GinkgoWriter to ensure that output from this function is + // printed sequentially within other test output produced with + // GinkgoWriter (e.g. `STEP:...`) when tests are run in + // parallel. ginkgo collects and writes stdout separately from + // GinkgoWriter during parallel execution and the resulting output + // can be confusing. ginkgo.GinkgoWriter.Print(s) } From a9fc55ae6065e901463a8b13f0a39384bb520644 Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Wed, 6 Sep 2023 17:18:23 -0700 Subject: [PATCH 11/11] fixup: remove eroneous comment --- tests/e2e/e2e.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/e2e/e2e.go b/tests/e2e/e2e.go index 01cf072a569a..c8fc1c991540 100644 --- a/tests/e2e/e2e.go +++ b/tests/e2e/e2e.go @@ -226,7 +226,6 @@ func SendEthTransaction(ethClient ethclient.Client, signedTx *types.Transaction) return true }, DefaultTimeout, DefaultPollingInterval, "failed to see transaction acceptance before timeout") - // Retrieve the contract address require.Equal(receipt.Status, types.ReceiptStatusSuccessful) return receipt }