github.com/MetalBlockchain/metalgo@v1.11.9/tests/e2e/x/transfer/virtuous.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  // Implements X-chain transfer tests.
     5  package transfer
     6  
     7  import (
     8  	"fmt"
     9  	"math/rand"
    10  	"time"
    11  
    12  	"github.com/prometheus/client_golang/prometheus"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"github.com/MetalBlockchain/metalgo/chains"
    16  	"github.com/MetalBlockchain/metalgo/ids"
    17  	"github.com/MetalBlockchain/metalgo/tests"
    18  	"github.com/MetalBlockchain/metalgo/tests/fixture/e2e"
    19  	"github.com/MetalBlockchain/metalgo/utils/set"
    20  	"github.com/MetalBlockchain/metalgo/vms/avm"
    21  	"github.com/MetalBlockchain/metalgo/vms/components/avax"
    22  	"github.com/MetalBlockchain/metalgo/vms/secp256k1fx"
    23  	"github.com/MetalBlockchain/metalgo/wallet/subnet/primary"
    24  	"github.com/MetalBlockchain/metalgo/wallet/subnet/primary/common"
    25  
    26  	ginkgo "github.com/onsi/ginkgo/v2"
    27  )
    28  
    29  const (
    30  	totalRounds = 50
    31  
    32  	blksProcessingMetric = "metal_snowman_blks_processing"
    33  	blksAcceptedMetric   = "metal_snowman_blks_accepted_count"
    34  )
    35  
    36  var xChainMetricLabels = prometheus.Labels{
    37  	chains.ChainLabel: "X",
    38  }
    39  
    40  // This test requires that the network not have ongoing blocks and
    41  // cannot reliably be run in parallel.
    42  var _ = e2e.DescribeXChainSerial("[Virtuous Transfer Tx AVAX]", func() {
    43  	require := require.New(ginkgo.GinkgoT())
    44  
    45  	ginkgo.It("can issue a virtuous transfer tx for AVAX asset",
    46  		func() {
    47  			rpcEps := make([]string, len(e2e.Env.URIs))
    48  			for i, nodeURI := range e2e.Env.URIs {
    49  				rpcEps[i] = nodeURI.URI
    50  			}
    51  
    52  			// Waiting for ongoing blocks to have completed before starting this
    53  			// test avoids the case of a previous test having initiated block
    54  			// processing but not having completed it.
    55  			e2e.Eventually(func() bool {
    56  				allNodeMetrics, err := tests.GetNodesMetrics(
    57  					e2e.DefaultContext(),
    58  					rpcEps,
    59  				)
    60  				require.NoError(err)
    61  
    62  				for _, metrics := range allNodeMetrics {
    63  					xBlksProcessing, ok := tests.GetMetricValue(metrics, blksProcessingMetric, xChainMetricLabels)
    64  					if !ok || xBlksProcessing > 0 {
    65  						return false
    66  					}
    67  				}
    68  				return true
    69  			},
    70  				e2e.DefaultTimeout,
    71  				e2e.DefaultPollingInterval,
    72  				"The cluster is generating ongoing blocks. Is this test being run in parallel?",
    73  			)
    74  
    75  			// Ensure the same set of 10 keys is used for all tests
    76  			// by retrieving them outside of runFunc.
    77  			testKeys := e2e.Env.AllocatePreFundedKeys(10)
    78  
    79  			runFunc := func(round int) {
    80  				tests.Outf("{{green}}\n\n\n\n\n\n---\n[ROUND #%02d]:{{/}}\n", round)
    81  
    82  				needPermute := round > 3
    83  				if needPermute {
    84  					rand.Seed(time.Now().UnixNano())
    85  					rand.Shuffle(len(testKeys), func(i, j int) {
    86  						testKeys[i], testKeys[j] = testKeys[j], testKeys[i]
    87  					})
    88  				}
    89  
    90  				keychain := secp256k1fx.NewKeychain(testKeys...)
    91  				baseWallet := e2e.NewWallet(keychain, e2e.Env.GetRandomNodeURI())
    92  				xWallet := baseWallet.X()
    93  				xBuilder := xWallet.Builder()
    94  				xContext := xBuilder.Context()
    95  				avaxAssetID := xContext.AVAXAssetID
    96  
    97  				wallets := make([]primary.Wallet, len(testKeys))
    98  				shortAddrs := make([]ids.ShortID, len(testKeys))
    99  				for i := range wallets {
   100  					shortAddrs[i] = testKeys[i].PublicKey().Address()
   101  
   102  					wallets[i] = primary.NewWalletWithOptions(
   103  						baseWallet,
   104  						common.WithCustomAddresses(set.Of(
   105  							testKeys[i].PublicKey().Address(),
   106  						)),
   107  					)
   108  				}
   109  
   110  				metricsBeforeTx, err := tests.GetNodesMetrics(
   111  					e2e.DefaultContext(),
   112  					rpcEps,
   113  				)
   114  				require.NoError(err)
   115  				for _, uri := range rpcEps {
   116  					for _, metric := range []string{blksProcessingMetric, blksAcceptedMetric} {
   117  						tests.Outf("{{green}}%s at %q:{{/}} %v\n", metric, uri, metricsBeforeTx[uri][metric])
   118  					}
   119  				}
   120  
   121  				testBalances := make([]uint64, 0)
   122  				for i, w := range wallets {
   123  					balances, err := w.X().Builder().GetFTBalance()
   124  					require.NoError(err)
   125  
   126  					bal := balances[avaxAssetID]
   127  					testBalances = append(testBalances, bal)
   128  
   129  					fmt.Printf(`CURRENT BALANCE %21d AVAX (SHORT ADDRESS %q)
   130  `,
   131  						bal,
   132  						testKeys[i].PublicKey().Address(),
   133  					)
   134  				}
   135  				fromIdx := -1
   136  				for i := range testBalances {
   137  					if fromIdx < 0 && testBalances[i] > 0 {
   138  						fromIdx = i
   139  						break
   140  					}
   141  				}
   142  				require.GreaterOrEqual(fromIdx, 0, "no address found with non-zero balance")
   143  
   144  				toIdx := -1
   145  				for i := range testBalances {
   146  					// prioritize the address with zero balance
   147  					if toIdx < 0 && i != fromIdx && testBalances[i] == 0 {
   148  						toIdx = i
   149  						break
   150  					}
   151  				}
   152  				if toIdx < 0 {
   153  					// no zero balance address, so just transfer between any two addresses
   154  					toIdx = (fromIdx + 1) % len(testBalances)
   155  				}
   156  
   157  				senderOrigBal := testBalances[fromIdx]
   158  				receiverOrigBal := testBalances[toIdx]
   159  
   160  				amountToTransfer := senderOrigBal / 10
   161  
   162  				senderNewBal := senderOrigBal - amountToTransfer - xContext.BaseTxFee
   163  				receiverNewBal := receiverOrigBal + amountToTransfer
   164  
   165  				ginkgo.By("X-Chain transfer with wrong amount must fail", func() {
   166  					_, err := wallets[fromIdx].X().IssueBaseTx(
   167  						[]*avax.TransferableOutput{{
   168  							Asset: avax.Asset{
   169  								ID: avaxAssetID,
   170  							},
   171  							Out: &secp256k1fx.TransferOutput{
   172  								Amt: senderOrigBal + 1,
   173  								OutputOwners: secp256k1fx.OutputOwners{
   174  									Threshold: 1,
   175  									Addrs:     []ids.ShortID{shortAddrs[toIdx]},
   176  								},
   177  							},
   178  						}},
   179  						e2e.WithDefaultContext(),
   180  					)
   181  					require.Contains(err.Error(), "insufficient funds")
   182  				})
   183  
   184  				fmt.Printf(`===
   185  TRANSFERRING
   186  
   187  FROM [%q]
   188  SENDER    CURRENT BALANCE     : %21d AVAX
   189  SENDER    NEW BALANCE (AFTER) : %21d AVAX
   190  
   191  TRANSFER AMOUNT FROM SENDER   : %21d AVAX
   192  
   193  TO [%q]
   194  RECEIVER  CURRENT BALANCE     : %21d AVAX
   195  RECEIVER  NEW BALANCE (AFTER) : %21d AVAX
   196  ===
   197  `,
   198  					shortAddrs[fromIdx],
   199  					senderOrigBal,
   200  					senderNewBal,
   201  					amountToTransfer,
   202  					shortAddrs[toIdx],
   203  					receiverOrigBal,
   204  					receiverNewBal,
   205  				)
   206  
   207  				tx, err := wallets[fromIdx].X().IssueBaseTx(
   208  					[]*avax.TransferableOutput{{
   209  						Asset: avax.Asset{
   210  							ID: avaxAssetID,
   211  						},
   212  						Out: &secp256k1fx.TransferOutput{
   213  							Amt: amountToTransfer,
   214  							OutputOwners: secp256k1fx.OutputOwners{
   215  								Threshold: 1,
   216  								Addrs:     []ids.ShortID{shortAddrs[toIdx]},
   217  							},
   218  						},
   219  					}},
   220  					e2e.WithDefaultContext(),
   221  				)
   222  				require.NoError(err)
   223  
   224  				balances, err := wallets[fromIdx].X().Builder().GetFTBalance()
   225  				require.NoError(err)
   226  				senderCurBalX := balances[avaxAssetID]
   227  				tests.Outf("{{green}}first wallet balance:{{/}}  %d\n", senderCurBalX)
   228  
   229  				balances, err = wallets[toIdx].X().Builder().GetFTBalance()
   230  				require.NoError(err)
   231  				receiverCurBalX := balances[avaxAssetID]
   232  				tests.Outf("{{green}}second wallet balance:{{/}} %d\n", receiverCurBalX)
   233  
   234  				require.Equal(senderCurBalX, senderNewBal)
   235  				require.Equal(receiverCurBalX, receiverNewBal)
   236  
   237  				txID := tx.ID()
   238  				for _, u := range rpcEps {
   239  					xc := avm.NewClient(u, "X")
   240  					require.NoError(avm.AwaitTxAccepted(xc, e2e.DefaultContext(), txID, 2*time.Second))
   241  				}
   242  
   243  				for _, u := range rpcEps {
   244  					xc := avm.NewClient(u, "X")
   245  					require.NoError(avm.AwaitTxAccepted(xc, e2e.DefaultContext(), txID, 2*time.Second))
   246  
   247  					mm, err := tests.GetNodeMetrics(e2e.DefaultContext(), u)
   248  					require.NoError(err)
   249  
   250  					prev := metricsBeforeTx[u]
   251  
   252  					// +0 since X-chain tx must have been processed and accepted
   253  					// by now
   254  					currentXBlksProcessing, _ := tests.GetMetricValue(mm, blksProcessingMetric, xChainMetricLabels)
   255  					previousXBlksProcessing, _ := tests.GetMetricValue(prev, blksProcessingMetric, xChainMetricLabels)
   256  					require.Equal(currentXBlksProcessing, previousXBlksProcessing)
   257  
   258  					// +1 since X-chain tx must have been accepted by now
   259  					currentXBlksAccepted, _ := tests.GetMetricValue(mm, blksAcceptedMetric, xChainMetricLabels)
   260  					previousXBlksAccepted, _ := tests.GetMetricValue(prev, blksAcceptedMetric, xChainMetricLabels)
   261  					require.Equal(currentXBlksAccepted, previousXBlksAccepted+1)
   262  
   263  					metricsBeforeTx[u] = mm
   264  				}
   265  			}
   266  
   267  			for i := 0; i < totalRounds; i++ {
   268  				runFunc(i)
   269  				time.Sleep(time.Second)
   270  			}
   271  		})
   272  })