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