github.com/MetalBlockchain/metalgo@v1.11.9/tests/e2e/p/interchain_workflow.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package p 5 6 import ( 7 "math/big" 8 "time" 9 10 "github.com/MetalBlockchain/coreth/plugin/evm" 11 "github.com/spf13/cast" 12 "github.com/stretchr/testify/require" 13 14 "github.com/MetalBlockchain/metalgo/api/info" 15 "github.com/MetalBlockchain/metalgo/config" 16 "github.com/MetalBlockchain/metalgo/ids" 17 "github.com/MetalBlockchain/metalgo/tests/fixture/e2e" 18 "github.com/MetalBlockchain/metalgo/tests/fixture/tmpnet" 19 "github.com/MetalBlockchain/metalgo/utils/constants" 20 "github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1" 21 "github.com/MetalBlockchain/metalgo/utils/set" 22 "github.com/MetalBlockchain/metalgo/utils/units" 23 "github.com/MetalBlockchain/metalgo/vms/components/avax" 24 "github.com/MetalBlockchain/metalgo/vms/platformvm/reward" 25 "github.com/MetalBlockchain/metalgo/vms/platformvm/txs" 26 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 27 "github.com/MetalBlockchain/metalgo/wallet/subnet/primary/common" 28 29 ginkgo "github.com/onsi/ginkgo/v2" 30 ) 31 32 var _ = e2e.DescribePChain("[Interchain Workflow]", ginkgo.Label(e2e.UsesCChainLabel), func() { 33 require := require.New(ginkgo.GinkgoT()) 34 35 const ( 36 transferAmount = 10 * units.Avax 37 weight = 2_000 * units.Avax // Used for both validation and delegation 38 ) 39 40 ginkgo.It("should ensure that funds can be transferred from the P-Chain to the X-Chain and the C-Chain", func() { 41 network := e2e.Env.GetNetwork() 42 43 ginkgo.By("checking that the network has a compatible minimum stake duration", func() { 44 minStakeDuration := cast.ToDuration(network.DefaultFlags[config.MinStakeDurationKey]) 45 require.Equal(tmpnet.DefaultMinStakeDuration, minStakeDuration) 46 }) 47 48 ginkgo.By("creating wallet with a funded key to send from and recipient key to deliver to") 49 recipientKey, err := secp256k1.NewPrivateKey() 50 require.NoError(err) 51 keychain := e2e.Env.NewKeychain(1) 52 keychain.Add(recipientKey) 53 nodeURI := e2e.Env.GetRandomNodeURI() 54 baseWallet := e2e.NewWallet(keychain, nodeURI) 55 xWallet := baseWallet.X() 56 cWallet := baseWallet.C() 57 pWallet := baseWallet.P() 58 59 xBuilder := xWallet.Builder() 60 xContext := xBuilder.Context() 61 pBuilder := pWallet.Builder() 62 pContext := pBuilder.Context() 63 cBuilder := cWallet.Builder() 64 cContext := cBuilder.Context() 65 66 ginkgo.By("defining common configuration") 67 recipientEthAddress := evm.GetEthAddress(recipientKey) 68 avaxAssetID := xContext.AVAXAssetID 69 // Use the same owner for sending to X-Chain and importing funds to P-Chain 70 recipientOwner := secp256k1fx.OutputOwners{ 71 Threshold: 1, 72 Addrs: []ids.ShortID{ 73 recipientKey.Address(), 74 }, 75 } 76 // Use the same outputs for both X-Chain and C-Chain exports 77 exportOutputs := []*avax.TransferableOutput{ 78 { 79 Asset: avax.Asset{ 80 ID: avaxAssetID, 81 }, 82 Out: &secp256k1fx.TransferOutput{ 83 Amt: transferAmount, 84 OutputOwners: secp256k1fx.OutputOwners{ 85 Threshold: 1, 86 Addrs: []ids.ShortID{ 87 keychain.Keys[0].Address(), 88 }, 89 }, 90 }, 91 }, 92 } 93 94 ginkgo.By("adding new node and waiting for it to report healthy") 95 node := e2e.AddEphemeralNode(network, tmpnet.FlagsMap{}) 96 e2e.WaitForHealthy(node) 97 98 ginkgo.By("retrieving new node's id and pop") 99 infoClient := info.NewClient(node.URI) 100 nodeID, nodePOP, err := infoClient.GetNodeID(e2e.DefaultContext()) 101 require.NoError(err) 102 103 // Adding a validator should not break interchain transfer. 104 endTime := time.Now().Add(30 * time.Second) 105 ginkgo.By("adding the new node as a validator", func() { 106 rewardKey, err := secp256k1.NewPrivateKey() 107 require.NoError(err) 108 109 const ( 110 delegationPercent = 0.10 // 10% 111 delegationShare = reward.PercentDenominator * delegationPercent 112 ) 113 114 _, err = pWallet.IssueAddPermissionlessValidatorTx( 115 &txs.SubnetValidator{ 116 Validator: txs.Validator{ 117 NodeID: nodeID, 118 End: uint64(endTime.Unix()), 119 Wght: weight, 120 }, 121 Subnet: constants.PrimaryNetworkID, 122 }, 123 nodePOP, 124 pContext.AVAXAssetID, 125 &secp256k1fx.OutputOwners{ 126 Threshold: 1, 127 Addrs: []ids.ShortID{rewardKey.Address()}, 128 }, 129 &secp256k1fx.OutputOwners{ 130 Threshold: 1, 131 Addrs: []ids.ShortID{rewardKey.Address()}, 132 }, 133 delegationShare, 134 e2e.WithDefaultContext(), 135 ) 136 require.NoError(err) 137 }) 138 139 // Adding a delegator should not break interchain transfer. 140 ginkgo.By("adding a delegator to the new node", func() { 141 rewardKey, err := secp256k1.NewPrivateKey() 142 require.NoError(err) 143 144 _, err = pWallet.IssueAddPermissionlessDelegatorTx( 145 &txs.SubnetValidator{ 146 Validator: txs.Validator{ 147 NodeID: nodeID, 148 End: uint64(endTime.Unix()), 149 Wght: weight, 150 }, 151 Subnet: constants.PrimaryNetworkID, 152 }, 153 pContext.AVAXAssetID, 154 &secp256k1fx.OutputOwners{ 155 Threshold: 1, 156 Addrs: []ids.ShortID{rewardKey.Address()}, 157 }, 158 e2e.WithDefaultContext(), 159 ) 160 require.NoError(err) 161 }) 162 163 ginkgo.By("exporting AVAX from the P-Chain to the X-Chain", func() { 164 _, err := pWallet.IssueExportTx( 165 xContext.BlockchainID, 166 exportOutputs, 167 e2e.WithDefaultContext(), 168 ) 169 require.NoError(err) 170 }) 171 172 ginkgo.By("importing AVAX from the P-Chain to the X-Chain", func() { 173 _, err := xWallet.IssueImportTx( 174 constants.PlatformChainID, 175 &recipientOwner, 176 e2e.WithDefaultContext(), 177 ) 178 require.NoError(err) 179 }) 180 181 ginkgo.By("checking that the recipient address has received imported funds on the X-Chain", func() { 182 balances, err := xWallet.Builder().GetFTBalance(common.WithCustomAddresses(set.Of( 183 recipientKey.Address(), 184 ))) 185 require.NoError(err) 186 require.Positive(balances[avaxAssetID]) 187 }) 188 189 ginkgo.By("exporting AVAX from the P-Chain to the C-Chain", func() { 190 _, err := pWallet.IssueExportTx( 191 cContext.BlockchainID, 192 exportOutputs, 193 e2e.WithDefaultContext(), 194 ) 195 require.NoError(err) 196 }) 197 198 ginkgo.By("initializing a new eth client") 199 ethClient := e2e.NewEthClient(nodeURI) 200 201 ginkgo.By("importing AVAX from the P-Chain to the C-Chain", func() { 202 _, err := cWallet.IssueImportTx( 203 constants.PlatformChainID, 204 recipientEthAddress, 205 e2e.WithDefaultContext(), 206 e2e.WithSuggestedGasPrice(ethClient), 207 ) 208 require.NoError(err) 209 }) 210 211 ginkgo.By("checking that the recipient address has received imported funds on the C-Chain") 212 balance, err := ethClient.BalanceAt(e2e.DefaultContext(), recipientEthAddress, nil) 213 require.NoError(err) 214 require.Positive(balance.Cmp(big.NewInt(0))) 215 216 ginkgo.By("stopping validator node to free up resources for a bootstrap check") 217 require.NoError(node.Stop(e2e.DefaultContext())) 218 219 e2e.CheckBootstrapIsPossible(network) 220 }) 221 })