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 })