diff --git a/vms/avm/environment_test.go b/vms/avm/environment_test.go index 52a76425e7ae..d4375aa092d1 100644 --- a/vms/avm/environment_test.go +++ b/vms/avm/environment_test.go @@ -24,7 +24,6 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/formatting" "github.com/ava-labs/avalanchego/utils/formatting/address" - "github.com/ava-labs/avalanchego/utils/linked" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/sampler" "github.com/ava-labs/avalanchego/utils/timer/mockable" @@ -32,6 +31,7 @@ import ( "github.com/ava-labs/avalanchego/vms/avm/config" "github.com/ava-labs/avalanchego/vms/avm/fxs" "github.com/ava-labs/avalanchego/vms/avm/txs" + "github.com/ava-labs/avalanchego/vms/avm/txs/txstest" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/nftfx" "github.com/ava-labs/avalanchego/vms/secp256k1fx" @@ -77,12 +77,6 @@ var ( keys = secp256k1.TestKeys()[:3] // TODO: Remove [:3] addrs []ids.ShortID // addrs[i] corresponds to keys[i] - - noFeesTestConfig = &config.Config{ - EUpgradeTime: mockable.MaxTime, - TxFee: 0, - CreateAssetTxFee: 0, - } ) func init() { @@ -110,13 +104,12 @@ type envConfig struct { } type environment struct { - genesisBytes []byte - genesisTx *txs.Tx - sharedMemory *atomic.Memory - issuer chan common.Message - vm *VM - service *Service - walletService *WalletService + genesisBytes []byte + genesisTx *txs.Tx + sharedMemory *atomic.Memory + issuer chan common.Message + vm *VM + txBuilder *txstest.Builder } // setup the testing environment @@ -210,13 +203,7 @@ func setup(tb testing.TB, c *envConfig) *environment { sharedMemory: m, issuer: issuer, vm: vm, - service: &Service{ - vm: vm, - }, - walletService: &WalletService{ - vm: vm, - pendingTxs: linked.NewHashmap[ids.ID, *txs.Tx](), - }, + txBuilder: txstest.New(vm.parser.Codec(), vm.ctx, &vm.Config, vm.feeAssetID, vm.state), } require.NoError(vm.SetState(context.Background(), snow.Bootstrapping)) @@ -230,6 +217,14 @@ func setup(tb testing.TB, c *envConfig) *environment { } require.NoError(vm.SetState(context.Background(), snow.NormalOp)) + + tb.Cleanup(func() { + env.vm.ctx.Lock.Lock() + defer env.vm.ctx.Lock.Unlock() + + require.NoError(env.vm.Shutdown(context.Background())) + }) + return env } diff --git a/vms/avm/index_test.go b/vms/avm/index_test.go index 459d4230f32c..3d8614d5ee6d 100644 --- a/vms/avm/index_test.go +++ b/vms/avm/index_test.go @@ -4,7 +4,6 @@ package avm import ( - "context" "testing" "github.com/prometheus/client_golang/prometheus" @@ -28,13 +27,8 @@ import ( func TestIndexTransaction_Ordered(t *testing.T) { require := require.New(t) - env := setup(t, &envConfig{ - vmStaticConfig: noFeesTestConfig, - }) - defer func() { - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + env := setup(t, &envConfig{fork: durango}) + defer env.vm.ctx.Lock.Unlock() key := keys[0] addr := key.PublicKey().Address() @@ -72,13 +66,8 @@ func TestIndexTransaction_Ordered(t *testing.T) { func TestIndexTransaction_MultipleTransactions(t *testing.T) { require := require.New(t) - env := setup(t, &envConfig{ - vmStaticConfig: noFeesTestConfig, - }) - defer func() { - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + env := setup(t, &envConfig{fork: durango}) + defer env.vm.ctx.Lock.Unlock() addressTxMap := map[ids.ShortID]*txs.Tx{} txAssetID := avax.Asset{ID: env.genesisTx.ID()} @@ -120,13 +109,8 @@ func TestIndexTransaction_MultipleTransactions(t *testing.T) { func TestIndexTransaction_MultipleAddresses(t *testing.T) { require := require.New(t) - env := setup(t, &envConfig{ - vmStaticConfig: noFeesTestConfig, - }) - defer func() { - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + env := setup(t, &envConfig{fork: durango}) + defer env.vm.ctx.Lock.Unlock() addrs := make([]ids.ShortID, len(keys)) for i, key := range keys { @@ -163,13 +147,8 @@ func TestIndexTransaction_MultipleAddresses(t *testing.T) { func TestIndexer_Read(t *testing.T) { require := require.New(t) - env := setup(t, &envConfig{ - vmStaticConfig: noFeesTestConfig, - }) - defer func() { - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + env := setup(t, &envConfig{fork: durango}) + defer env.vm.ctx.Lock.Unlock() // generate test address and asset IDs assetID := ids.GenerateTestID() @@ -259,7 +238,7 @@ func buildUTXO(utxoID avax.UTXOID, txAssetID avax.Asset, addr ids.ShortID) *avax UTXOID: utxoID, Asset: txAssetID, Out: &secp256k1fx.TransferOutput{ - Amt: 1000, + Amt: startBalance, OutputOwners: secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{addr}, @@ -277,14 +256,14 @@ func buildTX(chainID ids.ID, utxoID avax.UTXOID, txAssetID avax.Asset, address . UTXOID: utxoID, Asset: txAssetID, In: &secp256k1fx.TransferInput{ - Amt: 1000, + Amt: startBalance, Input: secp256k1fx.Input{SigIndices: []uint32{0}}, }, }}, Outs: []*avax.TransferableOutput{{ Asset: txAssetID, Out: &secp256k1fx.TransferOutput{ - Amt: 1000, + Amt: startBalance - testTxFee, OutputOwners: secp256k1fx.OutputOwners{ Threshold: 1, Addrs: address, diff --git a/vms/avm/service_test.go b/vms/avm/service_test.go index 677a0193ba97..d9dbb8db6f12 100644 --- a/vms/avm/service_test.go +++ b/vms/avm/service_test.go @@ -4,7 +4,6 @@ package avm import ( - "context" "encoding/json" "strings" "testing" @@ -28,8 +27,11 @@ import ( "github.com/ava-labs/avalanchego/utils/formatting" "github.com/ava-labs/avalanchego/utils/formatting/address" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/timer/mockable" + "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/avm/block" "github.com/ava-labs/avalanchego/vms/avm/block/executor" + "github.com/ava-labs/avalanchego/vms/avm/config" "github.com/ava-labs/avalanchego/vms/avm/state" "github.com/ava-labs/avalanchego/vms/avm/txs" "github.com/ava-labs/avalanchego/vms/components/avax" @@ -48,17 +50,12 @@ func TestServiceIssueTx(t *testing.T) { env := setup(t, &envConfig{ fork: latest, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - txArgs := &api.FormattedTx{} txReply := &api.JSONTxID{} - err := env.service.IssueTx(nil, txArgs, txReply) + err := service.IssueTx(nil, txArgs, txReply) require.ErrorIs(err, codec.ErrCantUnpackVersion) tx := newTx(t, env.genesisBytes, env.vm.ctx.ChainID, env.vm.parser, "AVAX") @@ -66,7 +63,7 @@ func TestServiceIssueTx(t *testing.T) { require.NoError(err) txArgs.Encoding = formatting.Hex txReply = &api.JSONTxID{} - require.NoError(env.service.IssueTx(nil, txArgs, txReply)) + require.NoError(service.IssueTx(nil, txArgs, txReply)) require.Equal(tx.ID(), txReply.TxID) } @@ -76,33 +73,28 @@ func TestServiceGetTxStatus(t *testing.T) { env := setup(t, &envConfig{ fork: latest, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - statusArgs := &api.JSONTxID{} statusReply := &GetTxStatusReply{} - err := env.service.GetTxStatus(nil, statusArgs, statusReply) + err := service.GetTxStatus(nil, statusArgs, statusReply) require.ErrorIs(err, errNilTxID) - newTx := newAvaxBaseTxWithOutputs(t, env.genesisBytes, env.vm.ctx.ChainID, env.vm.TxFee, env.vm.parser) + newTx := newAvaxBaseTxWithOutputs(t, env) txID := newTx.ID() statusArgs = &api.JSONTxID{ TxID: txID, } statusReply = &GetTxStatusReply{} - require.NoError(env.service.GetTxStatus(nil, statusArgs, statusReply)) + require.NoError(service.GetTxStatus(nil, statusArgs, statusReply)) require.Equal(choices.Unknown, statusReply.Status) issueAndAccept(require, env.vm, env.issuer, newTx) statusReply = &GetTxStatusReply{} - require.NoError(env.service.GetTxStatus(nil, statusArgs, statusReply)) + require.NoError(service.GetTxStatus(nil, statusArgs, statusReply)) require.Equal(choices.Accepted, statusReply.Status) } @@ -113,11 +105,7 @@ func TestServiceGetBalanceStrict(t *testing.T) { env := setup(t, &envConfig{ fork: latest, }) - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + service := &Service{vm: env.vm} assetID := ids.GenerateTestID() addr := ids.GenerateTestShortID() @@ -153,7 +141,7 @@ func TestServiceGetBalanceStrict(t *testing.T) { IncludePartial: true, } balanceReply := &GetBalanceReply{} - require.NoError(env.service.GetBalance(nil, balanceArgs, balanceReply)) + require.NoError(service.GetBalance(nil, balanceArgs, balanceReply)) // The balance should include the UTXO since it is partly owned by [addr] require.Equal(uint64(1337), uint64(balanceReply.Balance)) require.Len(balanceReply.UTXOIDs, 1) @@ -164,7 +152,7 @@ func TestServiceGetBalanceStrict(t *testing.T) { AssetID: assetID.String(), } balanceReply = &GetBalanceReply{} - require.NoError(env.service.GetBalance(nil, balanceArgs, balanceReply)) + require.NoError(service.GetBalance(nil, balanceArgs, balanceReply)) // The balance should not include the UTXO since it is only partly owned by [addr] require.Zero(balanceReply.Balance) require.Empty(balanceReply.UTXOIDs) @@ -200,7 +188,7 @@ func TestServiceGetBalanceStrict(t *testing.T) { IncludePartial: true, } balanceReply = &GetBalanceReply{} - require.NoError(env.service.GetBalance(nil, balanceArgs, balanceReply)) + require.NoError(service.GetBalance(nil, balanceArgs, balanceReply)) // The balance should include the UTXO since it is partly owned by [addr] require.Equal(uint64(1337+1337), uint64(balanceReply.Balance)) require.Len(balanceReply.UTXOIDs, 2) @@ -211,7 +199,7 @@ func TestServiceGetBalanceStrict(t *testing.T) { AssetID: assetID.String(), } balanceReply = &GetBalanceReply{} - require.NoError(env.service.GetBalance(nil, balanceArgs, balanceReply)) + require.NoError(service.GetBalance(nil, balanceArgs, balanceReply)) // The balance should not include the UTXO since it is only partly owned by [addr] require.Zero(balanceReply.Balance) require.Empty(balanceReply.UTXOIDs) @@ -249,7 +237,7 @@ func TestServiceGetBalanceStrict(t *testing.T) { IncludePartial: true, } balanceReply = &GetBalanceReply{} - require.NoError(env.service.GetBalance(nil, balanceArgs, balanceReply)) + require.NoError(service.GetBalance(nil, balanceArgs, balanceReply)) // The balance should include the UTXO since it is partly owned by [addr] require.Equal(uint64(1337*3), uint64(balanceReply.Balance)) require.Len(balanceReply.UTXOIDs, 3) @@ -260,7 +248,7 @@ func TestServiceGetBalanceStrict(t *testing.T) { AssetID: assetID.String(), } balanceReply = &GetBalanceReply{} - require.NoError(env.service.GetBalance(nil, balanceArgs, balanceReply)) + require.NoError(service.GetBalance(nil, balanceArgs, balanceReply)) // The balance should not include the UTXO since it is only partly owned by [addr] require.Zero(balanceReply.Balance) require.Empty(balanceReply.UTXOIDs) @@ -271,14 +259,11 @@ func TestServiceGetTxs(t *testing.T) { env := setup(t, &envConfig{ fork: latest, }) + service := &Service{vm: env.vm} + var err error env.vm.addressTxsIndexer, err = index.NewIndexer(env.vm.db, env.vm.ctx.Log, "", prometheus.NewRegistry(), false) require.NoError(err) - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() assetID := ids.GenerateTestID() addr := ids.GenerateTestShortID() @@ -297,14 +282,14 @@ func TestServiceGetTxs(t *testing.T) { AssetID: assetID.String(), } getTxsReply := &GetAddressTxsReply{} - require.NoError(env.service.GetAddressTxs(nil, getTxsArgs, getTxsReply)) + require.NoError(service.GetAddressTxs(nil, getTxsArgs, getTxsReply)) require.Len(getTxsReply.TxIDs, 10) require.Equal(getTxsReply.TxIDs, testTxs[:10]) // get the second page getTxsArgs.Cursor = getTxsReply.Cursor getTxsReply = &GetAddressTxsReply{} - require.NoError(env.service.GetAddressTxs(nil, getTxsArgs, getTxsReply)) + require.NoError(service.GetAddressTxs(nil, getTxsArgs, getTxsReply)) require.Len(getTxsReply.TxIDs, 10) require.Equal(getTxsReply.TxIDs, testTxs[10:20]) } @@ -315,11 +300,7 @@ func TestServiceGetAllBalances(t *testing.T) { env := setup(t, &envConfig{ fork: latest, }) - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + service := &Service{vm: env.vm} assetID := ids.GenerateTestID() addr := ids.GenerateTestShortID() @@ -353,7 +334,7 @@ func TestServiceGetAllBalances(t *testing.T) { IncludePartial: true, } reply := &GetAllBalancesReply{} - require.NoError(env.service.GetAllBalances(nil, balanceArgs, reply)) + require.NoError(service.GetAllBalances(nil, balanceArgs, reply)) // The balance should include the UTXO since it is partly owned by [addr] require.Len(reply.Balances, 1) require.Equal(assetID.String(), reply.Balances[0].AssetID) @@ -364,7 +345,7 @@ func TestServiceGetAllBalances(t *testing.T) { JSONAddress: api.JSONAddress{Address: addrStr}, } reply = &GetAllBalancesReply{} - require.NoError(env.service.GetAllBalances(nil, balanceArgs, reply)) + require.NoError(service.GetAllBalances(nil, balanceArgs, reply)) require.Empty(reply.Balances) env.vm.ctx.Lock.Lock() @@ -397,7 +378,7 @@ func TestServiceGetAllBalances(t *testing.T) { IncludePartial: true, } reply = &GetAllBalancesReply{} - require.NoError(env.service.GetAllBalances(nil, balanceArgs, reply)) + require.NoError(service.GetAllBalances(nil, balanceArgs, reply)) // The balance should include the UTXO since it is partly owned by [addr] require.Len(reply.Balances, 1) require.Equal(assetID.String(), reply.Balances[0].AssetID) @@ -408,7 +389,7 @@ func TestServiceGetAllBalances(t *testing.T) { JSONAddress: api.JSONAddress{Address: addrStr}, } reply = &GetAllBalancesReply{} - require.NoError(env.service.GetAllBalances(nil, balanceArgs, reply)) + require.NoError(service.GetAllBalances(nil, balanceArgs, reply)) // The balance should not include the UTXO since it is only partly owned by [addr] require.Empty(reply.Balances) @@ -444,7 +425,7 @@ func TestServiceGetAllBalances(t *testing.T) { IncludePartial: true, } reply = &GetAllBalancesReply{} - require.NoError(env.service.GetAllBalances(nil, balanceArgs, reply)) + require.NoError(service.GetAllBalances(nil, balanceArgs, reply)) // The balance should include the UTXO since it is partly owned by [addr] // The balance should include the UTXO since it is partly owned by [addr] require.Len(reply.Balances, 1) @@ -455,7 +436,7 @@ func TestServiceGetAllBalances(t *testing.T) { JSONAddress: api.JSONAddress{Address: addrStr}, } reply = &GetAllBalancesReply{} - require.NoError(env.service.GetAllBalances(nil, balanceArgs, reply)) + require.NoError(service.GetAllBalances(nil, balanceArgs, reply)) // The balance should not include the UTXO since it is only partly owned by [addr] require.Empty(reply.Balances) @@ -489,7 +470,7 @@ func TestServiceGetAllBalances(t *testing.T) { IncludePartial: true, } reply = &GetAllBalancesReply{} - require.NoError(env.service.GetAllBalances(nil, balanceArgs, reply)) + require.NoError(service.GetAllBalances(nil, balanceArgs, reply)) // The balance should include the UTXO since it is partly owned by [addr] require.Len(reply.Balances, 2) gotAssetIDs := []string{reply.Balances[0].AssetID, reply.Balances[1].AssetID} @@ -504,7 +485,7 @@ func TestServiceGetAllBalances(t *testing.T) { JSONAddress: api.JSONAddress{Address: addrStr}, } reply = &GetAllBalancesReply{} - require.NoError(env.service.GetAllBalances(nil, balanceArgs, reply)) + require.NoError(service.GetAllBalances(nil, balanceArgs, reply)) // The balance should include the UTXO since it is partly owned by [addr] require.Empty(reply.Balances) } @@ -515,18 +496,13 @@ func TestServiceGetTx(t *testing.T) { env := setup(t, &envConfig{ fork: latest, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - txID := env.genesisTx.ID() reply := api.GetTxReply{} - require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ + require.NoError(service.GetTx(nil, &api.GetTxArgs{ TxID: txID, Encoding: formatting.Hex, }, &reply)) @@ -545,18 +521,14 @@ func TestServiceGetTxJSON_BaseTx(t *testing.T) { env := setup(t, &envConfig{ fork: latest, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - newTx := newAvaxBaseTxWithOutputs(t, env.genesisBytes, env.vm.ctx.ChainID, env.vm.TxFee, env.vm.parser) + newTx := newAvaxBaseTxWithOutputs(t, env) issueAndAccept(require, env.vm, env.issuer, newTx) reply := api.GetTxReply{} - require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ + require.NoError(service.GetTx(nil, &api.GetTxArgs{ TxID: newTx.ID(), Encoding: formatting.JSON, }, &reply)) @@ -578,7 +550,19 @@ func TestServiceGetTxJSON_BaseTx(t *testing.T) { "addresses": [ "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" ], - "amount": 49000, + "amount": 1000, + "locktime": 0, + "threshold": 1 + } + }, + { + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "output": { + "addresses": [ + "X-testing1d6kkj0qh4wcmus3tk59npwt3rluc6en72ngurd" + ], + "amount": 48000, "locktime": 0, "threshold": 1 } @@ -630,18 +614,14 @@ func TestServiceGetTxJSON_ExportTx(t *testing.T) { env := setup(t, &envConfig{ fork: latest, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - newTx := newAvaxExportTxWithOutputs(t, env.genesisBytes, env.vm.ctx.ChainID, env.vm.TxFee, env.vm.parser) + newTx := buildTestExportTx(t, env, env.vm.ctx.CChainID) issueAndAccept(require, env.vm, env.issuer, newTx) reply := api.GetTxReply{} - require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ + require.NoError(service.GetTx(nil, &api.GetTxArgs{ TxID: newTx.ID(), Encoding: formatting.JSON, }, &reply)) @@ -654,7 +634,20 @@ func TestServiceGetTxJSON_ExportTx(t *testing.T) { "unsignedTx": { "networkID": 10, "blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID", - "outputs": null, + "outputs": [ + { + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "output": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "amount": 48000, + "locktime": 0, + "threshold": 1 + } + } + ], "inputs": [ { "txID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", @@ -670,7 +663,7 @@ func TestServiceGetTxJSON_ExportTx(t *testing.T) { } ], "memo": "0x", - "destinationChain": "11111111111111111111111111111111LpoYY", + "destinationChain": "2mcwQKiD8VEspmMJpL1dc7okQQ5dDVAWeCBZ7FWBFAbxpv3t7w", "exportedOutputs": [ { "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", @@ -679,7 +672,7 @@ func TestServiceGetTxJSON_ExportTx(t *testing.T) { "addresses": [ "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" ], - "amount": 49000, + "amount": 1000, "locktime": 0, "threshold": 1 } @@ -714,24 +707,65 @@ func TestServiceGetTxJSON_CreateAssetTx(t *testing.T) { require := require.New(t) env := setup(t, &envConfig{ - vmStaticConfig: noFeesTestConfig, + fork: latest, additionalFxs: []*common.Fx{{ ID: propertyfx.ID, Fx: &propertyfx.Fx{}, }}, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm.ctx.ChainID, env.vm.parser) + initialStates := map[uint32][]verify.State{ + 0: { + &nftfx.MintOutput{ + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, + }, + }, &secp256k1fx.MintOutput{ + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, + }, + }, + }, + 1: { + &nftfx.MintOutput{ + GroupID: 1, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, + }, + }, + &nftfx.MintOutput{ + GroupID: 2, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, + }, + }, + }, + 2: { + &propertyfx.MintOutput{ + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, + }, + }, + &propertyfx.MintOutput{ + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, + }, + }, + }, + } + createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env, initialStates) issueAndAccept(require, env.vm, env.issuer, createAssetTx) reply := api.GetTxReply{} - require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ + require.NoError(service.GetTx(nil, &api.GetTxArgs{ TxID: createAssetTx.ID(), Encoding: formatting.JSON, }, &reply)) @@ -745,8 +779,34 @@ func TestServiceGetTxJSON_CreateAssetTx(t *testing.T) { "unsignedTx": { "networkID": 10, "blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID", - "outputs": null, - "inputs": null, + "outputs": [ + { + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "output": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "amount": 49000, + "locktime": 0, + "threshold": 1 + } + } + ], + "inputs": [ + { + "txID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "outputIndex": 2, + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "input": { + "amount": 50000, + "signatureIndices": [ + 0 + ] + } + } + ], "memo": "0x", "name": "Team Rocket", "symbol": "TR", @@ -767,6 +827,7 @@ func TestServiceGetTxJSON_CreateAssetTx(t *testing.T) { "addresses": [ "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" ], + "groupID": 0, "locktime": 0, "threshold": 1 } @@ -816,13 +877,27 @@ func TestServiceGetTxJSON_CreateAssetTx(t *testing.T) { } ] }, - "credentials": null, + "credentials": [ + { + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "credential": { + "signatures": [ + "PLACEHOLDER_SIGNATURE" + ] + } + } + ], "id": "PLACEHOLDER_TX_ID" }` expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", createAssetTx.ID().String(), 1) expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", createAssetTx.Unsigned.(*txs.CreateAssetTx).BlockchainID.String(), 1) + sigStr, err := formatting.Encode(formatting.HexNC, createAssetTx.Creds[0].Credential.(*secp256k1fx.Credential).Sigs[0][:]) + require.NoError(err) + + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 1) + require.Equal(expectedReplyTxString, string(replyTxBytes)) } @@ -830,29 +905,43 @@ func TestServiceGetTxJSON_OperationTxWithNftxMintOp(t *testing.T) { require := require.New(t) env := setup(t, &envConfig{ - vmStaticConfig: noFeesTestConfig, + fork: latest, additionalFxs: []*common.Fx{{ ID: propertyfx.ID, Fx: &propertyfx.Fx{}, }}, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() key := keys[0] - createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm.ctx.ChainID, env.vm.parser) + initialStates := map[uint32][]verify.State{ + 1: { + &nftfx.MintOutput{ + GroupID: 1, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, + }, + }, + &nftfx.MintOutput{ + GroupID: 2, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, + }, + }, + }, + } + createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env, initialStates) issueAndAccept(require, env.vm, env.issuer, createAssetTx) - mintNFTTx := buildOperationTxWithOp(env.vm.ctx.ChainID, buildNFTxMintOp(createAssetTx, key, 2, 1)) - require.NoError(mintNFTTx.SignNFTFx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) + op := buildNFTxMintOp(createAssetTx, key, 1, 1) + mintNFTTx := buildOperationTxWithOps(t, env, op) issueAndAccept(require, env.vm, env.issuer, mintNFTTx) reply := api.GetTxReply{} - require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ + require.NoError(service.GetTx(nil, &api.GetTxArgs{ TxID: mintNFTTx.ID(), Encoding: formatting.JSON, }, &reply)) @@ -866,8 +955,34 @@ func TestServiceGetTxJSON_OperationTxWithNftxMintOp(t *testing.T) { "unsignedTx": { "networkID": 10, "blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID", - "outputs": null, - "inputs": null, + "outputs": [ + { + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "output": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "amount": 48000, + "locktime": 0, + "threshold": 1 + } + } + ], + "inputs": [ + { + "txID": "rSiY2aqcahSU5vyJeMiNBnwtPwfJFxsxskAGbU3HxHvAkrdpy", + "outputIndex": 0, + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "input": { + "amount": 49000, + "signatureIndices": [ + 0 + ] + } + } + ], "memo": "0x", "operations": [ { @@ -875,7 +990,7 @@ func TestServiceGetTxJSON_OperationTxWithNftxMintOp(t *testing.T) { "inputIDs": [ { "txID": "PLACEHOLDER_CREATE_ASSET_TX_ID", - "outputIndex": 2 + "outputIndex": 1 } ], "fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT", @@ -901,6 +1016,14 @@ func TestServiceGetTxJSON_OperationTxWithNftxMintOp(t *testing.T) { ] }, "credentials": [ + { + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "credential": { + "signatures": [ + "PLACEHOLDER_SIGNATURE" + ] + } + }, { "fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT", "credential": { @@ -917,10 +1040,10 @@ func TestServiceGetTxJSON_OperationTxWithNftxMintOp(t *testing.T) { expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", mintNFTTx.ID().String(), 1) expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", mintNFTTx.Unsigned.(*txs.OperationTx).BlockchainID.String(), 1) - sigStr, err := formatting.Encode(formatting.HexNC, mintNFTTx.Creds[0].Credential.(*nftfx.Credential).Sigs[0][:]) + sigStr, err := formatting.Encode(formatting.HexNC, mintNFTTx.Creds[1].Credential.(*nftfx.Credential).Sigs[0][:]) require.NoError(err) - expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 1) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 2) require.Equal(expectedReplyTxString, string(replyTxBytes)) } @@ -929,32 +1052,46 @@ func TestServiceGetTxJSON_OperationTxWithMultipleNftxMintOp(t *testing.T) { require := require.New(t) env := setup(t, &envConfig{ - vmStaticConfig: noFeesTestConfig, + fork: latest, additionalFxs: []*common.Fx{{ ID: propertyfx.ID, Fx: &propertyfx.Fx{}, }}, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() key := keys[0] - createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm.ctx.ChainID, env.vm.parser) + initialStates := map[uint32][]verify.State{ + 0: { + &nftfx.MintOutput{ + GroupID: 0, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, + }, + }, + }, + 1: { + &nftfx.MintOutput{ + GroupID: 1, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, + }, + }, + }, + } + createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env, initialStates) issueAndAccept(require, env.vm, env.issuer, createAssetTx) - mintOp1 := buildNFTxMintOp(createAssetTx, key, 2, 1) - mintOp2 := buildNFTxMintOp(createAssetTx, key, 3, 2) - mintNFTTx := buildOperationTxWithOp(env.vm.ctx.ChainID, mintOp1, mintOp2) - - require.NoError(mintNFTTx.SignNFTFx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}, {key}})) + mintOp1 := buildNFTxMintOp(createAssetTx, key, 1, 0) + mintOp2 := buildNFTxMintOp(createAssetTx, key, 2, 1) + mintNFTTx := buildOperationTxWithOps(t, env, mintOp1, mintOp2) issueAndAccept(require, env.vm, env.issuer, mintNFTTx) reply := api.GetTxReply{} - require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ + require.NoError(service.GetTx(nil, &api.GetTxArgs{ TxID: mintNFTTx.ID(), Encoding: formatting.JSON, }, &reply)) @@ -968,8 +1105,34 @@ func TestServiceGetTxJSON_OperationTxWithMultipleNftxMintOp(t *testing.T) { "unsignedTx": { "networkID": 10, "blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID", - "outputs": null, - "inputs": null, + "outputs": [ + { + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "output": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "amount": 48000, + "locktime": 0, + "threshold": 1 + } + } + ], + "inputs": [ + { + "txID": "BBhSA95iv6ueXc7xrMSka1bByBqcwJxyvMiyjy5H8ccAgxy4P", + "outputIndex": 0, + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "input": { + "amount": 49000, + "signatureIndices": [ + 0 + ] + } + } + ], "memo": "0x", "operations": [ { @@ -977,7 +1140,7 @@ func TestServiceGetTxJSON_OperationTxWithMultipleNftxMintOp(t *testing.T) { "inputIDs": [ { "txID": "PLACEHOLDER_CREATE_ASSET_TX_ID", - "outputIndex": 2 + "outputIndex": 1 } ], "fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT", @@ -987,7 +1150,7 @@ func TestServiceGetTxJSON_OperationTxWithMultipleNftxMintOp(t *testing.T) { 0 ] }, - "groupID": 1, + "groupID": 0, "payload": "0x68656c6c6f", "outputs": [ { @@ -1005,7 +1168,7 @@ func TestServiceGetTxJSON_OperationTxWithMultipleNftxMintOp(t *testing.T) { "inputIDs": [ { "txID": "PLACEHOLDER_CREATE_ASSET_TX_ID", - "outputIndex": 3 + "outputIndex": 2 } ], "fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT", @@ -1015,7 +1178,7 @@ func TestServiceGetTxJSON_OperationTxWithMultipleNftxMintOp(t *testing.T) { 0 ] }, - "groupID": 2, + "groupID": 1, "payload": "0x68656c6c6f", "outputs": [ { @@ -1031,6 +1194,14 @@ func TestServiceGetTxJSON_OperationTxWithMultipleNftxMintOp(t *testing.T) { ] }, "credentials": [ + { + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "credential": { + "signatures": [ + "PLACEHOLDER_SIGNATURE" + ] + } + }, { "fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT", "credential": { @@ -1055,10 +1226,10 @@ func TestServiceGetTxJSON_OperationTxWithMultipleNftxMintOp(t *testing.T) { expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", mintNFTTx.ID().String(), 1) expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", mintNFTTx.Unsigned.(*txs.OperationTx).BlockchainID.String(), 1) - sigStr, err := formatting.Encode(formatting.HexNC, mintNFTTx.Creds[0].Credential.(*nftfx.Credential).Sigs[0][:]) + sigStr, err := formatting.Encode(formatting.HexNC, mintNFTTx.Creds[1].Credential.(*nftfx.Credential).Sigs[0][:]) require.NoError(err) - expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 2) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 3) require.Equal(expectedReplyTxString, string(replyTxBytes)) } @@ -1067,29 +1238,40 @@ func TestServiceGetTxJSON_OperationTxWithSecpMintOp(t *testing.T) { require := require.New(t) env := setup(t, &envConfig{ - vmStaticConfig: noFeesTestConfig, + fork: latest, additionalFxs: []*common.Fx{{ ID: propertyfx.ID, Fx: &propertyfx.Fx{}, }}, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() key := keys[0] - createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm.ctx.ChainID, env.vm.parser) + initialStates := map[uint32][]verify.State{ + 0: { + &nftfx.MintOutput{ + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, + }, + }, &secp256k1fx.MintOutput{ + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, + }, + }, + }, + } + createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env, initialStates) issueAndAccept(require, env.vm, env.issuer, createAssetTx) - mintSecpOpTx := buildOperationTxWithOp(env.vm.ctx.ChainID, buildSecpMintOp(createAssetTx, key, 0)) - require.NoError(mintSecpOpTx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) + op := buildSecpMintOp(createAssetTx, key, 1) + mintSecpOpTx := buildOperationTxWithOps(t, env, op) issueAndAccept(require, env.vm, env.issuer, mintSecpOpTx) reply := api.GetTxReply{} - require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ + require.NoError(service.GetTx(nil, &api.GetTxArgs{ TxID: mintSecpOpTx.ID(), Encoding: formatting.JSON, }, &reply)) @@ -1103,8 +1285,34 @@ func TestServiceGetTxJSON_OperationTxWithSecpMintOp(t *testing.T) { "unsignedTx": { "networkID": 10, "blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID", - "outputs": null, - "inputs": null, + "outputs": [ + { + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "output": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "amount": 48000, + "locktime": 0, + "threshold": 1 + } + } + ], + "inputs": [ + { + "txID": "2YhAg3XUdub5syHHePZG7q3yFjKAy7ahsvQDxq5SMrYbN1s5Gn", + "outputIndex": 0, + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "input": { + "amount": 49000, + "signatureIndices": [ + 0 + ] + } + } + ], "memo": "0x", "operations": [ { @@ -1112,7 +1320,7 @@ func TestServiceGetTxJSON_OperationTxWithSecpMintOp(t *testing.T) { "inputIDs": [ { "txID": "PLACEHOLDER_CREATE_ASSET_TX_ID", - "outputIndex": 0 + "outputIndex": 1 } ], "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", @@ -1142,6 +1350,14 @@ func TestServiceGetTxJSON_OperationTxWithSecpMintOp(t *testing.T) { ] }, "credentials": [ + { + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "credential": { + "signatures": [ + "PLACEHOLDER_SIGNATURE" + ] + } + }, { "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", "credential": { @@ -1161,7 +1377,7 @@ func TestServiceGetTxJSON_OperationTxWithSecpMintOp(t *testing.T) { sigStr, err := formatting.Encode(formatting.HexNC, mintSecpOpTx.Creds[0].Credential.(*secp256k1fx.Credential).Sigs[0][:]) require.NoError(err) - expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 1) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 2) require.Equal(expectedReplyTxString, string(replyTxBytes)) } @@ -1170,32 +1386,44 @@ func TestServiceGetTxJSON_OperationTxWithMultipleSecpMintOp(t *testing.T) { require := require.New(t) env := setup(t, &envConfig{ - vmStaticConfig: noFeesTestConfig, + fork: durango, additionalFxs: []*common.Fx{{ ID: propertyfx.ID, Fx: &propertyfx.Fx{}, }}, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() key := keys[0] - createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm.ctx.ChainID, env.vm.parser) + initialStates := map[uint32][]verify.State{ + 0: { + &secp256k1fx.MintOutput{ + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{key.PublicKey().Address()}, + }, + }, + }, + 1: { + &secp256k1fx.MintOutput{ + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{key.PublicKey().Address()}, + }, + }, + }, + } + createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env, initialStates) issueAndAccept(require, env.vm, env.issuer, createAssetTx) - op1 := buildSecpMintOp(createAssetTx, key, 0) - op2 := buildSecpMintOp(createAssetTx, key, 1) - mintSecpOpTx := buildOperationTxWithOp(env.vm.ctx.ChainID, op1, op2) - - require.NoError(mintSecpOpTx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}, {key}})) + op1 := buildSecpMintOp(createAssetTx, key, 1) + op2 := buildSecpMintOp(createAssetTx, key, 2) + mintSecpOpTx := buildOperationTxWithOps(t, env, op1, op2) issueAndAccept(require, env.vm, env.issuer, mintSecpOpTx) reply := api.GetTxReply{} - require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ + require.NoError(service.GetTx(nil, &api.GetTxArgs{ TxID: mintSecpOpTx.ID(), Encoding: formatting.JSON, }, &reply)) @@ -1209,8 +1437,34 @@ func TestServiceGetTxJSON_OperationTxWithMultipleSecpMintOp(t *testing.T) { "unsignedTx": { "networkID": 10, "blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID", - "outputs": null, - "inputs": null, + "outputs": [ + { + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "output": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "amount": 48000, + "locktime": 0, + "threshold": 1 + } + } + ], + "inputs": [ + { + "txID": "2vxorPLUw5sneb7Mdhhjuws3H5AqaDp1V8ETz6fEuzvn835rVX", + "outputIndex": 0, + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "input": { + "amount": 49000, + "signatureIndices": [ + 0 + ] + } + } + ], "memo": "0x", "operations": [ { @@ -1218,7 +1472,7 @@ func TestServiceGetTxJSON_OperationTxWithMultipleSecpMintOp(t *testing.T) { "inputIDs": [ { "txID": "PLACEHOLDER_CREATE_ASSET_TX_ID", - "outputIndex": 0 + "outputIndex": 1 } ], "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", @@ -1250,7 +1504,7 @@ func TestServiceGetTxJSON_OperationTxWithMultipleSecpMintOp(t *testing.T) { "inputIDs": [ { "txID": "PLACEHOLDER_CREATE_ASSET_TX_ID", - "outputIndex": 1 + "outputIndex": 2 } ], "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", @@ -1288,6 +1542,14 @@ func TestServiceGetTxJSON_OperationTxWithMultipleSecpMintOp(t *testing.T) { ] } }, + { + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "credential": { + "signatures": [ + "PLACEHOLDER_SIGNATURE" + ] + } + }, { "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", "credential": { @@ -1307,7 +1569,7 @@ func TestServiceGetTxJSON_OperationTxWithMultipleSecpMintOp(t *testing.T) { sigStr, err := formatting.Encode(formatting.HexNC, mintSecpOpTx.Creds[0].Credential.(*secp256k1fx.Credential).Sigs[0][:]) require.NoError(err) - expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 2) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 3) require.Equal(expectedReplyTxString, string(replyTxBytes)) } @@ -1316,29 +1578,35 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOp(t *testing.T) { require := require.New(t) env := setup(t, &envConfig{ - vmStaticConfig: noFeesTestConfig, + fork: latest, additionalFxs: []*common.Fx{{ ID: propertyfx.ID, Fx: &propertyfx.Fx{}, }}, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() key := keys[0] - createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm.ctx.ChainID, env.vm.parser) + initialStates := map[uint32][]verify.State{ + 2: { + &propertyfx.MintOutput{ + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, + }, + }, + }, + } + createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env, initialStates) issueAndAccept(require, env.vm, env.issuer, createAssetTx) - mintPropertyFxOpTx := buildOperationTxWithOp(env.vm.ctx.ChainID, buildPropertyFxMintOp(createAssetTx, key, 4)) - require.NoError(mintPropertyFxOpTx.SignPropertyFx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) + op := buildPropertyFxMintOp(createAssetTx, key, 1) + mintPropertyFxOpTx := buildOperationTxWithOps(t, env, op) issueAndAccept(require, env.vm, env.issuer, mintPropertyFxOpTx) reply := api.GetTxReply{} - require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ + require.NoError(service.GetTx(nil, &api.GetTxArgs{ TxID: mintPropertyFxOpTx.ID(), Encoding: formatting.JSON, }, &reply)) @@ -1352,8 +1620,34 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOp(t *testing.T) { "unsignedTx": { "networkID": 10, "blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID", - "outputs": null, - "inputs": null, + "outputs": [ + { + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "output": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "amount": 48000, + "locktime": 0, + "threshold": 1 + } + } + ], + "inputs": [ + { + "txID": "nNUGBjszswU3ZmhCb8hBNWmg335UZqGWmNrYTAGyMF4bFpMXm", + "outputIndex": 0, + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "input": { + "amount": 49000, + "signatureIndices": [ + 0 + ] + } + } + ], "memo": "0x", "operations": [ { @@ -1361,7 +1655,7 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOp(t *testing.T) { "inputIDs": [ { "txID": "PLACEHOLDER_CREATE_ASSET_TX_ID", - "outputIndex": 4 + "outputIndex": 1 } ], "fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy", @@ -1388,6 +1682,14 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOp(t *testing.T) { ] }, "credentials": [ + { + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "credential": { + "signatures": [ + "PLACEHOLDER_SIGNATURE" + ] + } + }, { "fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy", "credential": { @@ -1404,10 +1706,10 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOp(t *testing.T) { expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", mintPropertyFxOpTx.ID().String(), 1) expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", mintPropertyFxOpTx.Unsigned.(*txs.OperationTx).BlockchainID.String(), 1) - sigStr, err := formatting.Encode(formatting.HexNC, mintPropertyFxOpTx.Creds[0].Credential.(*propertyfx.Credential).Sigs[0][:]) + sigStr, err := formatting.Encode(formatting.HexNC, mintPropertyFxOpTx.Creds[1].Credential.(*propertyfx.Credential).Sigs[0][:]) require.NoError(err) - expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 1) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 2) require.Equal(expectedReplyTxString, string(replyTxBytes)) } @@ -1416,32 +1718,42 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOpMultiple(t *testing.T) require := require.New(t) env := setup(t, &envConfig{ - vmStaticConfig: noFeesTestConfig, + fork: latest, additionalFxs: []*common.Fx{{ ID: propertyfx.ID, Fx: &propertyfx.Fx{}, }}, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() key := keys[0] - createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm.ctx.ChainID, env.vm.parser) + initialStates := map[uint32][]verify.State{ + 2: { + &propertyfx.MintOutput{ + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, + }, + }, + &propertyfx.MintOutput{ + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, + }, + }, + }, + } + createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env, initialStates) issueAndAccept(require, env.vm, env.issuer, createAssetTx) - op1 := buildPropertyFxMintOp(createAssetTx, key, 4) - op2 := buildPropertyFxMintOp(createAssetTx, key, 5) - mintPropertyFxOpTx := buildOperationTxWithOp(env.vm.ctx.ChainID, op1, op2) - - require.NoError(mintPropertyFxOpTx.SignPropertyFx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}, {key}})) + op1 := buildPropertyFxMintOp(createAssetTx, key, 1) + op2 := buildPropertyFxMintOp(createAssetTx, key, 2) + mintPropertyFxOpTx := buildOperationTxWithOps(t, env, op1, op2) issueAndAccept(require, env.vm, env.issuer, mintPropertyFxOpTx) reply := api.GetTxReply{} - require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ + require.NoError(service.GetTx(nil, &api.GetTxArgs{ TxID: mintPropertyFxOpTx.ID(), Encoding: formatting.JSON, }, &reply)) @@ -1455,8 +1767,34 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOpMultiple(t *testing.T) "unsignedTx": { "networkID": 10, "blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID", - "outputs": null, - "inputs": null, + "outputs": [ + { + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "output": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "amount": 48000, + "locktime": 0, + "threshold": 1 + } + } + ], + "inputs": [ + { + "txID": "2NV5AGoQQHVRY6VkT8sht8bhZDHR7uwta7fk7JwAZpacqMRWCa", + "outputIndex": 0, + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "input": { + "amount": 49000, + "signatureIndices": [ + 0 + ] + } + } + ], "memo": "0x", "operations": [ { @@ -1464,7 +1802,7 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOpMultiple(t *testing.T) "inputIDs": [ { "txID": "PLACEHOLDER_CREATE_ASSET_TX_ID", - "outputIndex": 4 + "outputIndex": 1 } ], "fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy", @@ -1493,7 +1831,7 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOpMultiple(t *testing.T) "inputIDs": [ { "txID": "PLACEHOLDER_CREATE_ASSET_TX_ID", - "outputIndex": 5 + "outputIndex": 2 } ], "fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy", @@ -1520,6 +1858,14 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOpMultiple(t *testing.T) ] }, "credentials": [ + { + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "credential": { + "signatures": [ + "PLACEHOLDER_SIGNATURE" + ] + } + }, { "fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy", "credential": { @@ -1544,169 +1890,76 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOpMultiple(t *testing.T) expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", mintPropertyFxOpTx.ID().String(), 1) expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", mintPropertyFxOpTx.Unsigned.(*txs.OperationTx).BlockchainID.String(), 1) - sigStr, err := formatting.Encode(formatting.HexNC, mintPropertyFxOpTx.Creds[0].Credential.(*propertyfx.Credential).Sigs[0][:]) + sigStr, err := formatting.Encode(formatting.HexNC, mintPropertyFxOpTx.Creds[1].Credential.(*propertyfx.Credential).Sigs[0][:]) require.NoError(err) - expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 2) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 3) require.Equal(expectedReplyTxString, string(replyTxBytes)) } -func newAvaxBaseTxWithOutputs(t *testing.T, genesisBytes []byte, chainID ids.ID, fee uint64, parser txs.Parser) *txs.Tx { - avaxTx := getCreateTxFromGenesisTest(t, genesisBytes, "AVAX") - key := keys[0] - tx := buildBaseTx(avaxTx, chainID, fee, key) - require.NoError(t, tx.SignSECP256K1Fx(parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) - return tx -} - -func newAvaxExportTxWithOutputs(t *testing.T, genesisBytes []byte, chainID ids.ID, fee uint64, parser txs.Parser) *txs.Tx { - avaxTx := getCreateTxFromGenesisTest(t, genesisBytes, "AVAX") - key := keys[0] - tx := buildExportTx(avaxTx, chainID, fee, key) - require.NoError(t, tx.SignSECP256K1Fx(parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) - return tx -} - -func newAvaxCreateAssetTxWithOutputs(t *testing.T, chainID ids.ID, parser txs.Parser) *txs.Tx { - key := keys[0] - tx := buildCreateAssetTx(chainID, key) - require.NoError(t, tx.Initialize(parser.Codec())) - return tx -} - -func buildBaseTx(avaxTx *txs.Tx, chainID ids.ID, fee uint64, key *secp256k1.PrivateKey) *txs.Tx { - return &txs.Tx{Unsigned: &txs.BaseTx{ - BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: chainID, - Memo: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Ins: []*avax.TransferableInput{{ - UTXOID: avax.UTXOID{ - TxID: avaxTx.ID(), - OutputIndex: 2, - }, - Asset: avax.Asset{ID: avaxTx.ID()}, - In: &secp256k1fx.TransferInput{ - Amt: startBalance, - Input: secp256k1fx.Input{ - SigIndices: []uint32{ - 0, - }, - }, - }, - }}, - Outs: []*avax.TransferableOutput{{ - Asset: avax.Asset{ID: avaxTx.ID()}, - Out: &secp256k1fx.TransferOutput{ - Amt: startBalance - fee, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{key.PublicKey().Address()}, - }, - }, - }}, - }, - }} -} - -func buildExportTx(avaxTx *txs.Tx, chainID ids.ID, fee uint64, key *secp256k1.PrivateKey) *txs.Tx { - return &txs.Tx{Unsigned: &txs.ExportTx{ - BaseTx: txs.BaseTx{ - BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: chainID, - Ins: []*avax.TransferableInput{{ - UTXOID: avax.UTXOID{ - TxID: avaxTx.ID(), - OutputIndex: 2, - }, - Asset: avax.Asset{ID: avaxTx.ID()}, - In: &secp256k1fx.TransferInput{ - Amt: startBalance, - Input: secp256k1fx.Input{SigIndices: []uint32{0}}, - }, - }}, - }, - }, - DestinationChain: constants.PlatformChainID, - ExportedOuts: []*avax.TransferableOutput{{ - Asset: avax.Asset{ID: avaxTx.ID()}, +func newAvaxBaseTxWithOutputs(t *testing.T, env *environment) *txs.Tx { + var ( + memo = []byte{1, 2, 3, 4, 5, 6, 7, 8} + key = keys[0] + changeKey = keys[1] + kc = secp256k1fx.NewKeychain(key) + ) + + tx, err := env.txBuilder.BaseTx( + []*avax.TransferableOutput{{ + Asset: avax.Asset{ID: env.vm.feeAssetID}, Out: &secp256k1fx.TransferOutput{ - Amt: startBalance - fee, + Amt: units.MicroAvax, OutputOwners: secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{key.PublicKey().Address()}, }, }, }}, - }} + memo, + kc, + changeKey.PublicKey().Address(), + ) + require.NoError(t, err) + return tx } -func buildCreateAssetTx(chainID ids.ID, key *secp256k1.PrivateKey) *txs.Tx { - return &txs.Tx{Unsigned: &txs.CreateAssetTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: chainID, - }}, - Name: "Team Rocket", - Symbol: "TR", - Denomination: 0, - States: []*txs.InitialState{ - { - FxIndex: 0, - Outs: []verify.State{ - &secp256k1fx.MintOutput{ - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{key.PublicKey().Address()}, - }, - }, &secp256k1fx.MintOutput{ - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{key.PublicKey().Address()}, - }, - }, - }, - }, - { - FxIndex: 1, - Outs: []verify.State{ - &nftfx.MintOutput{ - GroupID: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{key.PublicKey().Address()}, - }, - }, - &nftfx.MintOutput{ - GroupID: 2, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{key.PublicKey().Address()}, - }, - }, - }, - }, - { - FxIndex: 2, - Outs: []verify.State{ - &propertyfx.MintOutput{ - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, - }, - &propertyfx.MintOutput{ - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, - }, - }, - }, - }, - }} +func newAvaxCreateAssetTxWithOutputs(t *testing.T, env *environment, initialStates map[uint32][]verify.State) *txs.Tx { + var ( + key = keys[0] + kc = secp256k1fx.NewKeychain(key) + ) + + tx, err := env.txBuilder.CreateAssetTx( + "Team Rocket", // name + "TR", // symbol + 0, // denomination + initialStates, + kc, + key.Address(), + ) + require.NoError(t, err) + return tx +} + +func buildTestExportTx(t *testing.T, env *environment, chainID ids.ID) *txs.Tx { + var ( + key = keys[0] + kc = secp256k1fx.NewKeychain(key) + to = key.PublicKey().Address() + ) + + tx, err := env.txBuilder.ExportTx( + chainID, + to, + env.vm.feeAssetID, + units.MicroAvax, + kc, + key.Address(), + ) + require.NoError(t, err) + return tx } func buildNFTxMintOp(createAssetTx *txs.Tx, key *secp256k1.PrivateKey, outputIndex, groupID uint32) *txs.Operation { @@ -1782,14 +2035,19 @@ func buildSecpMintOp(createAssetTx *txs.Tx, key *secp256k1.PrivateKey, outputInd } } -func buildOperationTxWithOp(chainID ids.ID, op ...*txs.Operation) *txs.Tx { - return &txs.Tx{Unsigned: &txs.OperationTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: chainID, - }}, - Ops: op, - }} +func buildOperationTxWithOps(t *testing.T, env *environment, op ...*txs.Operation) *txs.Tx { + var ( + key = keys[0] + kc = secp256k1fx.NewKeychain(key) + ) + + tx, err := env.txBuilder.Operation( + op, + kc, + key.Address(), + ) + require.NoError(t, err) + return tx } func TestServiceGetNilTx(t *testing.T) { @@ -1798,16 +2056,11 @@ func TestServiceGetNilTx(t *testing.T) { env := setup(t, &envConfig{ fork: latest, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - reply := api.GetTxReply{} - err := env.service.GetTx(nil, &api.GetTxArgs{}, &reply) + err := service.GetTx(nil, &api.GetTxArgs{}, &reply) require.ErrorIs(err, errNilTxID) } @@ -1817,16 +2070,11 @@ func TestServiceGetUnknownTx(t *testing.T) { env := setup(t, &envConfig{ fork: latest, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - reply := api.GetTxReply{} - err := env.service.GetTx(nil, &api.GetTxArgs{TxID: ids.GenerateTestID()}, &reply) + err := service.GetTx(nil, &api.GetTxArgs{TxID: ids.GenerateTestID()}, &reply) require.ErrorIs(err, database.ErrNotFound) } @@ -1834,11 +2082,8 @@ func TestServiceGetUTXOs(t *testing.T) { env := setup(t, &envConfig{ fork: latest, }) - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(t, env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + service := &Service{vm: env.vm} + env.vm.ctx.Lock.Unlock() rawAddr := ids.GenerateTestShortID() rawEmptyAddr := ids.GenerateTestShortID() @@ -1910,8 +2155,6 @@ func TestServiceGetUTXOs(t *testing.T) { xEmptyAddr, err := env.vm.FormatLocalAddress(rawEmptyAddr) require.NoError(t, err) - env.vm.ctx.Lock.Unlock() - tests := []struct { label string count int @@ -2075,7 +2318,7 @@ func TestServiceGetUTXOs(t *testing.T) { t.Run(test.label, func(t *testing.T) { require := require.New(t) reply := &api.GetUTXOsReply{} - err := env.service.GetUTXOs(nil, test.args, reply) + err := service.GetUTXOs(nil, test.args, reply) require.ErrorIs(err, test.expectedErr) if test.expectedErr != nil { return @@ -2091,18 +2334,13 @@ func TestGetAssetDescription(t *testing.T) { env := setup(t, &envConfig{ fork: latest, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - avaxAssetID := env.genesisTx.ID() reply := GetAssetDescriptionReply{} - require.NoError(env.service.GetAssetDescription(nil, &GetAssetDescriptionArgs{ + require.NoError(service.GetAssetDescription(nil, &GetAssetDescriptionArgs{ AssetID: avaxAssetID.String(), }, &reply)) @@ -2116,20 +2354,15 @@ func TestGetBalance(t *testing.T) { env := setup(t, &envConfig{ fork: latest, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - avaxAssetID := env.genesisTx.ID() reply := GetBalanceReply{} addrStr, err := env.vm.FormatLocalAddress(keys[0].PublicKey().Address()) require.NoError(err) - require.NoError(env.service.GetBalance(nil, &GetBalanceArgs{ + require.NoError(service.GetBalance(nil, &GetBalanceArgs{ Address: addrStr, AssetID: avaxAssetID.String(), }, &reply)) @@ -2150,14 +2383,9 @@ func TestCreateFixedCapAsset(t *testing.T) { initialKeys: keys, }}, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - reply := AssetIDChangeAddr{} addrStr, err := env.vm.FormatLocalAddress(keys[0].PublicKey().Address()) require.NoError(err) @@ -2166,7 +2394,7 @@ func TestCreateFixedCapAsset(t *testing.T) { require.NoError(err) _, fromAddrsStr := sampleAddrs(t, env.vm.AddressManager, addrs) - require.NoError(env.service.CreateFixedCapAsset(nil, &CreateAssetArgs{ + require.NoError(service.CreateFixedCapAsset(nil, &CreateAssetArgs{ JSONSpendHeader: api.JSONSpendHeader{ UserPass: api.UserPass{ Username: username, @@ -2201,21 +2429,16 @@ func TestCreateVariableCapAsset(t *testing.T) { initialKeys: keys, }}, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - reply := AssetIDChangeAddr{} minterAddrStr, err := env.vm.FormatLocalAddress(keys[0].PublicKey().Address()) require.NoError(err) _, fromAddrsStr := sampleAddrs(t, env.vm.AddressManager, addrs) changeAddrStr := fromAddrsStr[0] - require.NoError(env.service.CreateVariableCapAsset(nil, &CreateAssetArgs{ + require.NoError(service.CreateVariableCapAsset(nil, &CreateAssetArgs{ JSONSpendHeader: api.JSONSpendHeader{ UserPass: api.UserPass{ Username: username, @@ -2254,7 +2477,7 @@ func TestCreateVariableCapAsset(t *testing.T) { To: minterAddrStr, // Send newly minted tokens to this address } mintReply := &api.JSONTxIDChangeAddr{} - require.NoError(env.service.Mint(nil, mintArgs, mintReply)) + require.NoError(service.Mint(nil, mintArgs, mintReply)) require.Equal(changeAddrStr, mintReply.ChangeAddr) buildAndAccept(require, env.vm, env.issuer, mintReply.TxID) @@ -2275,7 +2498,7 @@ func TestCreateVariableCapAsset(t *testing.T) { }, } sendReply := &api.JSONTxIDChangeAddr{} - require.NoError(env.service.Send(nil, sendArgs, sendReply)) + require.NoError(service.Send(nil, sendArgs, sendReply)) require.Equal(changeAddrStr, sendReply.ChangeAddr) }) } @@ -2294,14 +2517,9 @@ func TestNFTWorkflow(t *testing.T) { initialKeys: keys, }}, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - fromAddrs, fromAddrsStr := sampleAddrs(t, env.vm.AddressManager, addrs) // Test minting of the created variable cap asset @@ -2329,7 +2547,7 @@ func TestNFTWorkflow(t *testing.T) { }, } createReply := &AssetIDChangeAddr{} - require.NoError(env.service.CreateNFTAsset(nil, createArgs, createReply)) + require.NoError(service.CreateNFTAsset(nil, createArgs, createReply)) require.Equal(fromAddrsStr[0], createReply.ChangeAddr) buildAndAccept(require, env.vm, env.issuer, createReply.AssetID) @@ -2342,7 +2560,7 @@ func TestNFTWorkflow(t *testing.T) { require.NoError(err) reply := &GetBalanceReply{} - require.NoError(env.service.GetBalance(nil, + require.NoError(service.GetBalance(nil, &GetBalanceArgs{ Address: addrStr, AssetID: env.vm.feeAssetID.String(), @@ -2380,7 +2598,7 @@ func TestNFTWorkflow(t *testing.T) { } mintReply := &api.JSONTxIDChangeAddr{} - require.NoError(env.service.MintNFT(nil, mintArgs, mintReply)) + require.NoError(service.MintNFT(nil, mintArgs, mintReply)) require.Equal(fromAddrsStr[0], createReply.ChangeAddr) // Accept the transaction so that we can send the newly minted NFT @@ -2400,7 +2618,7 @@ func TestNFTWorkflow(t *testing.T) { To: addrStr, } sendReply := &api.JSONTxIDChangeAddr{} - require.NoError(env.service.SendNFT(nil, sendArgs, sendReply)) + require.NoError(service.SendNFT(nil, sendArgs, sendReply)) require.Equal(fromAddrsStr[0], sendReply.ChangeAddr) }) } @@ -2415,14 +2633,9 @@ func TestImportExportKey(t *testing.T) { password: password, }}, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - sk, err := secp256k1.NewPrivateKey() require.NoError(err) @@ -2434,7 +2647,7 @@ func TestImportExportKey(t *testing.T) { PrivateKey: sk, } importReply := &api.JSONAddress{} - require.NoError(env.service.ImportKey(nil, importArgs, importReply)) + require.NoError(service.ImportKey(nil, importArgs, importReply)) addrStr, err := env.vm.FormatLocalAddress(sk.PublicKey().Address()) require.NoError(err) @@ -2446,7 +2659,7 @@ func TestImportExportKey(t *testing.T) { Address: addrStr, } exportReply := &ExportKeyReply{} - require.NoError(env.service.ExportKey(nil, exportArgs, exportReply)) + require.NoError(service.ExportKey(nil, exportArgs, exportReply)) require.Equal(sk.Bytes(), exportReply.PrivateKey.Bytes()) } @@ -2459,14 +2672,9 @@ func TestImportAVMKeyNoDuplicates(t *testing.T) { password: password, }}, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - sk, err := secp256k1.NewPrivateKey() require.NoError(err) args := ImportKeyArgs{ @@ -2477,7 +2685,7 @@ func TestImportAVMKeyNoDuplicates(t *testing.T) { PrivateKey: sk, } reply := api.JSONAddress{} - require.NoError(env.service.ImportKey(nil, &args, &reply)) + require.NoError(service.ImportKey(nil, &args, &reply)) expectedAddress, err := env.vm.FormatLocalAddress(sk.PublicKey().Address()) require.NoError(err) @@ -2485,7 +2693,7 @@ func TestImportAVMKeyNoDuplicates(t *testing.T) { require.Equal(expectedAddress, reply.Address) reply2 := api.JSONAddress{} - require.NoError(env.service.ImportKey(nil, &args, &reply2)) + require.NoError(service.ImportKey(nil, &args, &reply2)) require.Equal(expectedAddress, reply2.Address) @@ -2494,7 +2702,7 @@ func TestImportAVMKeyNoDuplicates(t *testing.T) { Password: password, } addrsReply := api.JSONAddresses{} - require.NoError(env.service.ListAddresses(nil, &addrsArgs, &addrsReply)) + require.NoError(service.ListAddresses(nil, &addrsArgs, &addrsReply)) require.Len(addrsReply.Addresses, 1) require.Equal(expectedAddress, addrsReply.Addresses[0]) @@ -2510,14 +2718,9 @@ func TestSend(t *testing.T) { initialKeys: keys, }}, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - assetID := env.genesisTx.ID() addr := keys[0].PublicKey().Address() @@ -2543,7 +2746,7 @@ func TestSend(t *testing.T) { }, } reply := &api.JSONTxIDChangeAddr{} - require.NoError(env.service.Send(nil, args, reply)) + require.NoError(service.Send(nil, args, reply)) require.Equal(changeAddrStr, reply.ChangeAddr) buildAndAccept(require, env.vm, env.issuer, reply.TxID) @@ -2561,15 +2764,13 @@ func TestSendMultiple(t *testing.T) { password: password, initialKeys: keys, }}, + vmStaticConfig: &config.Config{ + EUpgradeTime: mockable.MaxTime, + }, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - assetID := env.genesisTx.ID() addr := keys[0].PublicKey().Address() @@ -2602,7 +2803,7 @@ func TestSendMultiple(t *testing.T) { }, } reply := &api.JSONTxIDChangeAddr{} - require.NoError(env.service.SendMultiple(nil, args, reply)) + require.NoError(service.SendMultiple(nil, args, reply)) require.Equal(changeAddrStr, reply.ChangeAddr) buildAndAccept(require, env.vm, env.issuer, reply.TxID) @@ -2619,21 +2820,16 @@ func TestCreateAndListAddresses(t *testing.T) { password: password, }}, }) + service := &Service{vm: env.vm} env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - createArgs := &api.UserPass{ Username: username, Password: password, } createReply := &api.JSONAddress{} - require.NoError(env.service.CreateAddress(nil, createArgs, createReply)) + require.NoError(service.CreateAddress(nil, createArgs, createReply)) newAddr := createReply.Address @@ -2643,7 +2839,7 @@ func TestCreateAndListAddresses(t *testing.T) { } listReply := &api.JSONAddresses{} - require.NoError(env.service.ListAddresses(nil, listArgs, listReply)) + require.NoError(service.ListAddresses(nil, listArgs, listReply)) require.Contains(listReply.Addresses, newAddr) } @@ -2660,12 +2856,9 @@ func TestImport(t *testing.T) { initialKeys: keys, }}, }) + service := &Service{vm: env.vm} + env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() assetID := env.genesisTx.ID() addr0 := keys[0].PublicKey().Address() @@ -2697,8 +2890,6 @@ func TestImport(t *testing.T) { }, })) - env.vm.ctx.Lock.Unlock() - addrStr, err := env.vm.FormatLocalAddress(keys[0].PublicKey().Address()) require.NoError(err) args := &ImportArgs{ @@ -2710,7 +2901,7 @@ func TestImport(t *testing.T) { To: addrStr, } reply := &api.JSONTxID{} - require.NoError(env.service.Import(nil, args, reply)) + require.NoError(service.Import(nil, args, reply)) }) } } diff --git a/vms/avm/state_test.go b/vms/avm/state_test.go index e71acf1251cb..35744fdc63e8 100644 --- a/vms/avm/state_test.go +++ b/vms/avm/state_test.go @@ -4,7 +4,6 @@ package avm import ( - "context" "math" "testing" @@ -35,10 +34,7 @@ func TestSetsAndGets(t *testing.T) { }, }}, }) - defer func() { - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + defer env.vm.ctx.Lock.Unlock() utxo := &avax.UTXO{ UTXOID: avax.UTXOID{ @@ -98,10 +94,7 @@ func TestFundingNoAddresses(t *testing.T) { }, }}, }) - defer func() { - require.NoError(t, env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + defer env.vm.ctx.Lock.Unlock() utxo := &avax.UTXO{ UTXOID: avax.UTXOID{ @@ -131,10 +124,7 @@ func TestFundingAddresses(t *testing.T) { }, }}, }) - defer func() { - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + defer env.vm.ctx.Lock.Unlock() utxo := &avax.UTXO{ UTXOID: avax.UTXOID{ diff --git a/vms/avm/txs/txstest/builder.go b/vms/avm/txs/txstest/builder.go new file mode 100644 index 000000000000..c52e56cdb5e7 --- /dev/null +++ b/vms/avm/txs/txstest/builder.go @@ -0,0 +1,231 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package txstest + +import ( + "context" + "fmt" + + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/vms/avm/config" + "github.com/ava-labs/avalanchego/vms/avm/state" + "github.com/ava-labs/avalanchego/vms/avm/txs" + "github.com/ava-labs/avalanchego/vms/components/avax" + "github.com/ava-labs/avalanchego/vms/components/verify" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + "github.com/ava-labs/avalanchego/wallet/chain/x/builder" + "github.com/ava-labs/avalanchego/wallet/chain/x/signer" + "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" +) + +type Builder struct { + utxos *utxos + ctx *builder.Context +} + +func New( + codec codec.Manager, + ctx *snow.Context, + cfg *config.Config, + feeAssetID ids.ID, + state state.State, +) *Builder { + utxos := newUTXOs(ctx, state, ctx.SharedMemory, codec) + return &Builder{ + utxos: utxos, + ctx: newContext(ctx, cfg, feeAssetID), + } +} + +func (b *Builder) CreateAssetTx( + name, symbol string, + denomination byte, + initialStates map[uint32][]verify.State, + kc *secp256k1fx.Keychain, + changeAddr ids.ShortID, +) (*txs.Tx, error) { + xBuilder, xSigner := b.builders(kc) + + utx, err := xBuilder.NewCreateAssetTx( + name, + symbol, + denomination, + initialStates, + common.WithChangeOwner(&secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{changeAddr}, + }), + ) + if err != nil { + return nil, fmt.Errorf("failed building base tx: %w", err) + } + + return signer.SignUnsigned(context.Background(), xSigner, utx) +} + +func (b *Builder) BaseTx( + outs []*avax.TransferableOutput, + memo []byte, + kc *secp256k1fx.Keychain, + changeAddr ids.ShortID, +) (*txs.Tx, error) { + xBuilder, xSigner := b.builders(kc) + + utx, err := xBuilder.NewBaseTx( + outs, + common.WithChangeOwner(&secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{changeAddr}, + }), + common.WithMemo(memo), + ) + if err != nil { + return nil, fmt.Errorf("failed building base tx: %w", err) + } + + return signer.SignUnsigned(context.Background(), xSigner, utx) +} + +func (b *Builder) MintNFT( + assetID ids.ID, + payload []byte, + owners []*secp256k1fx.OutputOwners, + kc *secp256k1fx.Keychain, + changeAddr ids.ShortID, +) (*txs.Tx, error) { + xBuilder, xSigner := b.builders(kc) + + utx, err := xBuilder.NewOperationTxMintNFT( + assetID, + payload, + owners, + common.WithChangeOwner(&secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{changeAddr}, + }), + ) + if err != nil { + return nil, fmt.Errorf("failed minting NFTs: %w", err) + } + + return signer.SignUnsigned(context.Background(), xSigner, utx) +} + +func (b *Builder) MintFTs( + outputs map[ids.ID]*secp256k1fx.TransferOutput, + kc *secp256k1fx.Keychain, + changeAddr ids.ShortID, +) (*txs.Tx, error) { + xBuilder, xSigner := b.builders(kc) + + utx, err := xBuilder.NewOperationTxMintFT( + outputs, + common.WithChangeOwner(&secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{changeAddr}, + }), + ) + if err != nil { + return nil, fmt.Errorf("failed minting FTs: %w", err) + } + + return signer.SignUnsigned(context.Background(), xSigner, utx) +} + +func (b *Builder) Operation( + ops []*txs.Operation, + kc *secp256k1fx.Keychain, + changeAddr ids.ShortID, +) (*txs.Tx, error) { + xBuilder, xSigner := b.builders(kc) + + utx, err := xBuilder.NewOperationTx( + ops, + common.WithChangeOwner(&secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{changeAddr}, + }), + ) + if err != nil { + return nil, fmt.Errorf("failed building operation tx: %w", err) + } + + return signer.SignUnsigned(context.Background(), xSigner, utx) +} + +func (b *Builder) ImportTx( + sourceChain ids.ID, + to ids.ShortID, + kc *secp256k1fx.Keychain, +) (*txs.Tx, error) { + xBuilder, xSigner := b.builders(kc) + + outOwner := &secp256k1fx.OutputOwners{ + Locktime: 0, + Threshold: 1, + Addrs: []ids.ShortID{to}, + } + + utx, err := xBuilder.NewImportTx( + sourceChain, + outOwner, + ) + if err != nil { + return nil, fmt.Errorf("failed building import tx: %w", err) + } + + return signer.SignUnsigned(context.Background(), xSigner, utx) +} + +func (b *Builder) ExportTx( + destinationChain ids.ID, + to ids.ShortID, + exportedAssetID ids.ID, + exportedAmt uint64, + kc *secp256k1fx.Keychain, + changeAddr ids.ShortID, +) (*txs.Tx, error) { + xBuilder, xSigner := b.builders(kc) + + outputs := []*avax.TransferableOutput{{ + Asset: avax.Asset{ID: exportedAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: exportedAmt, + OutputOwners: secp256k1fx.OutputOwners{ + Locktime: 0, + Threshold: 1, + Addrs: []ids.ShortID{to}, + }, + }, + }} + + utx, err := xBuilder.NewExportTx( + destinationChain, + outputs, + common.WithChangeOwner(&secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{changeAddr}, + }), + ) + if err != nil { + return nil, fmt.Errorf("failed building export tx: %w", err) + } + + return signer.SignUnsigned(context.Background(), xSigner, utx) +} + +func (b *Builder) builders(kc *secp256k1fx.Keychain) (builder.Builder, signer.Signer) { + var ( + addrs = kc.Addresses() + wa = &walletUTXOsAdapter{ + utxos: b.utxos, + addrs: addrs, + } + builder = builder.New(addrs, b.ctx, wa) + signer = signer.New(kc, wa) + ) + return builder, signer +} diff --git a/vms/avm/txs/txstest/context.go b/vms/avm/txs/txstest/context.go new file mode 100644 index 000000000000..ea3b9f2410f4 --- /dev/null +++ b/vms/avm/txs/txstest/context.go @@ -0,0 +1,25 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package txstest + +import ( + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/vms/avm/config" + "github.com/ava-labs/avalanchego/wallet/chain/x/builder" +) + +func newContext( + ctx *snow.Context, + cfg *config.Config, + feeAssetID ids.ID, +) *builder.Context { + return &builder.Context{ + NetworkID: ctx.NetworkID, + BlockchainID: ctx.XChainID, + AVAXAssetID: feeAssetID, + BaseTxFee: cfg.TxFee, + CreateAssetTxFee: cfg.CreateAssetTxFee, + } +} diff --git a/vms/avm/txs/txstest/utxos.go b/vms/avm/txs/txstest/utxos.go new file mode 100644 index 000000000000..39b3b712905b --- /dev/null +++ b/vms/avm/txs/txstest/utxos.go @@ -0,0 +1,103 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package txstest + +import ( + "context" + "fmt" + + "github.com/ava-labs/avalanchego/chains/atomic" + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/vms/avm/state" + "github.com/ava-labs/avalanchego/vms/components/avax" + "github.com/ava-labs/avalanchego/wallet/chain/x/builder" + "github.com/ava-labs/avalanchego/wallet/chain/x/signer" +) + +const maxPageSize uint64 = 1024 + +var ( + _ builder.Backend = (*walletUTXOsAdapter)(nil) + _ signer.Backend = (*walletUTXOsAdapter)(nil) +) + +func newUTXOs( + ctx *snow.Context, + state state.State, + sharedMemory atomic.SharedMemory, + codec codec.Manager, +) *utxos { + return &utxos{ + xchainID: ctx.ChainID, + state: state, + sharedMemory: sharedMemory, + codec: codec, + } +} + +type utxos struct { + xchainID ids.ID + state state.State + sharedMemory atomic.SharedMemory + codec codec.Manager +} + +func (u *utxos) UTXOs(addrs set.Set[ids.ShortID], sourceChainID ids.ID) ([]*avax.UTXO, error) { + if sourceChainID == u.xchainID { + return avax.GetAllUTXOs(u.state, addrs) + } + + atomicUTXOs, _, _, err := avax.GetAtomicUTXOs( + u.sharedMemory, + u.codec, + sourceChainID, + addrs, + ids.ShortEmpty, + ids.Empty, + int(maxPageSize), + ) + return atomicUTXOs, err +} + +func (u *utxos) GetUTXO(addrs set.Set[ids.ShortID], chainID, utxoID ids.ID) (*avax.UTXO, error) { + if chainID == u.xchainID { + return u.state.GetUTXO(utxoID) + } + + atomicUTXOs, _, _, err := avax.GetAtomicUTXOs( + u.sharedMemory, + u.codec, + chainID, + addrs, + ids.ShortEmpty, + ids.Empty, + int(maxPageSize), + ) + if err != nil { + return nil, fmt.Errorf("problem retrieving atomic UTXOs: %w", err) + } + for _, utxo := range atomicUTXOs { + if utxo.InputID() == utxoID { + return utxo, nil + } + } + return nil, database.ErrNotFound +} + +type walletUTXOsAdapter struct { + utxos *utxos + addrs set.Set[ids.ShortID] +} + +func (w *walletUTXOsAdapter) UTXOs(_ context.Context, sourceChainID ids.ID) ([]*avax.UTXO, error) { + return w.utxos.UTXOs(w.addrs, sourceChainID) +} + +func (w *walletUTXOsAdapter) GetUTXO(_ context.Context, chainID, utxoID ids.ID) (*avax.UTXO, error) { + return w.utxos.GetUTXO(w.addrs, chainID, utxoID) +} diff --git a/vms/avm/vm_benchmark_test.go b/vms/avm/vm_benchmark_test.go index e0bb3080c4e1..096ed51e13bc 100644 --- a/vms/avm/vm_benchmark_test.go +++ b/vms/avm/vm_benchmark_test.go @@ -4,7 +4,6 @@ package avm import ( - "context" "fmt" "math/rand" "testing" @@ -29,10 +28,7 @@ func BenchmarkLoadUser(b *testing.B) { password: password, }}, }) - defer func() { - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + defer env.vm.ctx.Lock.Unlock() user, err := keystore.NewUserFromKeystore(env.vm.ctx.Keystore, username, password) require.NoError(err) @@ -69,10 +65,7 @@ func getAllUTXOsBenchmark(b *testing.B, utxoCount int, randSrc rand.Source) { require := require.New(b) env := setup(b, &envConfig{fork: latest}) - defer func() { - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + defer env.vm.ctx.Lock.Unlock() addr := ids.GenerateTestShortID() diff --git a/vms/avm/vm_regression_test.go b/vms/avm/vm_regression_test.go index 0151ccaf6dab..9e684e756d5c 100644 --- a/vms/avm/vm_regression_test.go +++ b/vms/avm/vm_regression_test.go @@ -4,15 +4,11 @@ package avm import ( - "context" "testing" "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" - "github.com/ava-labs/avalanchego/vms/avm/txs" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/verify" "github.com/ava-labs/avalanchego/vms/nftfx" @@ -22,95 +18,78 @@ import ( func TestVerifyFxUsage(t *testing.T) { require := require.New(t) - env := setup(t, &envConfig{ - vmStaticConfig: noFeesTestConfig, - }) + env := setup(t, &envConfig{fork: latest}) env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - createAssetTx := &txs.Tx{Unsigned: &txs.CreateAssetTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: env.vm.ctx.XChainID, - }}, - Name: "Team Rocket", - Symbol: "TR", - Denomination: 0, - States: []*txs.InitialState{ - { - FxIndex: 0, - Outs: []verify.State{ - &secp256k1fx.TransferOutput{ - Amt: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, - }, + var ( + key = keys[0] + kc = secp256k1fx.NewKeychain(key) + ) + + initialStates := map[uint32][]verify.State{ + 0: { + &secp256k1fx.TransferOutput{ + Amt: 1, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, }, }, - { - FxIndex: 1, - Outs: []verify.State{ - &nftfx.MintOutput{ - GroupID: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, - }, + }, + 1: { + &nftfx.MintOutput{ + GroupID: 1, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, }, }, }, - }} - require.NoError(createAssetTx.Initialize(env.vm.parser.Codec())) + } + + // Create the asset + createAssetTx, err := env.txBuilder.CreateAssetTx( + "Team Rocket", // name + "TR", // symbol + 0, // denomination + initialStates, + kc, + key.Address(), + ) + require.NoError(err) issueAndAccept(require, env.vm, env.issuer, createAssetTx) - mintNFTTx := &txs.Tx{Unsigned: &txs.OperationTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: env.vm.ctx.XChainID, + // Mint the NFT + mintNFTTx, err := env.txBuilder.MintNFT( + createAssetTx.ID(), + []byte{'h', 'e', 'l', 'l', 'o'}, // payload + []*secp256k1fx.OutputOwners{{ + Threshold: 1, + Addrs: []ids.ShortID{key.Address()}, }}, - Ops: []*txs.Operation{{ - Asset: avax.Asset{ID: createAssetTx.ID()}, - UTXOIDs: []*avax.UTXOID{{ - TxID: createAssetTx.ID(), - OutputIndex: 1, - }}, - Op: &nftfx.MintOperation{ - MintInput: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - GroupID: 1, - Payload: []byte{'h', 'e', 'l', 'l', 'o'}, - Outputs: []*secp256k1fx.OutputOwners{{}}, - }, - }}, - }} - require.NoError(mintNFTTx.SignNFTFx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{keys[0]}})) + kc, + key.Address(), + ) + require.NoError(err) issueAndAccept(require, env.vm, env.issuer, mintNFTTx) - spendTx := &txs.Tx{Unsigned: &txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: env.vm.ctx.XChainID, - Ins: []*avax.TransferableInput{{ - UTXOID: avax.UTXOID{ - TxID: createAssetTx.ID(), - OutputIndex: 0, - }, + // move the NFT + to := keys[2].PublicKey().Address() + spendTx, err := env.txBuilder.BaseTx( + []*avax.TransferableOutput{{ Asset: avax.Asset{ID: createAssetTx.ID()}, - In: &secp256k1fx.TransferInput{ + Out: &secp256k1fx.TransferOutput{ Amt: 1, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{to}, }, }, }}, - }}} - require.NoError(spendTx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{keys[0]}})) + nil, // memo + kc, + key.Address(), + ) + require.NoError(err) issueAndAccept(require, env.vm, env.issuer, spendTx) } diff --git a/vms/avm/vm_test.go b/vms/avm/vm_test.go index 25824f4d0097..33af48c483f8 100644 --- a/vms/avm/vm_test.go +++ b/vms/avm/vm_test.go @@ -19,7 +19,6 @@ import ( "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" - "github.com/ava-labs/avalanchego/vms/avm/fxs" "github.com/ava-labs/avalanchego/vms/avm/txs" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/verify" @@ -118,11 +117,6 @@ func TestIssueTx(t *testing.T) { fork: latest, }) env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() tx := newTx(t, env.genesisBytes, env.vm.ctx.ChainID, env.vm.parser, "AVAX") issueAndAccept(require, env.vm, env.issuer, tx) @@ -133,99 +127,71 @@ func TestIssueNFT(t *testing.T) { require := require.New(t) env := setup(t, &envConfig{ - vmStaticConfig: noFeesTestConfig, + fork: latest, }) env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - createAssetTx := &txs.Tx{Unsigned: &txs.CreateAssetTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: env.vm.ctx.XChainID, - }}, - Name: "Team Rocket", - Symbol: "TR", - Denomination: 0, - States: []*txs.InitialState{{ - FxIndex: 1, - Outs: []verify.State{ - &nftfx.MintOutput{ - GroupID: 1, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, - }, - &nftfx.MintOutput{ - GroupID: 2, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, + var ( + key = keys[0] + kc = secp256k1fx.NewKeychain(key) + ) + + // Create the asset + initialStates := map[uint32][]verify.State{ + 1: { + &nftfx.MintOutput{ + GroupID: 1, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{key.PublicKey().Address()}, }, }, - }}, - }} - require.NoError(createAssetTx.Initialize(env.vm.parser.Codec())) + }, + } + + createAssetTx, err := env.txBuilder.CreateAssetTx( + "Team Rocket", // name + "TR", // symbol + 0, // denomination + initialStates, + kc, + key.Address(), + ) + require.NoError(err) issueAndAccept(require, env.vm, env.issuer, createAssetTx) - mintNFTTx := &txs.Tx{Unsigned: &txs.OperationTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: env.vm.ctx.XChainID, + // Mint the NFT + mintNFTTx, err := env.txBuilder.MintNFT( + createAssetTx.ID(), + []byte{'h', 'e', 'l', 'l', 'o'}, // payload + []*secp256k1fx.OutputOwners{{ + Threshold: 1, + Addrs: []ids.ShortID{key.Address()}, }}, - Ops: []*txs.Operation{{ - Asset: avax.Asset{ID: createAssetTx.ID()}, - UTXOIDs: []*avax.UTXOID{{ - TxID: createAssetTx.ID(), - OutputIndex: 0, - }}, - Op: &nftfx.MintOperation{ - MintInput: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - GroupID: 1, - Payload: []byte{'h', 'e', 'l', 'l', 'o'}, - Outputs: []*secp256k1fx.OutputOwners{{}}, - }, - }}, - }} - require.NoError(mintNFTTx.SignNFTFx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{keys[0]}})) + kc, + key.Address(), + ) + require.NoError(err) issueAndAccept(require, env.vm, env.issuer, mintNFTTx) - transferNFTTx := &txs.Tx{ - Unsigned: &txs.OperationTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: env.vm.ctx.XChainID, - }}, - Ops: []*txs.Operation{{ - Asset: avax.Asset{ID: createAssetTx.ID()}, - UTXOIDs: []*avax.UTXOID{{ - TxID: mintNFTTx.ID(), - OutputIndex: 0, - }}, - Op: &nftfx.TransferOperation{ - Input: secp256k1fx.Input{}, - Output: nftfx.TransferOutput{ - GroupID: 1, - Payload: []byte{'h', 'e', 'l', 'l', 'o'}, - OutputOwners: secp256k1fx.OutputOwners{}, - }, - }, - }}, - }, - Creds: []*fxs.FxCredential{ - { - Credential: &nftfx.Credential{}, - }, - }, - } - require.NoError(transferNFTTx.Initialize(env.vm.parser.Codec())) + // Move the NFT + utxos, err := avax.GetAllUTXOs(env.vm.state, kc.Addresses()) + require.NoError(err) + transferOp, _, err := env.vm.SpendNFT( + utxos, + kc, + createAssetTx.ID(), + 1, + keys[2].Address(), + ) + require.NoError(err) + + transferNFTTx, err := env.txBuilder.Operation( + transferOp, + kc, + key.Address(), + ) + require.NoError(err) issueAndAccept(require, env.vm, env.issuer, transferNFTTx) } @@ -234,92 +200,87 @@ func TestIssueProperty(t *testing.T) { require := require.New(t) env := setup(t, &envConfig{ - vmStaticConfig: noFeesTestConfig, + fork: latest, additionalFxs: []*common.Fx{{ ID: propertyfx.ID, Fx: &propertyfx.Fx{}, }}, }) env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() - createAssetTx := &txs.Tx{Unsigned: &txs.CreateAssetTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: env.vm.ctx.XChainID, - }}, - Name: "Team Rocket", - Symbol: "TR", - Denomination: 0, - States: []*txs.InitialState{{ - FxIndex: 2, - Outs: []verify.State{ - &propertyfx.MintOutput{ - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, + var ( + key = keys[0] + kc = secp256k1fx.NewKeychain(key) + ) + + // create the asset + initialStates := map[uint32][]verify.State{ + 2: { + &propertyfx.MintOutput{ + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, }, }, - }}, - }} - require.NoError(createAssetTx.Initialize(env.vm.parser.Codec())) + }, + } + + createAssetTx, err := env.txBuilder.CreateAssetTx( + "Team Rocket", // name + "TR", // symbol + 0, // denomination + initialStates, + kc, + key.Address(), + ) + require.NoError(err) issueAndAccept(require, env.vm, env.issuer, createAssetTx) - mintPropertyTx := &txs.Tx{Unsigned: &txs.OperationTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: env.vm.ctx.XChainID, + // mint the property + mintPropertyOp := &txs.Operation{ + Asset: avax.Asset{ID: createAssetTx.ID()}, + UTXOIDs: []*avax.UTXOID{{ + TxID: createAssetTx.ID(), + OutputIndex: 1, }}, - Ops: []*txs.Operation{{ - Asset: avax.Asset{ID: createAssetTx.ID()}, - UTXOIDs: []*avax.UTXOID{{ - TxID: createAssetTx.ID(), - OutputIndex: 0, - }}, - Op: &propertyfx.MintOperation{ - MintInput: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - MintOutput: propertyfx.MintOutput{ - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, + Op: &propertyfx.MintOperation{ + MintInput: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }, + MintOutput: propertyfx.MintOutput{ + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, }, - OwnedOutput: propertyfx.OwnedOutput{}, }, - }}, - }} + OwnedOutput: propertyfx.OwnedOutput{}, + }, + } - codec := env.vm.parser.Codec() - require.NoError(mintPropertyTx.SignPropertyFx(codec, [][]*secp256k1.PrivateKey{ - {keys[0]}, - })) + mintPropertyTx, err := env.txBuilder.Operation( + []*txs.Operation{mintPropertyOp}, + kc, + key.Address(), + ) + require.NoError(err) issueAndAccept(require, env.vm, env.issuer, mintPropertyTx) - burnPropertyTx := &txs.Tx{Unsigned: &txs.OperationTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: env.vm.ctx.XChainID, - }}, - Ops: []*txs.Operation{{ - Asset: avax.Asset{ID: createAssetTx.ID()}, - UTXOIDs: []*avax.UTXOID{{ - TxID: mintPropertyTx.ID(), - OutputIndex: 1, - }}, - Op: &propertyfx.BurnOperation{Input: secp256k1fx.Input{}}, + // burn the property + burnPropertyOp := &txs.Operation{ + Asset: avax.Asset{ID: createAssetTx.ID()}, + UTXOIDs: []*avax.UTXOID{{ + TxID: mintPropertyTx.ID(), + OutputIndex: 2, }}, - }} + Op: &propertyfx.BurnOperation{Input: secp256k1fx.Input{}}, + } - require.NoError(burnPropertyTx.SignPropertyFx(codec, [][]*secp256k1.PrivateKey{ - {}, - })) + burnPropertyTx, err := env.txBuilder.Operation( + []*txs.Operation{burnPropertyOp}, + kc, + key.Address(), + ) + require.NoError(err) issueAndAccept(require, env.vm, env.issuer, burnPropertyTx) } @@ -331,11 +292,6 @@ func TestIssueTxWithFeeAsset(t *testing.T) { isCustomFeeAsset: true, }) env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() // send first asset tx := newTx(t, env.genesisBytes, env.vm.ctx.ChainID, env.vm.parser, feeAssetName) @@ -350,58 +306,44 @@ func TestIssueTxWithAnotherAsset(t *testing.T) { isCustomFeeAsset: true, }) env.vm.ctx.Lock.Unlock() - defer func() { - env.vm.ctx.Lock.Lock() - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() // send second asset - feeAssetCreateTx := getCreateTxFromGenesisTest(t, env.genesisBytes, feeAssetName) - createTx := getCreateTxFromGenesisTest(t, env.genesisBytes, otherAssetName) + var ( + key = keys[0] + kc = secp256k1fx.NewKeychain(key) - tx := &txs.Tx{Unsigned: &txs.BaseTx{ - BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: env.vm.ctx.XChainID, - Ins: []*avax.TransferableInput{ - // fee asset - { - UTXOID: avax.UTXOID{ - TxID: feeAssetCreateTx.ID(), - OutputIndex: 2, - }, - Asset: avax.Asset{ID: feeAssetCreateTx.ID()}, - In: &secp256k1fx.TransferInput{ - Amt: startBalance, - Input: secp256k1fx.Input{ - SigIndices: []uint32{ - 0, - }, - }, + feeAssetCreateTx = getCreateTxFromGenesisTest(t, env.genesisBytes, feeAssetName) + createTx = getCreateTxFromGenesisTest(t, env.genesisBytes, otherAssetName) + ) + + tx, err := env.txBuilder.BaseTx( + []*avax.TransferableOutput{ + { // fee asset + Asset: avax.Asset{ID: feeAssetCreateTx.ID()}, + Out: &secp256k1fx.TransferOutput{ + Amt: startBalance - env.vm.TxFee, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{key.PublicKey().Address()}, }, }, - // issued asset - { - UTXOID: avax.UTXOID{ - TxID: createTx.ID(), - OutputIndex: 2, - }, - Asset: avax.Asset{ID: createTx.ID()}, - In: &secp256k1fx.TransferInput{ - Amt: startBalance, - Input: secp256k1fx.Input{ - SigIndices: []uint32{ - 0, - }, - }, + }, + { // issued asset + Asset: avax.Asset{ID: createTx.ID()}, + Out: &secp256k1fx.TransferOutput{ + Amt: startBalance - env.vm.TxFee, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{key.PublicKey().Address()}, }, }, }, }, - }} - require.NoError(tx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{keys[0]}, {keys[0]}})) - + nil, // memo + kc, + key.Address(), + ) + require.NoError(err) issueAndAccept(require, env.vm, env.issuer, tx) } @@ -409,10 +351,7 @@ func TestVMFormat(t *testing.T) { env := setup(t, &envConfig{ fork: latest, }) - defer func() { - require.NoError(t, env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + defer env.vm.ctx.Lock.Unlock() tests := []struct { in ids.ShortID @@ -440,45 +379,31 @@ func TestTxAcceptAfterParseTx(t *testing.T) { fork: latest, notLinearized: true, }) - defer func() { - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + defer env.vm.ctx.Lock.Unlock() - key := keys[0] - firstTx := &txs.Tx{Unsigned: &txs.BaseTx{ - BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: env.vm.ctx.XChainID, - Ins: []*avax.TransferableInput{{ - UTXOID: avax.UTXOID{ - TxID: env.genesisTx.ID(), - OutputIndex: 2, - }, - Asset: avax.Asset{ID: env.genesisTx.ID()}, - In: &secp256k1fx.TransferInput{ - Amt: startBalance, - Input: secp256k1fx.Input{ - SigIndices: []uint32{ - 0, - }, - }, - }, - }}, - Outs: []*avax.TransferableOutput{{ - Asset: avax.Asset{ID: env.genesisTx.ID()}, - Out: &secp256k1fx.TransferOutput{ - Amt: startBalance - env.vm.TxFee, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{key.PublicKey().Address()}, - }, + var ( + key = keys[0] + kc = secp256k1fx.NewKeychain(key) + ) + + firstTx, err := env.txBuilder.BaseTx( + []*avax.TransferableOutput{{ + Asset: avax.Asset{ID: env.genesisTx.ID()}, + Out: &secp256k1fx.TransferOutput{ + Amt: startBalance - env.vm.TxFee, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{key.PublicKey().Address()}, }, - }}, - }, - }} - require.NoError(firstTx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) + }, + }}, + nil, // memo + kc, + key.Address(), + ) + require.NoError(err) + // let secondTx spend firstTx outputs secondTx := &txs.Tx{Unsigned: &txs.BaseTx{ BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, @@ -526,75 +451,46 @@ func TestIssueImportTx(t *testing.T) { require := require.New(t) env := setup(t, &envConfig{ - vmStaticConfig: noFeesTestConfig, + fork: durango, }) - defer func() { - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + defer env.vm.ctx.Lock.Unlock() peerSharedMemory := env.sharedMemory.NewSharedMemory(constants.PlatformChainID) genesisTx := getCreateTxFromGenesisTest(t, env.genesisBytes, "AVAX") avaxID := genesisTx.ID() - key := keys[0] - utxoID := avax.UTXOID{ - TxID: ids.ID{ - 0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee, - 0x0d, 0x2d, 0x4d, 0x6d, 0x8c, 0xac, 0xcc, 0xec, - 0x0b, 0x2b, 0x4b, 0x6b, 0x8a, 0xaa, 0xca, 0xea, - 0x09, 0x29, 0x49, 0x69, 0x88, 0xa8, 0xc8, 0xe8, - }, - } + var ( + key = keys[0] + kc = secp256k1fx.NewKeychain(key) - txAssetID := avax.Asset{ID: avaxID} - tx := &txs.Tx{Unsigned: &txs.ImportTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: env.vm.ctx.XChainID, - Outs: []*avax.TransferableOutput{{ - Asset: txAssetID, - Out: &secp256k1fx.TransferOutput{ - Amt: 1000, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, - }, - }, - }}, - }}, - SourceChain: constants.PlatformChainID, - ImportedIns: []*avax.TransferableInput{{ + utxoID = avax.UTXOID{ + TxID: ids.ID{ + 0x0f, 0x2f, 0x4f, 0x6f, 0x8e, 0xae, 0xce, 0xee, + 0x0d, 0x2d, 0x4d, 0x6d, 0x8c, 0xac, 0xcc, 0xec, + 0x0b, 0x2b, 0x4b, 0x6b, 0x8a, 0xaa, 0xca, 0xea, + 0x09, 0x29, 0x49, 0x69, 0x88, 0xa8, 0xc8, 0xe8, + }, + } + txAssetID = avax.Asset{ID: avaxID} + importedUtxo = &avax.UTXO{ UTXOID: utxoID, Asset: txAssetID, - In: &secp256k1fx.TransferInput{ + Out: &secp256k1fx.TransferOutput{ Amt: 1010, - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{key.PublicKey().Address()}, }, }, - }}, - }} - require.NoError(tx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) + } + ) // Provide the platform UTXO: - utxo := &avax.UTXO{ - UTXOID: utxoID, - Asset: txAssetID, - Out: &secp256k1fx.TransferOutput{ - Amt: 1010, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{key.PublicKey().Address()}, - }, - }, - } - - utxoBytes, err := env.vm.parser.Codec().Marshal(txs.CodecVersion, utxo) + utxoBytes, err := env.vm.parser.Codec().Marshal(txs.CodecVersion, importedUtxo) require.NoError(err) - inputID := utxo.InputID() + inputID := importedUtxo.InputID() require.NoError(peerSharedMemory.Apply(map[ids.ID]*atomic.Requests{ env.vm.ctx.ChainID: { PutRequests: []*atomic.Element{{ @@ -607,6 +503,13 @@ func TestIssueImportTx(t *testing.T) { }, })) + tx, err := env.txBuilder.ImportTx( + constants.PlatformChainID, // source chain + key.Address(), + kc, + ) + require.NoError(err) + env.vm.ctx.Lock.Unlock() issueAndAccept(require, env.vm, env.issuer, tx) @@ -626,13 +529,10 @@ func TestForceAcceptImportTx(t *testing.T) { require := require.New(t) env := setup(t, &envConfig{ - vmStaticConfig: noFeesTestConfig, - notLinearized: true, + fork: durango, + notLinearized: true, }) - defer func() { - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + defer env.vm.ctx.Lock.Unlock() genesisTx := getCreateTxFromGenesisTest(t, env.genesisBytes, "AVAX") avaxID := genesisTx.ID() @@ -655,7 +555,7 @@ func TestForceAcceptImportTx(t *testing.T) { Outs: []*avax.TransferableOutput{{ Asset: txAssetID, Out: &secp256k1fx.TransferOutput{ - Amt: 1000, + Amt: 10, OutputOwners: secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, @@ -703,47 +603,28 @@ func TestImportTxNotState(t *testing.T) { func TestIssueExportTx(t *testing.T) { require := require.New(t) - env := setup(t, &envConfig{ - fork: latest, - }) - defer func() { - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + env := setup(t, &envConfig{fork: durango}) + defer env.vm.ctx.Lock.Unlock() genesisTx := getCreateTxFromGenesisTest(t, env.genesisBytes, "AVAX") - avaxID := genesisTx.ID() - key := keys[0] - tx := &txs.Tx{Unsigned: &txs.ExportTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: env.vm.ctx.XChainID, - Ins: []*avax.TransferableInput{{ - UTXOID: avax.UTXOID{ - TxID: avaxID, - OutputIndex: 2, - }, - Asset: avax.Asset{ID: avaxID}, - In: &secp256k1fx.TransferInput{ - Amt: startBalance, - Input: secp256k1fx.Input{SigIndices: []uint32{0}}, - }, - }}, - }}, - DestinationChain: constants.PlatformChainID, - ExportedOuts: []*avax.TransferableOutput{{ - Asset: avax.Asset{ID: avaxID}, - Out: &secp256k1fx.TransferOutput{ - Amt: startBalance - env.vm.TxFee, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{key.PublicKey().Address()}, - }, - }, - }}, - }} - require.NoError(tx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) + var ( + avaxID = genesisTx.ID() + key = keys[0] + kc = secp256k1fx.NewKeychain(key) + to = key.PublicKey().Address() + changeAddr = to + ) + + tx, err := env.txBuilder.ExportTx( + constants.PlatformChainID, + to, // to + avaxID, + startBalance-env.vm.TxFee, + kc, + changeAddr, + ) + require.NoError(err) peerSharedMemory := env.sharedMemory.NewSharedMemory(constants.PlatformChainID) utxoBytes, _, _, err := peerSharedMemory.Indexed( @@ -783,45 +664,28 @@ func TestClearForceAcceptedExportTx(t *testing.T) { env := setup(t, &envConfig{ fork: latest, }) - defer func() { - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + defer env.vm.ctx.Lock.Unlock() genesisTx := getCreateTxFromGenesisTest(t, env.genesisBytes, "AVAX") - avaxID := genesisTx.ID() - key := keys[0] - assetID := avax.Asset{ID: avaxID} - tx := &txs.Tx{Unsigned: &txs.ExportTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: env.vm.ctx.XChainID, - Ins: []*avax.TransferableInput{{ - UTXOID: avax.UTXOID{ - TxID: avaxID, - OutputIndex: 2, - }, - Asset: assetID, - In: &secp256k1fx.TransferInput{ - Amt: startBalance, - Input: secp256k1fx.Input{SigIndices: []uint32{0}}, - }, - }}, - }}, - DestinationChain: constants.PlatformChainID, - ExportedOuts: []*avax.TransferableOutput{{ - Asset: assetID, - Out: &secp256k1fx.TransferOutput{ - Amt: startBalance - env.vm.TxFee, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{key.PublicKey().Address()}, - }, - }, - }}, - }} - require.NoError(tx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) + var ( + avaxID = genesisTx.ID() + assetID = avax.Asset{ID: avaxID} + key = keys[0] + kc = secp256k1fx.NewKeychain(key) + to = key.PublicKey().Address() + changeAddr = to + ) + + tx, err := env.txBuilder.ExportTx( + constants.PlatformChainID, + to, // to + avaxID, + startBalance-env.vm.TxFee, + kc, + changeAddr, + ) + require.NoError(err) utxo := avax.UTXOID{ TxID: tx.ID(), @@ -836,7 +700,7 @@ func TestClearForceAcceptedExportTx(t *testing.T) { }, })) - _, err := peerSharedMemory.Get(env.vm.ctx.ChainID, [][]byte{utxoID[:]}) + _, err = peerSharedMemory.Get(env.vm.ctx.ChainID, [][]byte{utxoID[:]}) require.ErrorIs(err, database.ErrNotFound) env.vm.ctx.Lock.Unlock() diff --git a/vms/avm/wallet_service_test.go b/vms/avm/wallet_service_test.go index eeb214fba9bd..d4423bd31c7d 100644 --- a/vms/avm/wallet_service_test.go +++ b/vms/avm/wallet_service_test.go @@ -4,12 +4,14 @@ package avm import ( - "context" "testing" "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/api" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/linked" + "github.com/ava-labs/avalanchego/vms/avm/txs" ) func TestWalletService_SendMultiple(t *testing.T) { @@ -28,10 +30,10 @@ func TestWalletService_SendMultiple(t *testing.T) { }) env.vm.ctx.Lock.Unlock() - defer func() { - require.NoError(env.vm.Shutdown(context.Background())) - env.vm.ctx.Lock.Unlock() - }() + walletService := &WalletService{ + vm: env.vm, + pendingTxs: linked.NewHashmap[ids.ID, *txs.Tx](), + } assetID := env.genesisTx.ID() addr := keys[0].PublicKey().Address() @@ -65,14 +67,14 @@ func TestWalletService_SendMultiple(t *testing.T) { }, } reply := &api.JSONTxIDChangeAddr{} - require.NoError(env.walletService.SendMultiple(nil, args, reply)) + require.NoError(walletService.SendMultiple(nil, args, reply)) require.Equal(changeAddrStr, reply.ChangeAddr) buildAndAccept(require, env.vm, env.issuer, reply.TxID) env.vm.ctx.Lock.Lock() - _, err = env.vm.state.GetTx(reply.TxID) + env.vm.ctx.Lock.Unlock() require.NoError(err) }) }