github.com/ava-labs/avalanchego@v1.11.11/vms/avm/environment_test.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package avm
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"math/rand"
    10  	"testing"
    11  
    12  	"github.com/stretchr/testify/require"
    13  
    14  	"github.com/ava-labs/avalanchego/api/keystore"
    15  	"github.com/ava-labs/avalanchego/chains/atomic"
    16  	"github.com/ava-labs/avalanchego/database/memdb"
    17  	"github.com/ava-labs/avalanchego/database/prefixdb"
    18  	"github.com/ava-labs/avalanchego/ids"
    19  	"github.com/ava-labs/avalanchego/snow"
    20  	"github.com/ava-labs/avalanchego/snow/engine/common"
    21  	"github.com/ava-labs/avalanchego/snow/engine/enginetest"
    22  	"github.com/ava-labs/avalanchego/snow/snowtest"
    23  	"github.com/ava-labs/avalanchego/upgrade/upgradetest"
    24  	"github.com/ava-labs/avalanchego/utils/constants"
    25  	"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
    26  	"github.com/ava-labs/avalanchego/utils/formatting"
    27  	"github.com/ava-labs/avalanchego/utils/formatting/address"
    28  	"github.com/ava-labs/avalanchego/utils/logging"
    29  	"github.com/ava-labs/avalanchego/utils/sampler"
    30  	"github.com/ava-labs/avalanchego/vms/avm/block/executor"
    31  	"github.com/ava-labs/avalanchego/vms/avm/config"
    32  	"github.com/ava-labs/avalanchego/vms/avm/fxs"
    33  	"github.com/ava-labs/avalanchego/vms/avm/txs"
    34  	"github.com/ava-labs/avalanchego/vms/avm/txs/txstest"
    35  	"github.com/ava-labs/avalanchego/vms/components/avax"
    36  	"github.com/ava-labs/avalanchego/vms/nftfx"
    37  	"github.com/ava-labs/avalanchego/vms/secp256k1fx"
    38  
    39  	avajson "github.com/ava-labs/avalanchego/utils/json"
    40  	keystoreutils "github.com/ava-labs/avalanchego/vms/components/keystore"
    41  )
    42  
    43  const (
    44  	testTxFee    uint64 = 1000
    45  	startBalance uint64 = 50000
    46  
    47  	username       = "bobby"
    48  	password       = "StrnasfqewiurPasswdn56d" //#nosec G101
    49  	feeAssetName   = "TEST"
    50  	otherAssetName = "OTHER"
    51  )
    52  
    53  var (
    54  	testChangeAddr = ids.GenerateTestShortID()
    55  	testCases      = []struct {
    56  		name      string
    57  		avaxAsset bool
    58  	}{
    59  		{
    60  			name:      "genesis asset is AVAX",
    61  			avaxAsset: true,
    62  		},
    63  		{
    64  			name:      "genesis asset is TEST",
    65  			avaxAsset: false,
    66  		},
    67  	}
    68  
    69  	assetID = ids.ID{1, 2, 3}
    70  
    71  	keys  = secp256k1.TestKeys()[:3] // TODO: Remove [:3]
    72  	addrs []ids.ShortID              // addrs[i] corresponds to keys[i]
    73  )
    74  
    75  func init() {
    76  	addrs = make([]ids.ShortID, len(keys))
    77  	for i, key := range keys {
    78  		addrs[i] = key.Address()
    79  	}
    80  }
    81  
    82  type user struct {
    83  	username    string
    84  	password    string
    85  	initialKeys []*secp256k1.PrivateKey
    86  }
    87  
    88  type envConfig struct {
    89  	fork             upgradetest.Fork
    90  	isCustomFeeAsset bool
    91  	keystoreUsers    []*user
    92  	vmStaticConfig   *config.Config
    93  	vmDynamicConfig  *Config
    94  	additionalFxs    []*common.Fx
    95  	notLinearized    bool
    96  	notBootstrapped  bool
    97  }
    98  
    99  type environment struct {
   100  	genesisBytes []byte
   101  	genesisTx    *txs.Tx
   102  	sharedMemory *atomic.Memory
   103  	issuer       chan common.Message
   104  	vm           *VM
   105  	txBuilder    *txstest.Builder
   106  }
   107  
   108  // setup the testing environment
   109  func setup(tb testing.TB, c *envConfig) *environment {
   110  	require := require.New(tb)
   111  
   112  	var (
   113  		genesisArgs *BuildGenesisArgs
   114  		assetName   = "AVAX"
   115  	)
   116  	if c.isCustomFeeAsset {
   117  		genesisArgs = makeCustomAssetGenesis(tb)
   118  		assetName = feeAssetName
   119  	} else {
   120  		genesisArgs = makeDefaultGenesis(tb)
   121  	}
   122  
   123  	genesisBytes := buildGenesisTestWithArgs(tb, genesisArgs)
   124  
   125  	ctx := snowtest.Context(tb, snowtest.XChainID)
   126  
   127  	baseDB := memdb.New()
   128  	m := atomic.NewMemory(prefixdb.New([]byte{0}, baseDB))
   129  	ctx.SharedMemory = m.NewSharedMemory(ctx.ChainID)
   130  
   131  	// NB: this lock is intentionally left locked when this function returns.
   132  	// The caller of this function is responsible for unlocking.
   133  	ctx.Lock.Lock()
   134  
   135  	userKeystore := keystore.New(logging.NoLog{}, memdb.New())
   136  	ctx.Keystore = userKeystore.NewBlockchainKeyStore(ctx.ChainID)
   137  
   138  	for _, user := range c.keystoreUsers {
   139  		require.NoError(userKeystore.CreateUser(user.username, user.password))
   140  
   141  		// Import the initially funded private keys
   142  		keystoreUser, err := keystoreutils.NewUserFromKeystore(ctx.Keystore, user.username, user.password)
   143  		require.NoError(err)
   144  
   145  		require.NoError(keystoreUser.PutKeys(user.initialKeys...))
   146  		require.NoError(keystoreUser.Close())
   147  	}
   148  
   149  	vmStaticConfig := config.Config{
   150  		Upgrades:         upgradetest.GetConfig(c.fork),
   151  		TxFee:            testTxFee,
   152  		CreateAssetTxFee: testTxFee,
   153  	}
   154  	if c.vmStaticConfig != nil {
   155  		vmStaticConfig = *c.vmStaticConfig
   156  	}
   157  
   158  	vm := &VM{
   159  		Config: vmStaticConfig,
   160  	}
   161  
   162  	vmDynamicConfig := DefaultConfig
   163  	vmDynamicConfig.IndexTransactions = true
   164  	if c.vmDynamicConfig != nil {
   165  		vmDynamicConfig = *c.vmDynamicConfig
   166  	}
   167  	configBytes, err := json.Marshal(vmDynamicConfig)
   168  	require.NoError(err)
   169  
   170  	require.NoError(vm.Initialize(
   171  		context.Background(),
   172  		ctx,
   173  		prefixdb.New([]byte{1}, baseDB),
   174  		genesisBytes,
   175  		nil,
   176  		configBytes,
   177  		nil,
   178  		append(
   179  			[]*common.Fx{
   180  				{
   181  					ID: secp256k1fx.ID,
   182  					Fx: &secp256k1fx.Fx{},
   183  				},
   184  				{
   185  					ID: nftfx.ID,
   186  					Fx: &nftfx.Fx{},
   187  				},
   188  			},
   189  			c.additionalFxs...,
   190  		),
   191  		&enginetest.Sender{},
   192  	))
   193  
   194  	stopVertexID := ids.GenerateTestID()
   195  	issuer := make(chan common.Message, 1)
   196  
   197  	env := &environment{
   198  		genesisBytes: genesisBytes,
   199  		genesisTx:    getCreateTxFromGenesisTest(tb, genesisBytes, assetName),
   200  		sharedMemory: m,
   201  		issuer:       issuer,
   202  		vm:           vm,
   203  		txBuilder:    txstest.New(vm.parser.Codec(), vm.ctx, &vm.Config, vm.feeAssetID, vm.state),
   204  	}
   205  
   206  	require.NoError(vm.SetState(context.Background(), snow.Bootstrapping))
   207  	if c.notLinearized {
   208  		return env
   209  	}
   210  
   211  	require.NoError(vm.Linearize(context.Background(), stopVertexID, issuer))
   212  	if c.notBootstrapped {
   213  		return env
   214  	}
   215  
   216  	require.NoError(vm.SetState(context.Background(), snow.NormalOp))
   217  
   218  	tb.Cleanup(func() {
   219  		env.vm.ctx.Lock.Lock()
   220  		defer env.vm.ctx.Lock.Unlock()
   221  
   222  		require.NoError(env.vm.Shutdown(context.Background()))
   223  	})
   224  
   225  	return env
   226  }
   227  
   228  // Returns:
   229  //
   230  //  1. tx in genesis that creates asset
   231  //  2. the index of the output
   232  func getCreateTxFromGenesisTest(tb testing.TB, genesisBytes []byte, assetName string) *txs.Tx {
   233  	require := require.New(tb)
   234  
   235  	parser, err := txs.NewParser(
   236  		[]fxs.Fx{
   237  			&secp256k1fx.Fx{},
   238  		},
   239  	)
   240  	require.NoError(err)
   241  
   242  	cm := parser.GenesisCodec()
   243  	genesis := Genesis{}
   244  	_, err = cm.Unmarshal(genesisBytes, &genesis)
   245  	require.NoError(err)
   246  	require.NotEmpty(genesis.Txs)
   247  
   248  	var assetTx *GenesisAsset
   249  	for _, tx := range genesis.Txs {
   250  		if tx.Name == assetName {
   251  			assetTx = tx
   252  			break
   253  		}
   254  	}
   255  	require.NotNil(assetTx)
   256  
   257  	tx := &txs.Tx{
   258  		Unsigned: &assetTx.CreateAssetTx,
   259  	}
   260  	require.NoError(tx.Initialize(parser.GenesisCodec()))
   261  	return tx
   262  }
   263  
   264  // buildGenesisTest is the common Genesis builder for most tests
   265  func buildGenesisTest(tb testing.TB) []byte {
   266  	defaultArgs := makeDefaultGenesis(tb)
   267  	return buildGenesisTestWithArgs(tb, defaultArgs)
   268  }
   269  
   270  // buildGenesisTestWithArgs allows building the genesis while injecting different starting points (args)
   271  func buildGenesisTestWithArgs(tb testing.TB, args *BuildGenesisArgs) []byte {
   272  	require := require.New(tb)
   273  
   274  	ss := CreateStaticService()
   275  
   276  	reply := BuildGenesisReply{}
   277  	require.NoError(ss.BuildGenesis(nil, args, &reply))
   278  
   279  	b, err := formatting.Decode(reply.Encoding, reply.Bytes)
   280  	require.NoError(err)
   281  	return b
   282  }
   283  
   284  func newTx(tb testing.TB, genesisBytes []byte, chainID ids.ID, parser txs.Parser, assetName string) *txs.Tx {
   285  	require := require.New(tb)
   286  
   287  	createTx := getCreateTxFromGenesisTest(tb, genesisBytes, assetName)
   288  	tx := &txs.Tx{Unsigned: &txs.BaseTx{
   289  		BaseTx: avax.BaseTx{
   290  			NetworkID:    constants.UnitTestID,
   291  			BlockchainID: chainID,
   292  			Ins: []*avax.TransferableInput{{
   293  				UTXOID: avax.UTXOID{
   294  					TxID:        createTx.ID(),
   295  					OutputIndex: 2,
   296  				},
   297  				Asset: avax.Asset{ID: createTx.ID()},
   298  				In: &secp256k1fx.TransferInput{
   299  					Amt: startBalance,
   300  					Input: secp256k1fx.Input{
   301  						SigIndices: []uint32{
   302  							0,
   303  						},
   304  					},
   305  				},
   306  			}},
   307  		},
   308  	}}
   309  	require.NoError(tx.SignSECP256K1Fx(parser.Codec(), [][]*secp256k1.PrivateKey{{keys[0]}}))
   310  	return tx
   311  }
   312  
   313  // Sample from a set of addresses and return them raw and formatted as strings.
   314  // The size of the sample is between 1 and len(addrs)
   315  // If len(addrs) == 0, returns nil
   316  func sampleAddrs(tb testing.TB, addressFormatter avax.AddressManager, addrs []ids.ShortID) ([]ids.ShortID, []string) {
   317  	require := require.New(tb)
   318  
   319  	sampledAddrs := []ids.ShortID{}
   320  	sampledAddrsStr := []string{}
   321  
   322  	sampler := sampler.NewUniform()
   323  	sampler.Initialize(uint64(len(addrs)))
   324  
   325  	numAddrs := 1 + rand.Intn(len(addrs)) // #nosec G404
   326  	indices, ok := sampler.Sample(numAddrs)
   327  	require.True(ok)
   328  	for _, index := range indices {
   329  		addr := addrs[index]
   330  		addrStr, err := addressFormatter.FormatLocalAddress(addr)
   331  		require.NoError(err)
   332  
   333  		sampledAddrs = append(sampledAddrs, addr)
   334  		sampledAddrsStr = append(sampledAddrsStr, addrStr)
   335  	}
   336  	return sampledAddrs, sampledAddrsStr
   337  }
   338  
   339  func makeDefaultGenesis(tb testing.TB) *BuildGenesisArgs {
   340  	require := require.New(tb)
   341  
   342  	addr0Str, err := address.FormatBech32(constants.UnitTestHRP, addrs[0].Bytes())
   343  	require.NoError(err)
   344  
   345  	addr1Str, err := address.FormatBech32(constants.UnitTestHRP, addrs[1].Bytes())
   346  	require.NoError(err)
   347  
   348  	addr2Str, err := address.FormatBech32(constants.UnitTestHRP, addrs[2].Bytes())
   349  	require.NoError(err)
   350  
   351  	return &BuildGenesisArgs{
   352  		Encoding: formatting.Hex,
   353  		GenesisData: map[string]AssetDefinition{
   354  			"asset1": {
   355  				Name:   "AVAX",
   356  				Symbol: "SYMB",
   357  				InitialState: map[string][]interface{}{
   358  					"fixedCap": {
   359  						Holder{
   360  							Amount:  avajson.Uint64(startBalance),
   361  							Address: addr0Str,
   362  						},
   363  						Holder{
   364  							Amount:  avajson.Uint64(startBalance),
   365  							Address: addr1Str,
   366  						},
   367  						Holder{
   368  							Amount:  avajson.Uint64(startBalance),
   369  							Address: addr2Str,
   370  						},
   371  					},
   372  				},
   373  			},
   374  			"asset2": {
   375  				Name:   "myVarCapAsset",
   376  				Symbol: "MVCA",
   377  				InitialState: map[string][]interface{}{
   378  					"variableCap": {
   379  						Owners{
   380  							Threshold: 1,
   381  							Minters: []string{
   382  								addr0Str,
   383  								addr1Str,
   384  							},
   385  						},
   386  						Owners{
   387  							Threshold: 2,
   388  							Minters: []string{
   389  								addr0Str,
   390  								addr1Str,
   391  								addr2Str,
   392  							},
   393  						},
   394  					},
   395  				},
   396  			},
   397  			"asset3": {
   398  				Name: "myOtherVarCapAsset",
   399  				InitialState: map[string][]interface{}{
   400  					"variableCap": {
   401  						Owners{
   402  							Threshold: 1,
   403  							Minters: []string{
   404  								addr0Str,
   405  							},
   406  						},
   407  					},
   408  				},
   409  			},
   410  			"asset4": {
   411  				Name: "myFixedCapAsset",
   412  				InitialState: map[string][]interface{}{
   413  					"fixedCap": {
   414  						Holder{
   415  							Amount:  avajson.Uint64(startBalance),
   416  							Address: addr0Str,
   417  						},
   418  						Holder{
   419  							Amount:  avajson.Uint64(startBalance),
   420  							Address: addr1Str,
   421  						},
   422  					},
   423  				},
   424  			},
   425  		},
   426  	}
   427  }
   428  
   429  func makeCustomAssetGenesis(tb testing.TB) *BuildGenesisArgs {
   430  	require := require.New(tb)
   431  
   432  	addr0Str, err := address.FormatBech32(constants.UnitTestHRP, addrs[0].Bytes())
   433  	require.NoError(err)
   434  
   435  	addr1Str, err := address.FormatBech32(constants.UnitTestHRP, addrs[1].Bytes())
   436  	require.NoError(err)
   437  
   438  	addr2Str, err := address.FormatBech32(constants.UnitTestHRP, addrs[2].Bytes())
   439  	require.NoError(err)
   440  
   441  	return &BuildGenesisArgs{
   442  		Encoding: formatting.Hex,
   443  		GenesisData: map[string]AssetDefinition{
   444  			"asset1": {
   445  				Name:   feeAssetName,
   446  				Symbol: "TST",
   447  				InitialState: map[string][]interface{}{
   448  					"fixedCap": {
   449  						Holder{
   450  							Amount:  avajson.Uint64(startBalance),
   451  							Address: addr0Str,
   452  						},
   453  						Holder{
   454  							Amount:  avajson.Uint64(startBalance),
   455  							Address: addr1Str,
   456  						},
   457  						Holder{
   458  							Amount:  avajson.Uint64(startBalance),
   459  							Address: addr2Str,
   460  						},
   461  					},
   462  				},
   463  			},
   464  			"asset2": {
   465  				Name:   otherAssetName,
   466  				Symbol: "OTH",
   467  				InitialState: map[string][]interface{}{
   468  					"fixedCap": {
   469  						Holder{
   470  							Amount:  avajson.Uint64(startBalance),
   471  							Address: addr0Str,
   472  						},
   473  						Holder{
   474  							Amount:  avajson.Uint64(startBalance),
   475  							Address: addr1Str,
   476  						},
   477  						Holder{
   478  							Amount:  avajson.Uint64(startBalance),
   479  							Address: addr2Str,
   480  						},
   481  					},
   482  				},
   483  			},
   484  		},
   485  	}
   486  }
   487  
   488  // issueAndAccept expects the context lock not to be held
   489  func issueAndAccept(
   490  	require *require.Assertions,
   491  	vm *VM,
   492  	issuer <-chan common.Message,
   493  	tx *txs.Tx,
   494  ) {
   495  	txID, err := vm.issueTxFromRPC(tx)
   496  	require.NoError(err)
   497  	require.Equal(tx.ID(), txID)
   498  
   499  	buildAndAccept(require, vm, issuer, txID)
   500  }
   501  
   502  // buildAndAccept expects the context lock not to be held
   503  func buildAndAccept(
   504  	require *require.Assertions,
   505  	vm *VM,
   506  	issuer <-chan common.Message,
   507  	txID ids.ID,
   508  ) {
   509  	require.Equal(common.PendingTxs, <-issuer)
   510  
   511  	vm.ctx.Lock.Lock()
   512  	defer vm.ctx.Lock.Unlock()
   513  
   514  	blkIntf, err := vm.BuildBlock(context.Background())
   515  	require.NoError(err)
   516  	require.IsType(&executor.Block{}, blkIntf)
   517  
   518  	blk := blkIntf.(*executor.Block)
   519  	txs := blk.Txs()
   520  	require.Len(txs, 1)
   521  
   522  	issuedTx := txs[0]
   523  	require.Equal(txID, issuedTx.ID())
   524  	require.NoError(blk.Verify(context.Background()))
   525  	require.NoError(vm.SetPreference(context.Background(), blk.ID()))
   526  	require.NoError(blk.Accept(context.Background()))
   527  }