github.com/MetalBlockchain/metalgo@v1.11.9/vms/avm/service_test.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package avm
     5  
     6  import (
     7  	"encoding/json"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/btcsuite/btcd/btcutil/bech32"
    13  	"github.com/prometheus/client_golang/prometheus"
    14  	"github.com/stretchr/testify/require"
    15  	"go.uber.org/mock/gomock"
    16  
    17  	"github.com/MetalBlockchain/metalgo/api"
    18  	"github.com/MetalBlockchain/metalgo/chains/atomic"
    19  	"github.com/MetalBlockchain/metalgo/codec"
    20  	"github.com/MetalBlockchain/metalgo/database"
    21  	"github.com/MetalBlockchain/metalgo/ids"
    22  	"github.com/MetalBlockchain/metalgo/snow"
    23  	"github.com/MetalBlockchain/metalgo/snow/choices"
    24  	"github.com/MetalBlockchain/metalgo/snow/engine/common"
    25  	"github.com/MetalBlockchain/metalgo/utils/constants"
    26  	"github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1"
    27  	"github.com/MetalBlockchain/metalgo/utils/formatting"
    28  	"github.com/MetalBlockchain/metalgo/utils/formatting/address"
    29  	"github.com/MetalBlockchain/metalgo/utils/logging"
    30  	"github.com/MetalBlockchain/metalgo/utils/timer/mockable"
    31  	"github.com/MetalBlockchain/metalgo/utils/units"
    32  	"github.com/MetalBlockchain/metalgo/vms/avm/block"
    33  	"github.com/MetalBlockchain/metalgo/vms/avm/block/executor"
    34  	"github.com/MetalBlockchain/metalgo/vms/avm/config"
    35  	"github.com/MetalBlockchain/metalgo/vms/avm/state"
    36  	"github.com/MetalBlockchain/metalgo/vms/avm/txs"
    37  	"github.com/MetalBlockchain/metalgo/vms/components/avax"
    38  	"github.com/MetalBlockchain/metalgo/vms/components/index"
    39  	"github.com/MetalBlockchain/metalgo/vms/components/verify"
    40  	"github.com/MetalBlockchain/metalgo/vms/nftfx"
    41  	"github.com/MetalBlockchain/metalgo/vms/propertyfx"
    42  	"github.com/MetalBlockchain/metalgo/vms/secp256k1fx"
    43  
    44  	avajson "github.com/MetalBlockchain/metalgo/utils/json"
    45  )
    46  
    47  func TestServiceIssueTx(t *testing.T) {
    48  	require := require.New(t)
    49  
    50  	env := setup(t, &envConfig{
    51  		fork: latest,
    52  	})
    53  	service := &Service{vm: env.vm}
    54  	env.vm.ctx.Lock.Unlock()
    55  
    56  	txArgs := &api.FormattedTx{}
    57  	txReply := &api.JSONTxID{}
    58  	err := service.IssueTx(nil, txArgs, txReply)
    59  	require.ErrorIs(err, codec.ErrCantUnpackVersion)
    60  
    61  	tx := newTx(t, env.genesisBytes, env.vm.ctx.ChainID, env.vm.parser, "AVAX")
    62  	txArgs.Tx, err = formatting.Encode(formatting.Hex, tx.Bytes())
    63  	require.NoError(err)
    64  	txArgs.Encoding = formatting.Hex
    65  	txReply = &api.JSONTxID{}
    66  	require.NoError(service.IssueTx(nil, txArgs, txReply))
    67  	require.Equal(tx.ID(), txReply.TxID)
    68  }
    69  
    70  func TestServiceGetTxStatus(t *testing.T) {
    71  	require := require.New(t)
    72  
    73  	env := setup(t, &envConfig{
    74  		fork: latest,
    75  	})
    76  	service := &Service{vm: env.vm}
    77  	env.vm.ctx.Lock.Unlock()
    78  
    79  	statusArgs := &api.JSONTxID{}
    80  	statusReply := &GetTxStatusReply{}
    81  	err := service.GetTxStatus(nil, statusArgs, statusReply)
    82  	require.ErrorIs(err, errNilTxID)
    83  
    84  	newTx := newAvaxBaseTxWithOutputs(t, env)
    85  	txID := newTx.ID()
    86  
    87  	statusArgs = &api.JSONTxID{
    88  		TxID: txID,
    89  	}
    90  	statusReply = &GetTxStatusReply{}
    91  	require.NoError(service.GetTxStatus(nil, statusArgs, statusReply))
    92  	require.Equal(choices.Unknown, statusReply.Status)
    93  
    94  	issueAndAccept(require, env.vm, env.issuer, newTx)
    95  
    96  	statusReply = &GetTxStatusReply{}
    97  	require.NoError(service.GetTxStatus(nil, statusArgs, statusReply))
    98  	require.Equal(choices.Accepted, statusReply.Status)
    99  }
   100  
   101  // Test the GetBalance method when argument Strict is true
   102  func TestServiceGetBalanceStrict(t *testing.T) {
   103  	require := require.New(t)
   104  
   105  	env := setup(t, &envConfig{
   106  		fork: latest,
   107  	})
   108  	service := &Service{vm: env.vm}
   109  
   110  	assetID := ids.GenerateTestID()
   111  	addr := ids.GenerateTestShortID()
   112  	addrStr, err := env.vm.FormatLocalAddress(addr)
   113  	require.NoError(err)
   114  
   115  	// A UTXO with a 2 out of 2 multisig
   116  	// where one of the addresses is [addr]
   117  	twoOfTwoUTXO := &avax.UTXO{
   118  		UTXOID: avax.UTXOID{
   119  			TxID:        ids.GenerateTestID(),
   120  			OutputIndex: 0,
   121  		},
   122  		Asset: avax.Asset{ID: assetID},
   123  		Out: &secp256k1fx.TransferOutput{
   124  			Amt: 1337,
   125  			OutputOwners: secp256k1fx.OutputOwners{
   126  				Threshold: 2,
   127  				Addrs:     []ids.ShortID{addr, ids.GenerateTestShortID()},
   128  			},
   129  		},
   130  	}
   131  	// Insert the UTXO
   132  	env.vm.state.AddUTXO(twoOfTwoUTXO)
   133  	require.NoError(env.vm.state.Commit())
   134  
   135  	env.vm.ctx.Lock.Unlock()
   136  
   137  	// Check the balance with IncludePartial set to true
   138  	balanceArgs := &GetBalanceArgs{
   139  		Address:        addrStr,
   140  		AssetID:        assetID.String(),
   141  		IncludePartial: true,
   142  	}
   143  	balanceReply := &GetBalanceReply{}
   144  	require.NoError(service.GetBalance(nil, balanceArgs, balanceReply))
   145  	// The balance should include the UTXO since it is partly owned by [addr]
   146  	require.Equal(uint64(1337), uint64(balanceReply.Balance))
   147  	require.Len(balanceReply.UTXOIDs, 1)
   148  
   149  	// Check the balance with IncludePartial set to false
   150  	balanceArgs = &GetBalanceArgs{
   151  		Address: addrStr,
   152  		AssetID: assetID.String(),
   153  	}
   154  	balanceReply = &GetBalanceReply{}
   155  	require.NoError(service.GetBalance(nil, balanceArgs, balanceReply))
   156  	// The balance should not include the UTXO since it is only partly owned by [addr]
   157  	require.Zero(balanceReply.Balance)
   158  	require.Empty(balanceReply.UTXOIDs)
   159  
   160  	env.vm.ctx.Lock.Lock()
   161  
   162  	// A UTXO with a 1 out of 2 multisig
   163  	// where one of the addresses is [addr]
   164  	oneOfTwoUTXO := &avax.UTXO{
   165  		UTXOID: avax.UTXOID{
   166  			TxID:        ids.GenerateTestID(),
   167  			OutputIndex: 0,
   168  		},
   169  		Asset: avax.Asset{ID: assetID},
   170  		Out: &secp256k1fx.TransferOutput{
   171  			Amt: 1337,
   172  			OutputOwners: secp256k1fx.OutputOwners{
   173  				Threshold: 1,
   174  				Addrs:     []ids.ShortID{addr, ids.GenerateTestShortID()},
   175  			},
   176  		},
   177  	}
   178  	// Insert the UTXO
   179  	env.vm.state.AddUTXO(oneOfTwoUTXO)
   180  	require.NoError(env.vm.state.Commit())
   181  
   182  	env.vm.ctx.Lock.Unlock()
   183  
   184  	// Check the balance with IncludePartial set to true
   185  	balanceArgs = &GetBalanceArgs{
   186  		Address:        addrStr,
   187  		AssetID:        assetID.String(),
   188  		IncludePartial: true,
   189  	}
   190  	balanceReply = &GetBalanceReply{}
   191  	require.NoError(service.GetBalance(nil, balanceArgs, balanceReply))
   192  	// The balance should include the UTXO since it is partly owned by [addr]
   193  	require.Equal(uint64(1337+1337), uint64(balanceReply.Balance))
   194  	require.Len(balanceReply.UTXOIDs, 2)
   195  
   196  	// Check the balance with IncludePartial set to false
   197  	balanceArgs = &GetBalanceArgs{
   198  		Address: addrStr,
   199  		AssetID: assetID.String(),
   200  	}
   201  	balanceReply = &GetBalanceReply{}
   202  	require.NoError(service.GetBalance(nil, balanceArgs, balanceReply))
   203  	// The balance should not include the UTXO since it is only partly owned by [addr]
   204  	require.Zero(balanceReply.Balance)
   205  	require.Empty(balanceReply.UTXOIDs)
   206  
   207  	env.vm.ctx.Lock.Lock()
   208  
   209  	// A UTXO with a 1 out of 1 multisig
   210  	// but with a locktime in the future
   211  	now := env.vm.clock.Time()
   212  	futureUTXO := &avax.UTXO{
   213  		UTXOID: avax.UTXOID{
   214  			TxID:        ids.GenerateTestID(),
   215  			OutputIndex: 0,
   216  		},
   217  		Asset: avax.Asset{ID: assetID},
   218  		Out: &secp256k1fx.TransferOutput{
   219  			Amt: 1337,
   220  			OutputOwners: secp256k1fx.OutputOwners{
   221  				Locktime:  uint64(now.Add(10 * time.Hour).Unix()),
   222  				Threshold: 1,
   223  				Addrs:     []ids.ShortID{addr},
   224  			},
   225  		},
   226  	}
   227  	// Insert the UTXO
   228  	env.vm.state.AddUTXO(futureUTXO)
   229  	require.NoError(env.vm.state.Commit())
   230  
   231  	env.vm.ctx.Lock.Unlock()
   232  
   233  	// Check the balance with IncludePartial set to true
   234  	balanceArgs = &GetBalanceArgs{
   235  		Address:        addrStr,
   236  		AssetID:        assetID.String(),
   237  		IncludePartial: true,
   238  	}
   239  	balanceReply = &GetBalanceReply{}
   240  	require.NoError(service.GetBalance(nil, balanceArgs, balanceReply))
   241  	// The balance should include the UTXO since it is partly owned by [addr]
   242  	require.Equal(uint64(1337*3), uint64(balanceReply.Balance))
   243  	require.Len(balanceReply.UTXOIDs, 3)
   244  
   245  	// Check the balance with IncludePartial set to false
   246  	balanceArgs = &GetBalanceArgs{
   247  		Address: addrStr,
   248  		AssetID: assetID.String(),
   249  	}
   250  	balanceReply = &GetBalanceReply{}
   251  	require.NoError(service.GetBalance(nil, balanceArgs, balanceReply))
   252  	// The balance should not include the UTXO since it is only partly owned by [addr]
   253  	require.Zero(balanceReply.Balance)
   254  	require.Empty(balanceReply.UTXOIDs)
   255  }
   256  
   257  func TestServiceGetTxs(t *testing.T) {
   258  	require := require.New(t)
   259  	env := setup(t, &envConfig{
   260  		fork: latest,
   261  	})
   262  	service := &Service{vm: env.vm}
   263  
   264  	var err error
   265  	env.vm.addressTxsIndexer, err = index.NewIndexer(env.vm.db, env.vm.ctx.Log, "", prometheus.NewRegistry(), false)
   266  	require.NoError(err)
   267  
   268  	assetID := ids.GenerateTestID()
   269  	addr := ids.GenerateTestShortID()
   270  	addrStr, err := env.vm.FormatLocalAddress(addr)
   271  	require.NoError(err)
   272  
   273  	testTxCount := 25
   274  	testTxs := initTestTxIndex(t, env.vm.db, addr, assetID, testTxCount)
   275  
   276  	env.vm.ctx.Lock.Unlock()
   277  
   278  	// get the first page
   279  	getTxsArgs := &GetAddressTxsArgs{
   280  		PageSize:    10,
   281  		JSONAddress: api.JSONAddress{Address: addrStr},
   282  		AssetID:     assetID.String(),
   283  	}
   284  	getTxsReply := &GetAddressTxsReply{}
   285  	require.NoError(service.GetAddressTxs(nil, getTxsArgs, getTxsReply))
   286  	require.Len(getTxsReply.TxIDs, 10)
   287  	require.Equal(getTxsReply.TxIDs, testTxs[:10])
   288  
   289  	// get the second page
   290  	getTxsArgs.Cursor = getTxsReply.Cursor
   291  	getTxsReply = &GetAddressTxsReply{}
   292  	require.NoError(service.GetAddressTxs(nil, getTxsArgs, getTxsReply))
   293  	require.Len(getTxsReply.TxIDs, 10)
   294  	require.Equal(getTxsReply.TxIDs, testTxs[10:20])
   295  }
   296  
   297  func TestServiceGetAllBalances(t *testing.T) {
   298  	require := require.New(t)
   299  
   300  	env := setup(t, &envConfig{
   301  		fork: latest,
   302  	})
   303  	service := &Service{vm: env.vm}
   304  
   305  	assetID := ids.GenerateTestID()
   306  	addr := ids.GenerateTestShortID()
   307  	addrStr, err := env.vm.FormatLocalAddress(addr)
   308  	require.NoError(err)
   309  	// A UTXO with a 2 out of 2 multisig
   310  	// where one of the addresses is [addr]
   311  	twoOfTwoUTXO := &avax.UTXO{
   312  		UTXOID: avax.UTXOID{
   313  			TxID:        ids.GenerateTestID(),
   314  			OutputIndex: 0,
   315  		},
   316  		Asset: avax.Asset{ID: assetID},
   317  		Out: &secp256k1fx.TransferOutput{
   318  			Amt: 1337,
   319  			OutputOwners: secp256k1fx.OutputOwners{
   320  				Threshold: 2,
   321  				Addrs:     []ids.ShortID{addr, ids.GenerateTestShortID()},
   322  			},
   323  		},
   324  	}
   325  	// Insert the UTXO
   326  	env.vm.state.AddUTXO(twoOfTwoUTXO)
   327  	require.NoError(env.vm.state.Commit())
   328  
   329  	env.vm.ctx.Lock.Unlock()
   330  
   331  	// Check the balance with IncludePartial set to true
   332  	balanceArgs := &GetAllBalancesArgs{
   333  		JSONAddress:    api.JSONAddress{Address: addrStr},
   334  		IncludePartial: true,
   335  	}
   336  	reply := &GetAllBalancesReply{}
   337  	require.NoError(service.GetAllBalances(nil, balanceArgs, reply))
   338  	// The balance should include the UTXO since it is partly owned by [addr]
   339  	require.Len(reply.Balances, 1)
   340  	require.Equal(assetID.String(), reply.Balances[0].AssetID)
   341  	require.Equal(uint64(1337), uint64(reply.Balances[0].Balance))
   342  
   343  	// Check the balance with IncludePartial set to false
   344  	balanceArgs = &GetAllBalancesArgs{
   345  		JSONAddress: api.JSONAddress{Address: addrStr},
   346  	}
   347  	reply = &GetAllBalancesReply{}
   348  	require.NoError(service.GetAllBalances(nil, balanceArgs, reply))
   349  	require.Empty(reply.Balances)
   350  
   351  	env.vm.ctx.Lock.Lock()
   352  
   353  	// A UTXO with a 1 out of 2 multisig
   354  	// where one of the addresses is [addr]
   355  	oneOfTwoUTXO := &avax.UTXO{
   356  		UTXOID: avax.UTXOID{
   357  			TxID:        ids.GenerateTestID(),
   358  			OutputIndex: 0,
   359  		},
   360  		Asset: avax.Asset{ID: assetID},
   361  		Out: &secp256k1fx.TransferOutput{
   362  			Amt: 1337,
   363  			OutputOwners: secp256k1fx.OutputOwners{
   364  				Threshold: 1,
   365  				Addrs:     []ids.ShortID{addr, ids.GenerateTestShortID()},
   366  			},
   367  		},
   368  	}
   369  	// Insert the UTXO
   370  	env.vm.state.AddUTXO(oneOfTwoUTXO)
   371  	require.NoError(env.vm.state.Commit())
   372  
   373  	env.vm.ctx.Lock.Unlock()
   374  
   375  	// Check the balance with IncludePartial set to true
   376  	balanceArgs = &GetAllBalancesArgs{
   377  		JSONAddress:    api.JSONAddress{Address: addrStr},
   378  		IncludePartial: true,
   379  	}
   380  	reply = &GetAllBalancesReply{}
   381  	require.NoError(service.GetAllBalances(nil, balanceArgs, reply))
   382  	// The balance should include the UTXO since it is partly owned by [addr]
   383  	require.Len(reply.Balances, 1)
   384  	require.Equal(assetID.String(), reply.Balances[0].AssetID)
   385  	require.Equal(uint64(1337*2), uint64(reply.Balances[0].Balance))
   386  
   387  	// Check the balance with IncludePartial set to false
   388  	balanceArgs = &GetAllBalancesArgs{
   389  		JSONAddress: api.JSONAddress{Address: addrStr},
   390  	}
   391  	reply = &GetAllBalancesReply{}
   392  	require.NoError(service.GetAllBalances(nil, balanceArgs, reply))
   393  	// The balance should not include the UTXO since it is only partly owned by [addr]
   394  	require.Empty(reply.Balances)
   395  
   396  	env.vm.ctx.Lock.Lock()
   397  
   398  	// A UTXO with a 1 out of 1 multisig
   399  	// but with a locktime in the future
   400  	now := env.vm.clock.Time()
   401  	futureUTXO := &avax.UTXO{
   402  		UTXOID: avax.UTXOID{
   403  			TxID:        ids.GenerateTestID(),
   404  			OutputIndex: 0,
   405  		},
   406  		Asset: avax.Asset{ID: assetID},
   407  		Out: &secp256k1fx.TransferOutput{
   408  			Amt: 1337,
   409  			OutputOwners: secp256k1fx.OutputOwners{
   410  				Locktime:  uint64(now.Add(10 * time.Hour).Unix()),
   411  				Threshold: 1,
   412  				Addrs:     []ids.ShortID{addr},
   413  			},
   414  		},
   415  	}
   416  	// Insert the UTXO
   417  	env.vm.state.AddUTXO(futureUTXO)
   418  	require.NoError(env.vm.state.Commit())
   419  
   420  	env.vm.ctx.Lock.Unlock()
   421  
   422  	// Check the balance with IncludePartial set to true
   423  	balanceArgs = &GetAllBalancesArgs{
   424  		JSONAddress:    api.JSONAddress{Address: addrStr},
   425  		IncludePartial: true,
   426  	}
   427  	reply = &GetAllBalancesReply{}
   428  	require.NoError(service.GetAllBalances(nil, balanceArgs, reply))
   429  	// The balance should include the UTXO since it is partly owned by [addr]
   430  	// The balance should include the UTXO since it is partly owned by [addr]
   431  	require.Len(reply.Balances, 1)
   432  	require.Equal(assetID.String(), reply.Balances[0].AssetID)
   433  	require.Equal(uint64(1337*3), uint64(reply.Balances[0].Balance))
   434  	// Check the balance with IncludePartial set to false
   435  	balanceArgs = &GetAllBalancesArgs{
   436  		JSONAddress: api.JSONAddress{Address: addrStr},
   437  	}
   438  	reply = &GetAllBalancesReply{}
   439  	require.NoError(service.GetAllBalances(nil, balanceArgs, reply))
   440  	// The balance should not include the UTXO since it is only partly owned by [addr]
   441  	require.Empty(reply.Balances)
   442  
   443  	env.vm.ctx.Lock.Lock()
   444  
   445  	// A UTXO for a different asset
   446  	otherAssetID := ids.GenerateTestID()
   447  	otherAssetUTXO := &avax.UTXO{
   448  		UTXOID: avax.UTXOID{
   449  			TxID:        ids.GenerateTestID(),
   450  			OutputIndex: 0,
   451  		},
   452  		Asset: avax.Asset{ID: otherAssetID},
   453  		Out: &secp256k1fx.TransferOutput{
   454  			Amt: 1337,
   455  			OutputOwners: secp256k1fx.OutputOwners{
   456  				Threshold: 2,
   457  				Addrs:     []ids.ShortID{addr, ids.GenerateTestShortID()},
   458  			},
   459  		},
   460  	}
   461  	// Insert the UTXO
   462  	env.vm.state.AddUTXO(otherAssetUTXO)
   463  	require.NoError(env.vm.state.Commit())
   464  
   465  	env.vm.ctx.Lock.Unlock()
   466  
   467  	// Check the balance with IncludePartial set to true
   468  	balanceArgs = &GetAllBalancesArgs{
   469  		JSONAddress:    api.JSONAddress{Address: addrStr},
   470  		IncludePartial: true,
   471  	}
   472  	reply = &GetAllBalancesReply{}
   473  	require.NoError(service.GetAllBalances(nil, balanceArgs, reply))
   474  	// The balance should include the UTXO since it is partly owned by [addr]
   475  	require.Len(reply.Balances, 2)
   476  	gotAssetIDs := []string{reply.Balances[0].AssetID, reply.Balances[1].AssetID}
   477  	require.Contains(gotAssetIDs, assetID.String())
   478  	require.Contains(gotAssetIDs, otherAssetID.String())
   479  	gotBalances := []uint64{uint64(reply.Balances[0].Balance), uint64(reply.Balances[1].Balance)}
   480  	require.Contains(gotBalances, uint64(1337))
   481  	require.Contains(gotBalances, uint64(1337*3))
   482  
   483  	// Check the balance with IncludePartial set to false
   484  	balanceArgs = &GetAllBalancesArgs{
   485  		JSONAddress: api.JSONAddress{Address: addrStr},
   486  	}
   487  	reply = &GetAllBalancesReply{}
   488  	require.NoError(service.GetAllBalances(nil, balanceArgs, reply))
   489  	// The balance should include the UTXO since it is partly owned by [addr]
   490  	require.Empty(reply.Balances)
   491  }
   492  
   493  func TestServiceGetTx(t *testing.T) {
   494  	require := require.New(t)
   495  
   496  	env := setup(t, &envConfig{
   497  		fork: latest,
   498  	})
   499  	service := &Service{vm: env.vm}
   500  	env.vm.ctx.Lock.Unlock()
   501  
   502  	txID := env.genesisTx.ID()
   503  
   504  	reply := api.GetTxReply{}
   505  	require.NoError(service.GetTx(nil, &api.GetTxArgs{
   506  		TxID:     txID,
   507  		Encoding: formatting.Hex,
   508  	}, &reply))
   509  
   510  	var txStr string
   511  	require.NoError(json.Unmarshal(reply.Tx, &txStr))
   512  
   513  	txBytes, err := formatting.Decode(reply.Encoding, txStr)
   514  	require.NoError(err)
   515  	require.Equal(env.genesisTx.Bytes(), txBytes)
   516  }
   517  
   518  func TestServiceGetTxJSON_BaseTx(t *testing.T) {
   519  	require := require.New(t)
   520  
   521  	env := setup(t, &envConfig{
   522  		fork: latest,
   523  	})
   524  	service := &Service{vm: env.vm}
   525  	env.vm.ctx.Lock.Unlock()
   526  
   527  	newTx := newAvaxBaseTxWithOutputs(t, env)
   528  	issueAndAccept(require, env.vm, env.issuer, newTx)
   529  
   530  	reply := api.GetTxReply{}
   531  	require.NoError(service.GetTx(nil, &api.GetTxArgs{
   532  		TxID:     newTx.ID(),
   533  		Encoding: formatting.JSON,
   534  	}, &reply))
   535  
   536  	require.Equal(formatting.JSON, reply.Encoding)
   537  
   538  	replyTxBytes, err := json.MarshalIndent(reply.Tx, "", "\t")
   539  	require.NoError(err)
   540  
   541  	expectedReplyTxString := `{
   542  	"unsignedTx": {
   543  		"networkID": 10,
   544  		"blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID",
   545  		"outputs": [
   546  			{
   547  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
   548  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   549  				"output": {
   550  					"addresses": [
   551  						"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
   552  					],
   553  					"amount": 1000,
   554  					"locktime": 0,
   555  					"threshold": 1
   556  				}
   557  			},
   558  			{
   559  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
   560  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   561  				"output": {
   562  					"addresses": [
   563  						"X-testing1d6kkj0qh4wcmus3tk59npwt3rluc6en72ngurd"
   564  					],
   565  					"amount": 48000,
   566  					"locktime": 0,
   567  					"threshold": 1
   568  				}
   569  			}
   570  		],
   571  		"inputs": [
   572  			{
   573  				"txID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
   574  				"outputIndex": 2,
   575  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
   576  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   577  				"input": {
   578  					"amount": 50000,
   579  					"signatureIndices": [
   580  						0
   581  					]
   582  				}
   583  			}
   584  		],
   585  		"memo": "0x0102030405060708"
   586  	},
   587  	"credentials": [
   588  		{
   589  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   590  			"credential": {
   591  				"signatures": [
   592  					"PLACEHOLDER_SIGNATURE"
   593  				]
   594  			}
   595  		}
   596  	],
   597  	"id": "PLACEHOLDER_TX_ID"
   598  }`
   599  
   600  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", newTx.ID().String(), 1)
   601  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", newTx.Unsigned.(*txs.BaseTx).BlockchainID.String(), 1)
   602  
   603  	sigStr, err := formatting.Encode(formatting.HexNC, newTx.Creds[0].Credential.(*secp256k1fx.Credential).Sigs[0][:])
   604  	require.NoError(err)
   605  
   606  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 1)
   607  
   608  	require.Equal(expectedReplyTxString, string(replyTxBytes))
   609  }
   610  
   611  func TestServiceGetTxJSON_ExportTx(t *testing.T) {
   612  	require := require.New(t)
   613  
   614  	env := setup(t, &envConfig{
   615  		fork: latest,
   616  	})
   617  	service := &Service{vm: env.vm}
   618  	env.vm.ctx.Lock.Unlock()
   619  
   620  	newTx := buildTestExportTx(t, env, env.vm.ctx.CChainID)
   621  	issueAndAccept(require, env.vm, env.issuer, newTx)
   622  
   623  	reply := api.GetTxReply{}
   624  	require.NoError(service.GetTx(nil, &api.GetTxArgs{
   625  		TxID:     newTx.ID(),
   626  		Encoding: formatting.JSON,
   627  	}, &reply))
   628  
   629  	require.Equal(formatting.JSON, reply.Encoding)
   630  	replyTxBytes, err := json.MarshalIndent(reply.Tx, "", "\t")
   631  	require.NoError(err)
   632  
   633  	expectedReplyTxString := `{
   634  	"unsignedTx": {
   635  		"networkID": 10,
   636  		"blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID",
   637  		"outputs": [
   638  			{
   639  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
   640  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   641  				"output": {
   642  					"addresses": [
   643  						"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
   644  					],
   645  					"amount": 48000,
   646  					"locktime": 0,
   647  					"threshold": 1
   648  				}
   649  			}
   650  		],
   651  		"inputs": [
   652  			{
   653  				"txID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
   654  				"outputIndex": 2,
   655  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
   656  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   657  				"input": {
   658  					"amount": 50000,
   659  					"signatureIndices": [
   660  						0
   661  					]
   662  				}
   663  			}
   664  		],
   665  		"memo": "0x",
   666  		"destinationChain": "2mcwQKiD8VEspmMJpL1dc7okQQ5dDVAWeCBZ7FWBFAbxpv3t7w",
   667  		"exportedOutputs": [
   668  			{
   669  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
   670  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   671  				"output": {
   672  					"addresses": [
   673  						"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
   674  					],
   675  					"amount": 1000,
   676  					"locktime": 0,
   677  					"threshold": 1
   678  				}
   679  			}
   680  		]
   681  	},
   682  	"credentials": [
   683  		{
   684  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   685  			"credential": {
   686  				"signatures": [
   687  					"PLACEHOLDER_SIGNATURE"
   688  				]
   689  			}
   690  		}
   691  	],
   692  	"id": "PLACEHOLDER_TX_ID"
   693  }`
   694  
   695  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", newTx.ID().String(), 1)
   696  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", newTx.Unsigned.(*txs.ExportTx).BlockchainID.String(), 1)
   697  
   698  	sigStr, err := formatting.Encode(formatting.HexNC, newTx.Creds[0].Credential.(*secp256k1fx.Credential).Sigs[0][:])
   699  	require.NoError(err)
   700  
   701  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 1)
   702  
   703  	require.Equal(expectedReplyTxString, string(replyTxBytes))
   704  }
   705  
   706  func TestServiceGetTxJSON_CreateAssetTx(t *testing.T) {
   707  	require := require.New(t)
   708  
   709  	env := setup(t, &envConfig{
   710  		fork: latest,
   711  		additionalFxs: []*common.Fx{{
   712  			ID: propertyfx.ID,
   713  			Fx: &propertyfx.Fx{},
   714  		}},
   715  	})
   716  	service := &Service{vm: env.vm}
   717  	env.vm.ctx.Lock.Unlock()
   718  
   719  	initialStates := map[uint32][]verify.State{
   720  		0: {
   721  			&nftfx.MintOutput{
   722  				OutputOwners: secp256k1fx.OutputOwners{
   723  					Threshold: 1,
   724  					Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
   725  				},
   726  			}, &secp256k1fx.MintOutput{
   727  				OutputOwners: secp256k1fx.OutputOwners{
   728  					Threshold: 1,
   729  					Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
   730  				},
   731  			},
   732  		},
   733  		1: {
   734  			&nftfx.MintOutput{
   735  				GroupID: 1,
   736  				OutputOwners: secp256k1fx.OutputOwners{
   737  					Threshold: 1,
   738  					Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
   739  				},
   740  			},
   741  			&nftfx.MintOutput{
   742  				GroupID: 2,
   743  				OutputOwners: secp256k1fx.OutputOwners{
   744  					Threshold: 1,
   745  					Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
   746  				},
   747  			},
   748  		},
   749  		2: {
   750  			&propertyfx.MintOutput{
   751  				OutputOwners: secp256k1fx.OutputOwners{
   752  					Threshold: 1,
   753  					Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
   754  				},
   755  			},
   756  			&propertyfx.MintOutput{
   757  				OutputOwners: secp256k1fx.OutputOwners{
   758  					Threshold: 1,
   759  					Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
   760  				},
   761  			},
   762  		},
   763  	}
   764  	createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env, initialStates)
   765  	issueAndAccept(require, env.vm, env.issuer, createAssetTx)
   766  
   767  	reply := api.GetTxReply{}
   768  	require.NoError(service.GetTx(nil, &api.GetTxArgs{
   769  		TxID:     createAssetTx.ID(),
   770  		Encoding: formatting.JSON,
   771  	}, &reply))
   772  
   773  	require.Equal(formatting.JSON, reply.Encoding)
   774  
   775  	replyTxBytes, err := json.MarshalIndent(reply.Tx, "", "\t")
   776  	require.NoError(err)
   777  
   778  	expectedReplyTxString := `{
   779  	"unsignedTx": {
   780  		"networkID": 10,
   781  		"blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID",
   782  		"outputs": [
   783  			{
   784  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
   785  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   786  				"output": {
   787  					"addresses": [
   788  						"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
   789  					],
   790  					"amount": 49000,
   791  					"locktime": 0,
   792  					"threshold": 1
   793  				}
   794  			}
   795  		],
   796  		"inputs": [
   797  			{
   798  				"txID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
   799  				"outputIndex": 2,
   800  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
   801  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   802  				"input": {
   803  					"amount": 50000,
   804  					"signatureIndices": [
   805  						0
   806  					]
   807  				}
   808  			}
   809  		],
   810  		"memo": "0x",
   811  		"name": "Team Rocket",
   812  		"symbol": "TR",
   813  		"denomination": 0,
   814  		"initialStates": [
   815  			{
   816  				"fxIndex": 0,
   817  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   818  				"outputs": [
   819  					{
   820  						"addresses": [
   821  							"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
   822  						],
   823  						"locktime": 0,
   824  						"threshold": 1
   825  					},
   826  					{
   827  						"addresses": [
   828  							"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
   829  						],
   830  						"groupID": 0,
   831  						"locktime": 0,
   832  						"threshold": 1
   833  					}
   834  				]
   835  			},
   836  			{
   837  				"fxIndex": 1,
   838  				"fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT",
   839  				"outputs": [
   840  					{
   841  						"addresses": [
   842  							"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
   843  						],
   844  						"groupID": 1,
   845  						"locktime": 0,
   846  						"threshold": 1
   847  					},
   848  					{
   849  						"addresses": [
   850  							"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
   851  						],
   852  						"groupID": 2,
   853  						"locktime": 0,
   854  						"threshold": 1
   855  					}
   856  				]
   857  			},
   858  			{
   859  				"fxIndex": 2,
   860  				"fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy",
   861  				"outputs": [
   862  					{
   863  						"addresses": [
   864  							"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
   865  						],
   866  						"locktime": 0,
   867  						"threshold": 1
   868  					},
   869  					{
   870  						"addresses": [
   871  							"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
   872  						],
   873  						"locktime": 0,
   874  						"threshold": 1
   875  					}
   876  				]
   877  			}
   878  		]
   879  	},
   880  	"credentials": [
   881  		{
   882  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   883  			"credential": {
   884  				"signatures": [
   885  					"PLACEHOLDER_SIGNATURE"
   886  				]
   887  			}
   888  		}
   889  	],
   890  	"id": "PLACEHOLDER_TX_ID"
   891  }`
   892  
   893  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", createAssetTx.ID().String(), 1)
   894  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", createAssetTx.Unsigned.(*txs.CreateAssetTx).BlockchainID.String(), 1)
   895  
   896  	sigStr, err := formatting.Encode(formatting.HexNC, createAssetTx.Creds[0].Credential.(*secp256k1fx.Credential).Sigs[0][:])
   897  	require.NoError(err)
   898  
   899  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 1)
   900  
   901  	require.Equal(expectedReplyTxString, string(replyTxBytes))
   902  }
   903  
   904  func TestServiceGetTxJSON_OperationTxWithNftxMintOp(t *testing.T) {
   905  	require := require.New(t)
   906  
   907  	env := setup(t, &envConfig{
   908  		fork: latest,
   909  		additionalFxs: []*common.Fx{{
   910  			ID: propertyfx.ID,
   911  			Fx: &propertyfx.Fx{},
   912  		}},
   913  	})
   914  	service := &Service{vm: env.vm}
   915  	env.vm.ctx.Lock.Unlock()
   916  
   917  	key := keys[0]
   918  	initialStates := map[uint32][]verify.State{
   919  		1: {
   920  			&nftfx.MintOutput{
   921  				GroupID: 1,
   922  				OutputOwners: secp256k1fx.OutputOwners{
   923  					Threshold: 1,
   924  					Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
   925  				},
   926  			},
   927  			&nftfx.MintOutput{
   928  				GroupID: 2,
   929  				OutputOwners: secp256k1fx.OutputOwners{
   930  					Threshold: 1,
   931  					Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
   932  				},
   933  			},
   934  		},
   935  	}
   936  	createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env, initialStates)
   937  	issueAndAccept(require, env.vm, env.issuer, createAssetTx)
   938  
   939  	op := buildNFTxMintOp(createAssetTx, key, 1, 1)
   940  	mintNFTTx := buildOperationTxWithOps(t, env, op)
   941  	issueAndAccept(require, env.vm, env.issuer, mintNFTTx)
   942  
   943  	reply := api.GetTxReply{}
   944  	require.NoError(service.GetTx(nil, &api.GetTxArgs{
   945  		TxID:     mintNFTTx.ID(),
   946  		Encoding: formatting.JSON,
   947  	}, &reply))
   948  
   949  	require.Equal(formatting.JSON, reply.Encoding)
   950  
   951  	replyTxBytes, err := json.MarshalIndent(reply.Tx, "", "\t")
   952  	require.NoError(err)
   953  
   954  	expectedReplyTxString := `{
   955  	"unsignedTx": {
   956  		"networkID": 10,
   957  		"blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID",
   958  		"outputs": [
   959  			{
   960  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
   961  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   962  				"output": {
   963  					"addresses": [
   964  						"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
   965  					],
   966  					"amount": 48000,
   967  					"locktime": 0,
   968  					"threshold": 1
   969  				}
   970  			}
   971  		],
   972  		"inputs": [
   973  			{
   974  				"txID": "rSiY2aqcahSU5vyJeMiNBnwtPwfJFxsxskAGbU3HxHvAkrdpy",
   975  				"outputIndex": 0,
   976  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
   977  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   978  				"input": {
   979  					"amount": 49000,
   980  					"signatureIndices": [
   981  						0
   982  					]
   983  				}
   984  			}
   985  		],
   986  		"memo": "0x",
   987  		"operations": [
   988  			{
   989  				"assetID": "PLACEHOLDER_CREATE_ASSET_TX_ID",
   990  				"inputIDs": [
   991  					{
   992  						"txID": "PLACEHOLDER_CREATE_ASSET_TX_ID",
   993  						"outputIndex": 1
   994  					}
   995  				],
   996  				"fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT",
   997  				"operation": {
   998  					"mintInput": {
   999  						"signatureIndices": [
  1000  							0
  1001  						]
  1002  					},
  1003  					"groupID": 1,
  1004  					"payload": "0x68656c6c6f",
  1005  					"outputs": [
  1006  						{
  1007  							"addresses": [
  1008  								"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
  1009  							],
  1010  							"locktime": 0,
  1011  							"threshold": 1
  1012  						}
  1013  					]
  1014  				}
  1015  			}
  1016  		]
  1017  	},
  1018  	"credentials": [
  1019  		{
  1020  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1021  			"credential": {
  1022  				"signatures": [
  1023  					"PLACEHOLDER_SIGNATURE"
  1024  				]
  1025  			}
  1026  		},
  1027  		{
  1028  			"fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT",
  1029  			"credential": {
  1030  				"signatures": [
  1031  					"PLACEHOLDER_SIGNATURE"
  1032  				]
  1033  			}
  1034  		}
  1035  	],
  1036  	"id": "PLACEHOLDER_TX_ID"
  1037  }`
  1038  
  1039  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_CREATE_ASSET_TX_ID", createAssetTx.ID().String(), 2)
  1040  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", mintNFTTx.ID().String(), 1)
  1041  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", mintNFTTx.Unsigned.(*txs.OperationTx).BlockchainID.String(), 1)
  1042  
  1043  	sigStr, err := formatting.Encode(formatting.HexNC, mintNFTTx.Creds[1].Credential.(*nftfx.Credential).Sigs[0][:])
  1044  	require.NoError(err)
  1045  
  1046  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 2)
  1047  
  1048  	require.Equal(expectedReplyTxString, string(replyTxBytes))
  1049  }
  1050  
  1051  func TestServiceGetTxJSON_OperationTxWithMultipleNftxMintOp(t *testing.T) {
  1052  	require := require.New(t)
  1053  
  1054  	env := setup(t, &envConfig{
  1055  		fork: latest,
  1056  		additionalFxs: []*common.Fx{{
  1057  			ID: propertyfx.ID,
  1058  			Fx: &propertyfx.Fx{},
  1059  		}},
  1060  	})
  1061  	service := &Service{vm: env.vm}
  1062  	env.vm.ctx.Lock.Unlock()
  1063  
  1064  	key := keys[0]
  1065  	initialStates := map[uint32][]verify.State{
  1066  		0: {
  1067  			&nftfx.MintOutput{
  1068  				GroupID: 0,
  1069  				OutputOwners: secp256k1fx.OutputOwners{
  1070  					Threshold: 1,
  1071  					Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
  1072  				},
  1073  			},
  1074  		},
  1075  		1: {
  1076  			&nftfx.MintOutput{
  1077  				GroupID: 1,
  1078  				OutputOwners: secp256k1fx.OutputOwners{
  1079  					Threshold: 1,
  1080  					Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
  1081  				},
  1082  			},
  1083  		},
  1084  	}
  1085  	createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env, initialStates)
  1086  	issueAndAccept(require, env.vm, env.issuer, createAssetTx)
  1087  
  1088  	mintOp1 := buildNFTxMintOp(createAssetTx, key, 1, 0)
  1089  	mintOp2 := buildNFTxMintOp(createAssetTx, key, 2, 1)
  1090  	mintNFTTx := buildOperationTxWithOps(t, env, mintOp1, mintOp2)
  1091  	issueAndAccept(require, env.vm, env.issuer, mintNFTTx)
  1092  
  1093  	reply := api.GetTxReply{}
  1094  	require.NoError(service.GetTx(nil, &api.GetTxArgs{
  1095  		TxID:     mintNFTTx.ID(),
  1096  		Encoding: formatting.JSON,
  1097  	}, &reply))
  1098  
  1099  	require.Equal(formatting.JSON, reply.Encoding)
  1100  
  1101  	replyTxBytes, err := json.MarshalIndent(reply.Tx, "", "\t")
  1102  	require.NoError(err)
  1103  
  1104  	expectedReplyTxString := `{
  1105  	"unsignedTx": {
  1106  		"networkID": 10,
  1107  		"blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID",
  1108  		"outputs": [
  1109  			{
  1110  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
  1111  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1112  				"output": {
  1113  					"addresses": [
  1114  						"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
  1115  					],
  1116  					"amount": 48000,
  1117  					"locktime": 0,
  1118  					"threshold": 1
  1119  				}
  1120  			}
  1121  		],
  1122  		"inputs": [
  1123  			{
  1124  				"txID": "BBhSA95iv6ueXc7xrMSka1bByBqcwJxyvMiyjy5H8ccAgxy4P",
  1125  				"outputIndex": 0,
  1126  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
  1127  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1128  				"input": {
  1129  					"amount": 49000,
  1130  					"signatureIndices": [
  1131  						0
  1132  					]
  1133  				}
  1134  			}
  1135  		],
  1136  		"memo": "0x",
  1137  		"operations": [
  1138  			{
  1139  				"assetID": "PLACEHOLDER_CREATE_ASSET_TX_ID",
  1140  				"inputIDs": [
  1141  					{
  1142  						"txID": "PLACEHOLDER_CREATE_ASSET_TX_ID",
  1143  						"outputIndex": 1
  1144  					}
  1145  				],
  1146  				"fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT",
  1147  				"operation": {
  1148  					"mintInput": {
  1149  						"signatureIndices": [
  1150  							0
  1151  						]
  1152  					},
  1153  					"groupID": 0,
  1154  					"payload": "0x68656c6c6f",
  1155  					"outputs": [
  1156  						{
  1157  							"addresses": [
  1158  								"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
  1159  							],
  1160  							"locktime": 0,
  1161  							"threshold": 1
  1162  						}
  1163  					]
  1164  				}
  1165  			},
  1166  			{
  1167  				"assetID": "PLACEHOLDER_CREATE_ASSET_TX_ID",
  1168  				"inputIDs": [
  1169  					{
  1170  						"txID": "PLACEHOLDER_CREATE_ASSET_TX_ID",
  1171  						"outputIndex": 2
  1172  					}
  1173  				],
  1174  				"fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT",
  1175  				"operation": {
  1176  					"mintInput": {
  1177  						"signatureIndices": [
  1178  							0
  1179  						]
  1180  					},
  1181  					"groupID": 1,
  1182  					"payload": "0x68656c6c6f",
  1183  					"outputs": [
  1184  						{
  1185  							"addresses": [
  1186  								"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
  1187  							],
  1188  							"locktime": 0,
  1189  							"threshold": 1
  1190  						}
  1191  					]
  1192  				}
  1193  			}
  1194  		]
  1195  	},
  1196  	"credentials": [
  1197  		{
  1198  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1199  			"credential": {
  1200  				"signatures": [
  1201  					"PLACEHOLDER_SIGNATURE"
  1202  				]
  1203  			}
  1204  		},
  1205  		{
  1206  			"fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT",
  1207  			"credential": {
  1208  				"signatures": [
  1209  					"PLACEHOLDER_SIGNATURE"
  1210  				]
  1211  			}
  1212  		},
  1213  		{
  1214  			"fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT",
  1215  			"credential": {
  1216  				"signatures": [
  1217  					"PLACEHOLDER_SIGNATURE"
  1218  				]
  1219  			}
  1220  		}
  1221  	],
  1222  	"id": "PLACEHOLDER_TX_ID"
  1223  }`
  1224  
  1225  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_CREATE_ASSET_TX_ID", createAssetTx.ID().String(), 4)
  1226  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", mintNFTTx.ID().String(), 1)
  1227  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", mintNFTTx.Unsigned.(*txs.OperationTx).BlockchainID.String(), 1)
  1228  
  1229  	sigStr, err := formatting.Encode(formatting.HexNC, mintNFTTx.Creds[1].Credential.(*nftfx.Credential).Sigs[0][:])
  1230  	require.NoError(err)
  1231  
  1232  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 3)
  1233  
  1234  	require.Equal(expectedReplyTxString, string(replyTxBytes))
  1235  }
  1236  
  1237  func TestServiceGetTxJSON_OperationTxWithSecpMintOp(t *testing.T) {
  1238  	require := require.New(t)
  1239  
  1240  	env := setup(t, &envConfig{
  1241  		fork: latest,
  1242  		additionalFxs: []*common.Fx{{
  1243  			ID: propertyfx.ID,
  1244  			Fx: &propertyfx.Fx{},
  1245  		}},
  1246  	})
  1247  	service := &Service{vm: env.vm}
  1248  	env.vm.ctx.Lock.Unlock()
  1249  
  1250  	key := keys[0]
  1251  	initialStates := map[uint32][]verify.State{
  1252  		0: {
  1253  			&nftfx.MintOutput{
  1254  				OutputOwners: secp256k1fx.OutputOwners{
  1255  					Threshold: 1,
  1256  					Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
  1257  				},
  1258  			}, &secp256k1fx.MintOutput{
  1259  				OutputOwners: secp256k1fx.OutputOwners{
  1260  					Threshold: 1,
  1261  					Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
  1262  				},
  1263  			},
  1264  		},
  1265  	}
  1266  	createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env, initialStates)
  1267  	issueAndAccept(require, env.vm, env.issuer, createAssetTx)
  1268  
  1269  	op := buildSecpMintOp(createAssetTx, key, 1)
  1270  	mintSecpOpTx := buildOperationTxWithOps(t, env, op)
  1271  	issueAndAccept(require, env.vm, env.issuer, mintSecpOpTx)
  1272  
  1273  	reply := api.GetTxReply{}
  1274  	require.NoError(service.GetTx(nil, &api.GetTxArgs{
  1275  		TxID:     mintSecpOpTx.ID(),
  1276  		Encoding: formatting.JSON,
  1277  	}, &reply))
  1278  
  1279  	require.Equal(formatting.JSON, reply.Encoding)
  1280  
  1281  	replyTxBytes, err := json.MarshalIndent(reply.Tx, "", "\t")
  1282  	require.NoError(err)
  1283  
  1284  	expectedReplyTxString := `{
  1285  	"unsignedTx": {
  1286  		"networkID": 10,
  1287  		"blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID",
  1288  		"outputs": [
  1289  			{
  1290  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
  1291  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1292  				"output": {
  1293  					"addresses": [
  1294  						"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
  1295  					],
  1296  					"amount": 48000,
  1297  					"locktime": 0,
  1298  					"threshold": 1
  1299  				}
  1300  			}
  1301  		],
  1302  		"inputs": [
  1303  			{
  1304  				"txID": "2YhAg3XUdub5syHHePZG7q3yFjKAy7ahsvQDxq5SMrYbN1s5Gn",
  1305  				"outputIndex": 0,
  1306  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
  1307  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1308  				"input": {
  1309  					"amount": 49000,
  1310  					"signatureIndices": [
  1311  						0
  1312  					]
  1313  				}
  1314  			}
  1315  		],
  1316  		"memo": "0x",
  1317  		"operations": [
  1318  			{
  1319  				"assetID": "PLACEHOLDER_CREATE_ASSET_TX_ID",
  1320  				"inputIDs": [
  1321  					{
  1322  						"txID": "PLACEHOLDER_CREATE_ASSET_TX_ID",
  1323  						"outputIndex": 1
  1324  					}
  1325  				],
  1326  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1327  				"operation": {
  1328  					"mintInput": {
  1329  						"signatureIndices": [
  1330  							0
  1331  						]
  1332  					},
  1333  					"mintOutput": {
  1334  						"addresses": [
  1335  							"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
  1336  						],
  1337  						"locktime": 0,
  1338  						"threshold": 1
  1339  					},
  1340  					"transferOutput": {
  1341  						"addresses": [
  1342  							"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
  1343  						],
  1344  						"amount": 1,
  1345  						"locktime": 0,
  1346  						"threshold": 1
  1347  					}
  1348  				}
  1349  			}
  1350  		]
  1351  	},
  1352  	"credentials": [
  1353  		{
  1354  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1355  			"credential": {
  1356  				"signatures": [
  1357  					"PLACEHOLDER_SIGNATURE"
  1358  				]
  1359  			}
  1360  		},
  1361  		{
  1362  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1363  			"credential": {
  1364  				"signatures": [
  1365  					"PLACEHOLDER_SIGNATURE"
  1366  				]
  1367  			}
  1368  		}
  1369  	],
  1370  	"id": "PLACEHOLDER_TX_ID"
  1371  }`
  1372  
  1373  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_CREATE_ASSET_TX_ID", createAssetTx.ID().String(), 2)
  1374  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", mintSecpOpTx.ID().String(), 1)
  1375  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", mintSecpOpTx.Unsigned.(*txs.OperationTx).BlockchainID.String(), 1)
  1376  
  1377  	sigStr, err := formatting.Encode(formatting.HexNC, mintSecpOpTx.Creds[0].Credential.(*secp256k1fx.Credential).Sigs[0][:])
  1378  	require.NoError(err)
  1379  
  1380  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 2)
  1381  
  1382  	require.Equal(expectedReplyTxString, string(replyTxBytes))
  1383  }
  1384  
  1385  func TestServiceGetTxJSON_OperationTxWithMultipleSecpMintOp(t *testing.T) {
  1386  	require := require.New(t)
  1387  
  1388  	env := setup(t, &envConfig{
  1389  		fork: durango,
  1390  		additionalFxs: []*common.Fx{{
  1391  			ID: propertyfx.ID,
  1392  			Fx: &propertyfx.Fx{},
  1393  		}},
  1394  	})
  1395  	service := &Service{vm: env.vm}
  1396  	env.vm.ctx.Lock.Unlock()
  1397  
  1398  	key := keys[0]
  1399  	initialStates := map[uint32][]verify.State{
  1400  		0: {
  1401  			&secp256k1fx.MintOutput{
  1402  				OutputOwners: secp256k1fx.OutputOwners{
  1403  					Threshold: 1,
  1404  					Addrs:     []ids.ShortID{key.PublicKey().Address()},
  1405  				},
  1406  			},
  1407  		},
  1408  		1: {
  1409  			&secp256k1fx.MintOutput{
  1410  				OutputOwners: secp256k1fx.OutputOwners{
  1411  					Threshold: 1,
  1412  					Addrs:     []ids.ShortID{key.PublicKey().Address()},
  1413  				},
  1414  			},
  1415  		},
  1416  	}
  1417  	createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env, initialStates)
  1418  	issueAndAccept(require, env.vm, env.issuer, createAssetTx)
  1419  
  1420  	op1 := buildSecpMintOp(createAssetTx, key, 1)
  1421  	op2 := buildSecpMintOp(createAssetTx, key, 2)
  1422  	mintSecpOpTx := buildOperationTxWithOps(t, env, op1, op2)
  1423  	issueAndAccept(require, env.vm, env.issuer, mintSecpOpTx)
  1424  
  1425  	reply := api.GetTxReply{}
  1426  	require.NoError(service.GetTx(nil, &api.GetTxArgs{
  1427  		TxID:     mintSecpOpTx.ID(),
  1428  		Encoding: formatting.JSON,
  1429  	}, &reply))
  1430  
  1431  	require.Equal(formatting.JSON, reply.Encoding)
  1432  
  1433  	replyTxBytes, err := json.MarshalIndent(reply.Tx, "", "\t")
  1434  	require.NoError(err)
  1435  
  1436  	expectedReplyTxString := `{
  1437  	"unsignedTx": {
  1438  		"networkID": 10,
  1439  		"blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID",
  1440  		"outputs": [
  1441  			{
  1442  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
  1443  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1444  				"output": {
  1445  					"addresses": [
  1446  						"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
  1447  					],
  1448  					"amount": 48000,
  1449  					"locktime": 0,
  1450  					"threshold": 1
  1451  				}
  1452  			}
  1453  		],
  1454  		"inputs": [
  1455  			{
  1456  				"txID": "2vxorPLUw5sneb7Mdhhjuws3H5AqaDp1V8ETz6fEuzvn835rVX",
  1457  				"outputIndex": 0,
  1458  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
  1459  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1460  				"input": {
  1461  					"amount": 49000,
  1462  					"signatureIndices": [
  1463  						0
  1464  					]
  1465  				}
  1466  			}
  1467  		],
  1468  		"memo": "0x",
  1469  		"operations": [
  1470  			{
  1471  				"assetID": "PLACEHOLDER_CREATE_ASSET_TX_ID",
  1472  				"inputIDs": [
  1473  					{
  1474  						"txID": "PLACEHOLDER_CREATE_ASSET_TX_ID",
  1475  						"outputIndex": 1
  1476  					}
  1477  				],
  1478  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1479  				"operation": {
  1480  					"mintInput": {
  1481  						"signatureIndices": [
  1482  							0
  1483  						]
  1484  					},
  1485  					"mintOutput": {
  1486  						"addresses": [
  1487  							"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
  1488  						],
  1489  						"locktime": 0,
  1490  						"threshold": 1
  1491  					},
  1492  					"transferOutput": {
  1493  						"addresses": [
  1494  							"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
  1495  						],
  1496  						"amount": 1,
  1497  						"locktime": 0,
  1498  						"threshold": 1
  1499  					}
  1500  				}
  1501  			},
  1502  			{
  1503  				"assetID": "PLACEHOLDER_CREATE_ASSET_TX_ID",
  1504  				"inputIDs": [
  1505  					{
  1506  						"txID": "PLACEHOLDER_CREATE_ASSET_TX_ID",
  1507  						"outputIndex": 2
  1508  					}
  1509  				],
  1510  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1511  				"operation": {
  1512  					"mintInput": {
  1513  						"signatureIndices": [
  1514  							0
  1515  						]
  1516  					},
  1517  					"mintOutput": {
  1518  						"addresses": [
  1519  							"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
  1520  						],
  1521  						"locktime": 0,
  1522  						"threshold": 1
  1523  					},
  1524  					"transferOutput": {
  1525  						"addresses": [
  1526  							"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
  1527  						],
  1528  						"amount": 1,
  1529  						"locktime": 0,
  1530  						"threshold": 1
  1531  					}
  1532  				}
  1533  			}
  1534  		]
  1535  	},
  1536  	"credentials": [
  1537  		{
  1538  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1539  			"credential": {
  1540  				"signatures": [
  1541  					"PLACEHOLDER_SIGNATURE"
  1542  				]
  1543  			}
  1544  		},
  1545  		{
  1546  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1547  			"credential": {
  1548  				"signatures": [
  1549  					"PLACEHOLDER_SIGNATURE"
  1550  				]
  1551  			}
  1552  		},
  1553  		{
  1554  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1555  			"credential": {
  1556  				"signatures": [
  1557  					"PLACEHOLDER_SIGNATURE"
  1558  				]
  1559  			}
  1560  		}
  1561  	],
  1562  	"id": "PLACEHOLDER_TX_ID"
  1563  }`
  1564  
  1565  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_CREATE_ASSET_TX_ID", createAssetTx.ID().String(), 4)
  1566  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", mintSecpOpTx.ID().String(), 1)
  1567  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", mintSecpOpTx.Unsigned.(*txs.OperationTx).BlockchainID.String(), 1)
  1568  
  1569  	sigStr, err := formatting.Encode(formatting.HexNC, mintSecpOpTx.Creds[0].Credential.(*secp256k1fx.Credential).Sigs[0][:])
  1570  	require.NoError(err)
  1571  
  1572  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 3)
  1573  
  1574  	require.Equal(expectedReplyTxString, string(replyTxBytes))
  1575  }
  1576  
  1577  func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOp(t *testing.T) {
  1578  	require := require.New(t)
  1579  
  1580  	env := setup(t, &envConfig{
  1581  		fork: latest,
  1582  		additionalFxs: []*common.Fx{{
  1583  			ID: propertyfx.ID,
  1584  			Fx: &propertyfx.Fx{},
  1585  		}},
  1586  	})
  1587  	service := &Service{vm: env.vm}
  1588  	env.vm.ctx.Lock.Unlock()
  1589  
  1590  	key := keys[0]
  1591  	initialStates := map[uint32][]verify.State{
  1592  		2: {
  1593  			&propertyfx.MintOutput{
  1594  				OutputOwners: secp256k1fx.OutputOwners{
  1595  					Threshold: 1,
  1596  					Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
  1597  				},
  1598  			},
  1599  		},
  1600  	}
  1601  	createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env, initialStates)
  1602  	issueAndAccept(require, env.vm, env.issuer, createAssetTx)
  1603  
  1604  	op := buildPropertyFxMintOp(createAssetTx, key, 1)
  1605  	mintPropertyFxOpTx := buildOperationTxWithOps(t, env, op)
  1606  	issueAndAccept(require, env.vm, env.issuer, mintPropertyFxOpTx)
  1607  
  1608  	reply := api.GetTxReply{}
  1609  	require.NoError(service.GetTx(nil, &api.GetTxArgs{
  1610  		TxID:     mintPropertyFxOpTx.ID(),
  1611  		Encoding: formatting.JSON,
  1612  	}, &reply))
  1613  
  1614  	require.Equal(formatting.JSON, reply.Encoding)
  1615  
  1616  	replyTxBytes, err := json.MarshalIndent(reply.Tx, "", "\t")
  1617  	require.NoError(err)
  1618  
  1619  	expectedReplyTxString := `{
  1620  	"unsignedTx": {
  1621  		"networkID": 10,
  1622  		"blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID",
  1623  		"outputs": [
  1624  			{
  1625  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
  1626  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1627  				"output": {
  1628  					"addresses": [
  1629  						"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
  1630  					],
  1631  					"amount": 48000,
  1632  					"locktime": 0,
  1633  					"threshold": 1
  1634  				}
  1635  			}
  1636  		],
  1637  		"inputs": [
  1638  			{
  1639  				"txID": "nNUGBjszswU3ZmhCb8hBNWmg335UZqGWmNrYTAGyMF4bFpMXm",
  1640  				"outputIndex": 0,
  1641  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
  1642  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1643  				"input": {
  1644  					"amount": 49000,
  1645  					"signatureIndices": [
  1646  						0
  1647  					]
  1648  				}
  1649  			}
  1650  		],
  1651  		"memo": "0x",
  1652  		"operations": [
  1653  			{
  1654  				"assetID": "PLACEHOLDER_CREATE_ASSET_TX_ID",
  1655  				"inputIDs": [
  1656  					{
  1657  						"txID": "PLACEHOLDER_CREATE_ASSET_TX_ID",
  1658  						"outputIndex": 1
  1659  					}
  1660  				],
  1661  				"fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy",
  1662  				"operation": {
  1663  					"mintInput": {
  1664  						"signatureIndices": [
  1665  							0
  1666  						]
  1667  					},
  1668  					"mintOutput": {
  1669  						"addresses": [
  1670  							"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
  1671  						],
  1672  						"locktime": 0,
  1673  						"threshold": 1
  1674  					},
  1675  					"ownedOutput": {
  1676  						"addresses": [],
  1677  						"locktime": 0,
  1678  						"threshold": 0
  1679  					}
  1680  				}
  1681  			}
  1682  		]
  1683  	},
  1684  	"credentials": [
  1685  		{
  1686  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1687  			"credential": {
  1688  				"signatures": [
  1689  					"PLACEHOLDER_SIGNATURE"
  1690  				]
  1691  			}
  1692  		},
  1693  		{
  1694  			"fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy",
  1695  			"credential": {
  1696  				"signatures": [
  1697  					"PLACEHOLDER_SIGNATURE"
  1698  				]
  1699  			}
  1700  		}
  1701  	],
  1702  	"id": "PLACEHOLDER_TX_ID"
  1703  }`
  1704  
  1705  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_CREATE_ASSET_TX_ID", createAssetTx.ID().String(), 2)
  1706  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", mintPropertyFxOpTx.ID().String(), 1)
  1707  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", mintPropertyFxOpTx.Unsigned.(*txs.OperationTx).BlockchainID.String(), 1)
  1708  
  1709  	sigStr, err := formatting.Encode(formatting.HexNC, mintPropertyFxOpTx.Creds[1].Credential.(*propertyfx.Credential).Sigs[0][:])
  1710  	require.NoError(err)
  1711  
  1712  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 2)
  1713  
  1714  	require.Equal(expectedReplyTxString, string(replyTxBytes))
  1715  }
  1716  
  1717  func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOpMultiple(t *testing.T) {
  1718  	require := require.New(t)
  1719  
  1720  	env := setup(t, &envConfig{
  1721  		fork: latest,
  1722  		additionalFxs: []*common.Fx{{
  1723  			ID: propertyfx.ID,
  1724  			Fx: &propertyfx.Fx{},
  1725  		}},
  1726  	})
  1727  	service := &Service{vm: env.vm}
  1728  	env.vm.ctx.Lock.Unlock()
  1729  
  1730  	key := keys[0]
  1731  	initialStates := map[uint32][]verify.State{
  1732  		2: {
  1733  			&propertyfx.MintOutput{
  1734  				OutputOwners: secp256k1fx.OutputOwners{
  1735  					Threshold: 1,
  1736  					Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
  1737  				},
  1738  			},
  1739  			&propertyfx.MintOutput{
  1740  				OutputOwners: secp256k1fx.OutputOwners{
  1741  					Threshold: 1,
  1742  					Addrs:     []ids.ShortID{keys[0].PublicKey().Address()},
  1743  				},
  1744  			},
  1745  		},
  1746  	}
  1747  	createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env, initialStates)
  1748  	issueAndAccept(require, env.vm, env.issuer, createAssetTx)
  1749  
  1750  	op1 := buildPropertyFxMintOp(createAssetTx, key, 1)
  1751  	op2 := buildPropertyFxMintOp(createAssetTx, key, 2)
  1752  	mintPropertyFxOpTx := buildOperationTxWithOps(t, env, op1, op2)
  1753  	issueAndAccept(require, env.vm, env.issuer, mintPropertyFxOpTx)
  1754  
  1755  	reply := api.GetTxReply{}
  1756  	require.NoError(service.GetTx(nil, &api.GetTxArgs{
  1757  		TxID:     mintPropertyFxOpTx.ID(),
  1758  		Encoding: formatting.JSON,
  1759  	}, &reply))
  1760  
  1761  	require.Equal(formatting.JSON, reply.Encoding)
  1762  
  1763  	replyTxBytes, err := json.MarshalIndent(reply.Tx, "", "\t")
  1764  	require.NoError(err)
  1765  
  1766  	expectedReplyTxString := `{
  1767  	"unsignedTx": {
  1768  		"networkID": 10,
  1769  		"blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID",
  1770  		"outputs": [
  1771  			{
  1772  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
  1773  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1774  				"output": {
  1775  					"addresses": [
  1776  						"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
  1777  					],
  1778  					"amount": 48000,
  1779  					"locktime": 0,
  1780  					"threshold": 1
  1781  				}
  1782  			}
  1783  		],
  1784  		"inputs": [
  1785  			{
  1786  				"txID": "2NV5AGoQQHVRY6VkT8sht8bhZDHR7uwta7fk7JwAZpacqMRWCa",
  1787  				"outputIndex": 0,
  1788  				"assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ",
  1789  				"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1790  				"input": {
  1791  					"amount": 49000,
  1792  					"signatureIndices": [
  1793  						0
  1794  					]
  1795  				}
  1796  			}
  1797  		],
  1798  		"memo": "0x",
  1799  		"operations": [
  1800  			{
  1801  				"assetID": "PLACEHOLDER_CREATE_ASSET_TX_ID",
  1802  				"inputIDs": [
  1803  					{
  1804  						"txID": "PLACEHOLDER_CREATE_ASSET_TX_ID",
  1805  						"outputIndex": 1
  1806  					}
  1807  				],
  1808  				"fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy",
  1809  				"operation": {
  1810  					"mintInput": {
  1811  						"signatureIndices": [
  1812  							0
  1813  						]
  1814  					},
  1815  					"mintOutput": {
  1816  						"addresses": [
  1817  							"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
  1818  						],
  1819  						"locktime": 0,
  1820  						"threshold": 1
  1821  					},
  1822  					"ownedOutput": {
  1823  						"addresses": [],
  1824  						"locktime": 0,
  1825  						"threshold": 0
  1826  					}
  1827  				}
  1828  			},
  1829  			{
  1830  				"assetID": "PLACEHOLDER_CREATE_ASSET_TX_ID",
  1831  				"inputIDs": [
  1832  					{
  1833  						"txID": "PLACEHOLDER_CREATE_ASSET_TX_ID",
  1834  						"outputIndex": 2
  1835  					}
  1836  				],
  1837  				"fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy",
  1838  				"operation": {
  1839  					"mintInput": {
  1840  						"signatureIndices": [
  1841  							0
  1842  						]
  1843  					},
  1844  					"mintOutput": {
  1845  						"addresses": [
  1846  							"X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"
  1847  						],
  1848  						"locktime": 0,
  1849  						"threshold": 1
  1850  					},
  1851  					"ownedOutput": {
  1852  						"addresses": [],
  1853  						"locktime": 0,
  1854  						"threshold": 0
  1855  					}
  1856  				}
  1857  			}
  1858  		]
  1859  	},
  1860  	"credentials": [
  1861  		{
  1862  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
  1863  			"credential": {
  1864  				"signatures": [
  1865  					"PLACEHOLDER_SIGNATURE"
  1866  				]
  1867  			}
  1868  		},
  1869  		{
  1870  			"fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy",
  1871  			"credential": {
  1872  				"signatures": [
  1873  					"PLACEHOLDER_SIGNATURE"
  1874  				]
  1875  			}
  1876  		},
  1877  		{
  1878  			"fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy",
  1879  			"credential": {
  1880  				"signatures": [
  1881  					"PLACEHOLDER_SIGNATURE"
  1882  				]
  1883  			}
  1884  		}
  1885  	],
  1886  	"id": "PLACEHOLDER_TX_ID"
  1887  }`
  1888  
  1889  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_CREATE_ASSET_TX_ID", createAssetTx.ID().String(), 4)
  1890  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", mintPropertyFxOpTx.ID().String(), 1)
  1891  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", mintPropertyFxOpTx.Unsigned.(*txs.OperationTx).BlockchainID.String(), 1)
  1892  
  1893  	sigStr, err := formatting.Encode(formatting.HexNC, mintPropertyFxOpTx.Creds[1].Credential.(*propertyfx.Credential).Sigs[0][:])
  1894  	require.NoError(err)
  1895  
  1896  	expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 3)
  1897  
  1898  	require.Equal(expectedReplyTxString, string(replyTxBytes))
  1899  }
  1900  
  1901  func newAvaxBaseTxWithOutputs(t *testing.T, env *environment) *txs.Tx {
  1902  	var (
  1903  		memo      = []byte{1, 2, 3, 4, 5, 6, 7, 8}
  1904  		key       = keys[0]
  1905  		changeKey = keys[1]
  1906  		kc        = secp256k1fx.NewKeychain(key)
  1907  	)
  1908  
  1909  	tx, err := env.txBuilder.BaseTx(
  1910  		[]*avax.TransferableOutput{{
  1911  			Asset: avax.Asset{ID: env.vm.feeAssetID},
  1912  			Out: &secp256k1fx.TransferOutput{
  1913  				Amt: units.MicroAvax,
  1914  				OutputOwners: secp256k1fx.OutputOwners{
  1915  					Threshold: 1,
  1916  					Addrs:     []ids.ShortID{key.PublicKey().Address()},
  1917  				},
  1918  			},
  1919  		}},
  1920  		memo,
  1921  		kc,
  1922  		changeKey.PublicKey().Address(),
  1923  	)
  1924  	require.NoError(t, err)
  1925  	return tx
  1926  }
  1927  
  1928  func newAvaxCreateAssetTxWithOutputs(t *testing.T, env *environment, initialStates map[uint32][]verify.State) *txs.Tx {
  1929  	var (
  1930  		key = keys[0]
  1931  		kc  = secp256k1fx.NewKeychain(key)
  1932  	)
  1933  
  1934  	tx, err := env.txBuilder.CreateAssetTx(
  1935  		"Team Rocket", // name
  1936  		"TR",          // symbol
  1937  		0,             // denomination
  1938  		initialStates,
  1939  		kc,
  1940  		key.Address(),
  1941  	)
  1942  	require.NoError(t, err)
  1943  	return tx
  1944  }
  1945  
  1946  func buildTestExportTx(t *testing.T, env *environment, chainID ids.ID) *txs.Tx {
  1947  	var (
  1948  		key = keys[0]
  1949  		kc  = secp256k1fx.NewKeychain(key)
  1950  		to  = key.PublicKey().Address()
  1951  	)
  1952  
  1953  	tx, err := env.txBuilder.ExportTx(
  1954  		chainID,
  1955  		to,
  1956  		env.vm.feeAssetID,
  1957  		units.MicroAvax,
  1958  		kc,
  1959  		key.Address(),
  1960  	)
  1961  	require.NoError(t, err)
  1962  	return tx
  1963  }
  1964  
  1965  func buildNFTxMintOp(createAssetTx *txs.Tx, key *secp256k1.PrivateKey, outputIndex, groupID uint32) *txs.Operation {
  1966  	return &txs.Operation{
  1967  		Asset: avax.Asset{ID: createAssetTx.ID()},
  1968  		UTXOIDs: []*avax.UTXOID{{
  1969  			TxID:        createAssetTx.ID(),
  1970  			OutputIndex: outputIndex,
  1971  		}},
  1972  		Op: &nftfx.MintOperation{
  1973  			MintInput: secp256k1fx.Input{
  1974  				SigIndices: []uint32{0},
  1975  			},
  1976  			GroupID: groupID,
  1977  			Payload: []byte{'h', 'e', 'l', 'l', 'o'},
  1978  			Outputs: []*secp256k1fx.OutputOwners{{
  1979  				Threshold: 1,
  1980  				Addrs:     []ids.ShortID{key.PublicKey().Address()},
  1981  			}},
  1982  		},
  1983  	}
  1984  }
  1985  
  1986  func buildPropertyFxMintOp(createAssetTx *txs.Tx, key *secp256k1.PrivateKey, outputIndex uint32) *txs.Operation {
  1987  	return &txs.Operation{
  1988  		Asset: avax.Asset{ID: createAssetTx.ID()},
  1989  		UTXOIDs: []*avax.UTXOID{{
  1990  			TxID:        createAssetTx.ID(),
  1991  			OutputIndex: outputIndex,
  1992  		}},
  1993  		Op: &propertyfx.MintOperation{
  1994  			MintInput: secp256k1fx.Input{
  1995  				SigIndices: []uint32{0},
  1996  			},
  1997  			MintOutput: propertyfx.MintOutput{OutputOwners: secp256k1fx.OutputOwners{
  1998  				Threshold: 1,
  1999  				Addrs: []ids.ShortID{
  2000  					key.PublicKey().Address(),
  2001  				},
  2002  			}},
  2003  		},
  2004  	}
  2005  }
  2006  
  2007  func buildSecpMintOp(createAssetTx *txs.Tx, key *secp256k1.PrivateKey, outputIndex uint32) *txs.Operation {
  2008  	return &txs.Operation{
  2009  		Asset: avax.Asset{ID: createAssetTx.ID()},
  2010  		UTXOIDs: []*avax.UTXOID{{
  2011  			TxID:        createAssetTx.ID(),
  2012  			OutputIndex: outputIndex,
  2013  		}},
  2014  		Op: &secp256k1fx.MintOperation{
  2015  			MintInput: secp256k1fx.Input{
  2016  				SigIndices: []uint32{0},
  2017  			},
  2018  			MintOutput: secp256k1fx.MintOutput{
  2019  				OutputOwners: secp256k1fx.OutputOwners{
  2020  					Threshold: 1,
  2021  					Addrs: []ids.ShortID{
  2022  						key.PublicKey().Address(),
  2023  					},
  2024  				},
  2025  			},
  2026  			TransferOutput: secp256k1fx.TransferOutput{
  2027  				Amt: 1,
  2028  				OutputOwners: secp256k1fx.OutputOwners{
  2029  					Locktime:  0,
  2030  					Threshold: 1,
  2031  					Addrs:     []ids.ShortID{key.PublicKey().Address()},
  2032  				},
  2033  			},
  2034  		},
  2035  	}
  2036  }
  2037  
  2038  func buildOperationTxWithOps(t *testing.T, env *environment, op ...*txs.Operation) *txs.Tx {
  2039  	var (
  2040  		key = keys[0]
  2041  		kc  = secp256k1fx.NewKeychain(key)
  2042  	)
  2043  
  2044  	tx, err := env.txBuilder.Operation(
  2045  		op,
  2046  		kc,
  2047  		key.Address(),
  2048  	)
  2049  	require.NoError(t, err)
  2050  	return tx
  2051  }
  2052  
  2053  func TestServiceGetNilTx(t *testing.T) {
  2054  	require := require.New(t)
  2055  
  2056  	env := setup(t, &envConfig{
  2057  		fork: latest,
  2058  	})
  2059  	service := &Service{vm: env.vm}
  2060  	env.vm.ctx.Lock.Unlock()
  2061  
  2062  	reply := api.GetTxReply{}
  2063  	err := service.GetTx(nil, &api.GetTxArgs{}, &reply)
  2064  	require.ErrorIs(err, errNilTxID)
  2065  }
  2066  
  2067  func TestServiceGetUnknownTx(t *testing.T) {
  2068  	require := require.New(t)
  2069  
  2070  	env := setup(t, &envConfig{
  2071  		fork: latest,
  2072  	})
  2073  	service := &Service{vm: env.vm}
  2074  	env.vm.ctx.Lock.Unlock()
  2075  
  2076  	reply := api.GetTxReply{}
  2077  	err := service.GetTx(nil, &api.GetTxArgs{TxID: ids.GenerateTestID()}, &reply)
  2078  	require.ErrorIs(err, database.ErrNotFound)
  2079  }
  2080  
  2081  func TestServiceGetUTXOs(t *testing.T) {
  2082  	env := setup(t, &envConfig{
  2083  		fork: latest,
  2084  	})
  2085  	service := &Service{vm: env.vm}
  2086  	env.vm.ctx.Lock.Unlock()
  2087  
  2088  	rawAddr := ids.GenerateTestShortID()
  2089  	rawEmptyAddr := ids.GenerateTestShortID()
  2090  
  2091  	numUTXOs := 10
  2092  	// Put a bunch of UTXOs
  2093  	for i := 0; i < numUTXOs; i++ {
  2094  		utxo := &avax.UTXO{
  2095  			UTXOID: avax.UTXOID{
  2096  				TxID: ids.GenerateTestID(),
  2097  			},
  2098  			Asset: avax.Asset{ID: env.vm.ctx.AVAXAssetID},
  2099  			Out: &secp256k1fx.TransferOutput{
  2100  				Amt: 1,
  2101  				OutputOwners: secp256k1fx.OutputOwners{
  2102  					Threshold: 1,
  2103  					Addrs:     []ids.ShortID{rawAddr},
  2104  				},
  2105  			},
  2106  		}
  2107  		env.vm.state.AddUTXO(utxo)
  2108  	}
  2109  	require.NoError(t, env.vm.state.Commit())
  2110  
  2111  	sm := env.sharedMemory.NewSharedMemory(constants.PlatformChainID)
  2112  
  2113  	elems := make([]*atomic.Element, numUTXOs)
  2114  	codec := env.vm.parser.Codec()
  2115  	for i := range elems {
  2116  		utxo := &avax.UTXO{
  2117  			UTXOID: avax.UTXOID{
  2118  				TxID: ids.GenerateTestID(),
  2119  			},
  2120  			Asset: avax.Asset{ID: env.vm.ctx.AVAXAssetID},
  2121  			Out: &secp256k1fx.TransferOutput{
  2122  				Amt: 1,
  2123  				OutputOwners: secp256k1fx.OutputOwners{
  2124  					Threshold: 1,
  2125  					Addrs:     []ids.ShortID{rawAddr},
  2126  				},
  2127  			},
  2128  		}
  2129  
  2130  		utxoBytes, err := codec.Marshal(txs.CodecVersion, utxo)
  2131  		require.NoError(t, err)
  2132  		utxoID := utxo.InputID()
  2133  		elems[i] = &atomic.Element{
  2134  			Key:   utxoID[:],
  2135  			Value: utxoBytes,
  2136  			Traits: [][]byte{
  2137  				rawAddr.Bytes(),
  2138  			},
  2139  		}
  2140  	}
  2141  
  2142  	require.NoError(t, sm.Apply(map[ids.ID]*atomic.Requests{
  2143  		env.vm.ctx.ChainID: {
  2144  			PutRequests: elems,
  2145  		},
  2146  	}))
  2147  
  2148  	hrp := constants.GetHRP(env.vm.ctx.NetworkID)
  2149  	xAddr, err := env.vm.FormatLocalAddress(rawAddr)
  2150  	require.NoError(t, err)
  2151  	pAddr, err := env.vm.FormatAddress(constants.PlatformChainID, rawAddr)
  2152  	require.NoError(t, err)
  2153  	unknownChainAddr, err := address.Format("R", hrp, rawAddr.Bytes())
  2154  	require.NoError(t, err)
  2155  	xEmptyAddr, err := env.vm.FormatLocalAddress(rawEmptyAddr)
  2156  	require.NoError(t, err)
  2157  
  2158  	tests := []struct {
  2159  		label       string
  2160  		count       int
  2161  		expectedErr error
  2162  		args        *api.GetUTXOsArgs
  2163  	}{
  2164  		{
  2165  			label:       "invalid address: ''",
  2166  			expectedErr: address.ErrNoSeparator,
  2167  			args: &api.GetUTXOsArgs{
  2168  				Addresses: []string{""},
  2169  			},
  2170  		},
  2171  		{
  2172  			label:       "invalid address: '-'",
  2173  			expectedErr: bech32.ErrInvalidLength(0),
  2174  			args: &api.GetUTXOsArgs{
  2175  				Addresses: []string{"-"},
  2176  			},
  2177  		},
  2178  		{
  2179  			label:       "invalid address: 'foo'",
  2180  			expectedErr: address.ErrNoSeparator,
  2181  			args: &api.GetUTXOsArgs{
  2182  				Addresses: []string{"foo"},
  2183  			},
  2184  		},
  2185  		{
  2186  			label:       "invalid address: 'foo-bar'",
  2187  			expectedErr: bech32.ErrInvalidLength(3),
  2188  			args: &api.GetUTXOsArgs{
  2189  				Addresses: []string{"foo-bar"},
  2190  			},
  2191  		},
  2192  		{
  2193  			label:       "invalid address: '<ChainID>'",
  2194  			expectedErr: address.ErrNoSeparator,
  2195  			args: &api.GetUTXOsArgs{
  2196  				Addresses: []string{env.vm.ctx.ChainID.String()},
  2197  			},
  2198  		},
  2199  		{
  2200  			label:       "invalid address: '<ChainID>-'",
  2201  			expectedErr: bech32.ErrInvalidLength(0),
  2202  			args: &api.GetUTXOsArgs{
  2203  				Addresses: []string{env.vm.ctx.ChainID.String() + "-"},
  2204  			},
  2205  		},
  2206  		{
  2207  			label:       "invalid address: '<Unknown ID>-<addr>'",
  2208  			expectedErr: ids.ErrNoIDWithAlias,
  2209  			args: &api.GetUTXOsArgs{
  2210  				Addresses: []string{unknownChainAddr},
  2211  			},
  2212  		},
  2213  		{
  2214  			label:       "no addresses",
  2215  			expectedErr: errNoAddresses,
  2216  			args:        &api.GetUTXOsArgs{},
  2217  		},
  2218  		{
  2219  			label: "get all X-chain UTXOs",
  2220  			count: numUTXOs,
  2221  			args: &api.GetUTXOsArgs{
  2222  				Addresses: []string{
  2223  					xAddr,
  2224  				},
  2225  			},
  2226  		},
  2227  		{
  2228  			label: "get one X-chain UTXO",
  2229  			count: 1,
  2230  			args: &api.GetUTXOsArgs{
  2231  				Addresses: []string{
  2232  					xAddr,
  2233  				},
  2234  				Limit: 1,
  2235  			},
  2236  		},
  2237  		{
  2238  			label: "limit greater than number of UTXOs",
  2239  			count: numUTXOs,
  2240  			args: &api.GetUTXOsArgs{
  2241  				Addresses: []string{
  2242  					xAddr,
  2243  				},
  2244  				Limit: avajson.Uint32(numUTXOs + 1),
  2245  			},
  2246  		},
  2247  		{
  2248  			label: "no utxos to return",
  2249  			count: 0,
  2250  			args: &api.GetUTXOsArgs{
  2251  				Addresses: []string{
  2252  					xEmptyAddr,
  2253  				},
  2254  			},
  2255  		},
  2256  		{
  2257  			label: "multiple address with utxos",
  2258  			count: numUTXOs,
  2259  			args: &api.GetUTXOsArgs{
  2260  				Addresses: []string{
  2261  					xEmptyAddr,
  2262  					xAddr,
  2263  				},
  2264  			},
  2265  		},
  2266  		{
  2267  			label: "get all P-chain UTXOs",
  2268  			count: numUTXOs,
  2269  			args: &api.GetUTXOsArgs{
  2270  				Addresses: []string{
  2271  					xAddr,
  2272  				},
  2273  				SourceChain: "P",
  2274  			},
  2275  		},
  2276  		{
  2277  			label:       "invalid source chain ID",
  2278  			expectedErr: ids.ErrNoIDWithAlias,
  2279  			count:       numUTXOs,
  2280  			args: &api.GetUTXOsArgs{
  2281  				Addresses: []string{
  2282  					xAddr,
  2283  				},
  2284  				SourceChain: "HomeRunDerby",
  2285  			},
  2286  		},
  2287  		{
  2288  			label: "get all P-chain UTXOs",
  2289  			count: numUTXOs,
  2290  			args: &api.GetUTXOsArgs{
  2291  				Addresses: []string{
  2292  					xAddr,
  2293  				},
  2294  				SourceChain: "P",
  2295  			},
  2296  		},
  2297  		{
  2298  			label:       "get UTXOs from multiple chains",
  2299  			expectedErr: avax.ErrMismatchedChainIDs,
  2300  			args: &api.GetUTXOsArgs{
  2301  				Addresses: []string{
  2302  					xAddr,
  2303  					pAddr,
  2304  				},
  2305  			},
  2306  		},
  2307  		{
  2308  			label:       "get UTXOs for an address on a different chain",
  2309  			expectedErr: avax.ErrMismatchedChainIDs,
  2310  			args: &api.GetUTXOsArgs{
  2311  				Addresses: []string{
  2312  					pAddr,
  2313  				},
  2314  			},
  2315  		},
  2316  	}
  2317  	for _, test := range tests {
  2318  		t.Run(test.label, func(t *testing.T) {
  2319  			require := require.New(t)
  2320  			reply := &api.GetUTXOsReply{}
  2321  			err := service.GetUTXOs(nil, test.args, reply)
  2322  			require.ErrorIs(err, test.expectedErr)
  2323  			if test.expectedErr != nil {
  2324  				return
  2325  			}
  2326  			require.Len(reply.UTXOs, test.count)
  2327  		})
  2328  	}
  2329  }
  2330  
  2331  func TestGetAssetDescription(t *testing.T) {
  2332  	require := require.New(t)
  2333  
  2334  	env := setup(t, &envConfig{
  2335  		fork: latest,
  2336  	})
  2337  	service := &Service{vm: env.vm}
  2338  	env.vm.ctx.Lock.Unlock()
  2339  
  2340  	avaxAssetID := env.genesisTx.ID()
  2341  
  2342  	reply := GetAssetDescriptionReply{}
  2343  	require.NoError(service.GetAssetDescription(nil, &GetAssetDescriptionArgs{
  2344  		AssetID: avaxAssetID.String(),
  2345  	}, &reply))
  2346  
  2347  	require.Equal("AVAX", reply.Name)
  2348  	require.Equal("SYMB", reply.Symbol)
  2349  }
  2350  
  2351  func TestGetBalance(t *testing.T) {
  2352  	require := require.New(t)
  2353  
  2354  	env := setup(t, &envConfig{
  2355  		fork: latest,
  2356  	})
  2357  	service := &Service{vm: env.vm}
  2358  	env.vm.ctx.Lock.Unlock()
  2359  
  2360  	avaxAssetID := env.genesisTx.ID()
  2361  
  2362  	reply := GetBalanceReply{}
  2363  	addrStr, err := env.vm.FormatLocalAddress(keys[0].PublicKey().Address())
  2364  	require.NoError(err)
  2365  	require.NoError(service.GetBalance(nil, &GetBalanceArgs{
  2366  		Address: addrStr,
  2367  		AssetID: avaxAssetID.String(),
  2368  	}, &reply))
  2369  
  2370  	require.Equal(startBalance, uint64(reply.Balance))
  2371  }
  2372  
  2373  func TestCreateFixedCapAsset(t *testing.T) {
  2374  	for _, tc := range testCases {
  2375  		t.Run(tc.name, func(t *testing.T) {
  2376  			require := require.New(t)
  2377  
  2378  			env := setup(t, &envConfig{
  2379  				isCustomFeeAsset: !tc.avaxAsset,
  2380  				keystoreUsers: []*user{{
  2381  					username:    username,
  2382  					password:    password,
  2383  					initialKeys: keys,
  2384  				}},
  2385  			})
  2386  			service := &Service{vm: env.vm}
  2387  			env.vm.ctx.Lock.Unlock()
  2388  
  2389  			reply := AssetIDChangeAddr{}
  2390  			addrStr, err := env.vm.FormatLocalAddress(keys[0].PublicKey().Address())
  2391  			require.NoError(err)
  2392  
  2393  			changeAddrStr, err := env.vm.FormatLocalAddress(testChangeAddr)
  2394  			require.NoError(err)
  2395  			_, fromAddrsStr := sampleAddrs(t, env.vm.AddressManager, addrs)
  2396  
  2397  			require.NoError(service.CreateFixedCapAsset(nil, &CreateAssetArgs{
  2398  				JSONSpendHeader: api.JSONSpendHeader{
  2399  					UserPass: api.UserPass{
  2400  						Username: username,
  2401  						Password: password,
  2402  					},
  2403  					JSONFromAddrs:  api.JSONFromAddrs{From: fromAddrsStr},
  2404  					JSONChangeAddr: api.JSONChangeAddr{ChangeAddr: changeAddrStr},
  2405  				},
  2406  				Name:         "testAsset",
  2407  				Symbol:       "TEST",
  2408  				Denomination: 1,
  2409  				InitialHolders: []*Holder{{
  2410  					Amount:  123456789,
  2411  					Address: addrStr,
  2412  				}},
  2413  			}, &reply))
  2414  			require.Equal(changeAddrStr, reply.ChangeAddr)
  2415  		})
  2416  	}
  2417  }
  2418  
  2419  func TestCreateVariableCapAsset(t *testing.T) {
  2420  	for _, tc := range testCases {
  2421  		t.Run(tc.name, func(t *testing.T) {
  2422  			require := require.New(t)
  2423  
  2424  			env := setup(t, &envConfig{
  2425  				isCustomFeeAsset: !tc.avaxAsset,
  2426  				keystoreUsers: []*user{{
  2427  					username:    username,
  2428  					password:    password,
  2429  					initialKeys: keys,
  2430  				}},
  2431  			})
  2432  			service := &Service{vm: env.vm}
  2433  			env.vm.ctx.Lock.Unlock()
  2434  
  2435  			reply := AssetIDChangeAddr{}
  2436  			minterAddrStr, err := env.vm.FormatLocalAddress(keys[0].PublicKey().Address())
  2437  			require.NoError(err)
  2438  			_, fromAddrsStr := sampleAddrs(t, env.vm.AddressManager, addrs)
  2439  			changeAddrStr := fromAddrsStr[0]
  2440  
  2441  			require.NoError(service.CreateVariableCapAsset(nil, &CreateAssetArgs{
  2442  				JSONSpendHeader: api.JSONSpendHeader{
  2443  					UserPass: api.UserPass{
  2444  						Username: username,
  2445  						Password: password,
  2446  					},
  2447  					JSONFromAddrs:  api.JSONFromAddrs{From: fromAddrsStr},
  2448  					JSONChangeAddr: api.JSONChangeAddr{ChangeAddr: changeAddrStr},
  2449  				},
  2450  				Name:   "test asset",
  2451  				Symbol: "TEST",
  2452  				MinterSets: []Owners{
  2453  					{
  2454  						Threshold: 1,
  2455  						Minters: []string{
  2456  							minterAddrStr,
  2457  						},
  2458  					},
  2459  				},
  2460  			}, &reply))
  2461  			require.Equal(changeAddrStr, reply.ChangeAddr)
  2462  
  2463  			buildAndAccept(require, env.vm, env.issuer, reply.AssetID)
  2464  
  2465  			createdAssetID := reply.AssetID.String()
  2466  			// Test minting of the created variable cap asset
  2467  			mintArgs := &MintArgs{
  2468  				JSONSpendHeader: api.JSONSpendHeader{
  2469  					UserPass: api.UserPass{
  2470  						Username: username,
  2471  						Password: password,
  2472  					},
  2473  					JSONChangeAddr: api.JSONChangeAddr{ChangeAddr: changeAddrStr},
  2474  				},
  2475  				Amount:  200,
  2476  				AssetID: createdAssetID,
  2477  				To:      minterAddrStr, // Send newly minted tokens to this address
  2478  			}
  2479  			mintReply := &api.JSONTxIDChangeAddr{}
  2480  			require.NoError(service.Mint(nil, mintArgs, mintReply))
  2481  			require.Equal(changeAddrStr, mintReply.ChangeAddr)
  2482  
  2483  			buildAndAccept(require, env.vm, env.issuer, mintReply.TxID)
  2484  
  2485  			sendArgs := &SendArgs{
  2486  				JSONSpendHeader: api.JSONSpendHeader{
  2487  					UserPass: api.UserPass{
  2488  						Username: username,
  2489  						Password: password,
  2490  					},
  2491  					JSONFromAddrs:  api.JSONFromAddrs{From: []string{minterAddrStr}},
  2492  					JSONChangeAddr: api.JSONChangeAddr{ChangeAddr: changeAddrStr},
  2493  				},
  2494  				SendOutput: SendOutput{
  2495  					Amount:  200,
  2496  					AssetID: createdAssetID,
  2497  					To:      fromAddrsStr[0],
  2498  				},
  2499  			}
  2500  			sendReply := &api.JSONTxIDChangeAddr{}
  2501  			require.NoError(service.Send(nil, sendArgs, sendReply))
  2502  			require.Equal(changeAddrStr, sendReply.ChangeAddr)
  2503  		})
  2504  	}
  2505  }
  2506  
  2507  func TestNFTWorkflow(t *testing.T) {
  2508  	for _, tc := range testCases {
  2509  		t.Run(tc.name, func(t *testing.T) {
  2510  			require := require.New(t)
  2511  
  2512  			env := setup(t, &envConfig{
  2513  				isCustomFeeAsset: !tc.avaxAsset,
  2514  				keystoreUsers: []*user{{
  2515  					username:    username,
  2516  					password:    password,
  2517  					initialKeys: keys,
  2518  				}},
  2519  			})
  2520  			service := &Service{vm: env.vm}
  2521  			env.vm.ctx.Lock.Unlock()
  2522  
  2523  			fromAddrs, fromAddrsStr := sampleAddrs(t, env.vm.AddressManager, addrs)
  2524  
  2525  			// Test minting of the created variable cap asset
  2526  			addrStr, err := env.vm.FormatLocalAddress(keys[0].PublicKey().Address())
  2527  			require.NoError(err)
  2528  
  2529  			createArgs := &CreateNFTAssetArgs{
  2530  				JSONSpendHeader: api.JSONSpendHeader{
  2531  					UserPass: api.UserPass{
  2532  						Username: username,
  2533  						Password: password,
  2534  					},
  2535  					JSONFromAddrs:  api.JSONFromAddrs{From: fromAddrsStr},
  2536  					JSONChangeAddr: api.JSONChangeAddr{ChangeAddr: fromAddrsStr[0]},
  2537  				},
  2538  				Name:   "BIG COIN",
  2539  				Symbol: "COIN",
  2540  				MinterSets: []Owners{
  2541  					{
  2542  						Threshold: 1,
  2543  						Minters: []string{
  2544  							addrStr,
  2545  						},
  2546  					},
  2547  				},
  2548  			}
  2549  			createReply := &AssetIDChangeAddr{}
  2550  			require.NoError(service.CreateNFTAsset(nil, createArgs, createReply))
  2551  			require.Equal(fromAddrsStr[0], createReply.ChangeAddr)
  2552  
  2553  			buildAndAccept(require, env.vm, env.issuer, createReply.AssetID)
  2554  
  2555  			// Key: Address
  2556  			// Value: AVAX balance
  2557  			balances := map[ids.ShortID]uint64{}
  2558  			for _, addr := range addrs { // get balances for all addresses
  2559  				addrStr, err := env.vm.FormatLocalAddress(addr)
  2560  				require.NoError(err)
  2561  
  2562  				reply := &GetBalanceReply{}
  2563  				require.NoError(service.GetBalance(nil,
  2564  					&GetBalanceArgs{
  2565  						Address: addrStr,
  2566  						AssetID: env.vm.feeAssetID.String(),
  2567  					},
  2568  					reply,
  2569  				))
  2570  
  2571  				balances[addr] = uint64(reply.Balance)
  2572  			}
  2573  
  2574  			fromAddrsTotalBalance := uint64(0)
  2575  			for _, addr := range fromAddrs {
  2576  				fromAddrsTotalBalance += balances[addr]
  2577  			}
  2578  
  2579  			fromAddrsStartBalance := startBalance * uint64(len(fromAddrs))
  2580  			require.Equal(fromAddrsStartBalance-env.vm.TxFee, fromAddrsTotalBalance)
  2581  
  2582  			assetID := createReply.AssetID
  2583  			payload, err := formatting.Encode(formatting.Hex, []byte{1, 2, 3, 4, 5})
  2584  			require.NoError(err)
  2585  			mintArgs := &MintNFTArgs{
  2586  				JSONSpendHeader: api.JSONSpendHeader{
  2587  					UserPass: api.UserPass{
  2588  						Username: username,
  2589  						Password: password,
  2590  					},
  2591  					JSONFromAddrs:  api.JSONFromAddrs{},
  2592  					JSONChangeAddr: api.JSONChangeAddr{ChangeAddr: fromAddrsStr[0]},
  2593  				},
  2594  				AssetID:  assetID.String(),
  2595  				Payload:  payload,
  2596  				To:       addrStr,
  2597  				Encoding: formatting.Hex,
  2598  			}
  2599  			mintReply := &api.JSONTxIDChangeAddr{}
  2600  
  2601  			require.NoError(service.MintNFT(nil, mintArgs, mintReply))
  2602  			require.Equal(fromAddrsStr[0], createReply.ChangeAddr)
  2603  
  2604  			// Accept the transaction so that we can send the newly minted NFT
  2605  			buildAndAccept(require, env.vm, env.issuer, mintReply.TxID)
  2606  
  2607  			sendArgs := &SendNFTArgs{
  2608  				JSONSpendHeader: api.JSONSpendHeader{
  2609  					UserPass: api.UserPass{
  2610  						Username: username,
  2611  						Password: password,
  2612  					},
  2613  					JSONFromAddrs:  api.JSONFromAddrs{},
  2614  					JSONChangeAddr: api.JSONChangeAddr{ChangeAddr: fromAddrsStr[0]},
  2615  				},
  2616  				AssetID: assetID.String(),
  2617  				GroupID: 0,
  2618  				To:      addrStr,
  2619  			}
  2620  			sendReply := &api.JSONTxIDChangeAddr{}
  2621  			require.NoError(service.SendNFT(nil, sendArgs, sendReply))
  2622  			require.Equal(fromAddrsStr[0], sendReply.ChangeAddr)
  2623  		})
  2624  	}
  2625  }
  2626  
  2627  func TestImportExportKey(t *testing.T) {
  2628  	require := require.New(t)
  2629  
  2630  	env := setup(t, &envConfig{
  2631  		keystoreUsers: []*user{{
  2632  			username: username,
  2633  			password: password,
  2634  		}},
  2635  	})
  2636  	service := &Service{vm: env.vm}
  2637  	env.vm.ctx.Lock.Unlock()
  2638  
  2639  	sk, err := secp256k1.NewPrivateKey()
  2640  	require.NoError(err)
  2641  
  2642  	importArgs := &ImportKeyArgs{
  2643  		UserPass: api.UserPass{
  2644  			Username: username,
  2645  			Password: password,
  2646  		},
  2647  		PrivateKey: sk,
  2648  	}
  2649  	importReply := &api.JSONAddress{}
  2650  	require.NoError(service.ImportKey(nil, importArgs, importReply))
  2651  
  2652  	addrStr, err := env.vm.FormatLocalAddress(sk.PublicKey().Address())
  2653  	require.NoError(err)
  2654  	exportArgs := &ExportKeyArgs{
  2655  		UserPass: api.UserPass{
  2656  			Username: username,
  2657  			Password: password,
  2658  		},
  2659  		Address: addrStr,
  2660  	}
  2661  	exportReply := &ExportKeyReply{}
  2662  	require.NoError(service.ExportKey(nil, exportArgs, exportReply))
  2663  	require.Equal(sk.Bytes(), exportReply.PrivateKey.Bytes())
  2664  }
  2665  
  2666  func TestImportAVMKeyNoDuplicates(t *testing.T) {
  2667  	require := require.New(t)
  2668  
  2669  	env := setup(t, &envConfig{
  2670  		keystoreUsers: []*user{{
  2671  			username: username,
  2672  			password: password,
  2673  		}},
  2674  	})
  2675  	service := &Service{vm: env.vm}
  2676  	env.vm.ctx.Lock.Unlock()
  2677  
  2678  	sk, err := secp256k1.NewPrivateKey()
  2679  	require.NoError(err)
  2680  	args := ImportKeyArgs{
  2681  		UserPass: api.UserPass{
  2682  			Username: username,
  2683  			Password: password,
  2684  		},
  2685  		PrivateKey: sk,
  2686  	}
  2687  	reply := api.JSONAddress{}
  2688  	require.NoError(service.ImportKey(nil, &args, &reply))
  2689  
  2690  	expectedAddress, err := env.vm.FormatLocalAddress(sk.PublicKey().Address())
  2691  	require.NoError(err)
  2692  
  2693  	require.Equal(expectedAddress, reply.Address)
  2694  
  2695  	reply2 := api.JSONAddress{}
  2696  	require.NoError(service.ImportKey(nil, &args, &reply2))
  2697  
  2698  	require.Equal(expectedAddress, reply2.Address)
  2699  
  2700  	addrsArgs := api.UserPass{
  2701  		Username: username,
  2702  		Password: password,
  2703  	}
  2704  	addrsReply := api.JSONAddresses{}
  2705  	require.NoError(service.ListAddresses(nil, &addrsArgs, &addrsReply))
  2706  
  2707  	require.Len(addrsReply.Addresses, 1)
  2708  	require.Equal(expectedAddress, addrsReply.Addresses[0])
  2709  }
  2710  
  2711  func TestSend(t *testing.T) {
  2712  	require := require.New(t)
  2713  
  2714  	env := setup(t, &envConfig{
  2715  		keystoreUsers: []*user{{
  2716  			username:    username,
  2717  			password:    password,
  2718  			initialKeys: keys,
  2719  		}},
  2720  	})
  2721  	service := &Service{vm: env.vm}
  2722  	env.vm.ctx.Lock.Unlock()
  2723  
  2724  	assetID := env.genesisTx.ID()
  2725  	addr := keys[0].PublicKey().Address()
  2726  
  2727  	addrStr, err := env.vm.FormatLocalAddress(addr)
  2728  	require.NoError(err)
  2729  	changeAddrStr, err := env.vm.FormatLocalAddress(testChangeAddr)
  2730  	require.NoError(err)
  2731  	_, fromAddrsStr := sampleAddrs(t, env.vm.AddressManager, addrs)
  2732  
  2733  	args := &SendArgs{
  2734  		JSONSpendHeader: api.JSONSpendHeader{
  2735  			UserPass: api.UserPass{
  2736  				Username: username,
  2737  				Password: password,
  2738  			},
  2739  			JSONFromAddrs:  api.JSONFromAddrs{From: fromAddrsStr},
  2740  			JSONChangeAddr: api.JSONChangeAddr{ChangeAddr: changeAddrStr},
  2741  		},
  2742  		SendOutput: SendOutput{
  2743  			Amount:  500,
  2744  			AssetID: assetID.String(),
  2745  			To:      addrStr,
  2746  		},
  2747  	}
  2748  	reply := &api.JSONTxIDChangeAddr{}
  2749  	require.NoError(service.Send(nil, args, reply))
  2750  	require.Equal(changeAddrStr, reply.ChangeAddr)
  2751  
  2752  	buildAndAccept(require, env.vm, env.issuer, reply.TxID)
  2753  }
  2754  
  2755  func TestSendMultiple(t *testing.T) {
  2756  	for _, tc := range testCases {
  2757  		t.Run(tc.name, func(t *testing.T) {
  2758  			require := require.New(t)
  2759  
  2760  			env := setup(t, &envConfig{
  2761  				isCustomFeeAsset: !tc.avaxAsset,
  2762  				keystoreUsers: []*user{{
  2763  					username:    username,
  2764  					password:    password,
  2765  					initialKeys: keys,
  2766  				}},
  2767  				vmStaticConfig: &config.Config{
  2768  					EUpgradeTime: mockable.MaxTime,
  2769  				},
  2770  			})
  2771  			service := &Service{vm: env.vm}
  2772  			env.vm.ctx.Lock.Unlock()
  2773  
  2774  			assetID := env.genesisTx.ID()
  2775  			addr := keys[0].PublicKey().Address()
  2776  
  2777  			addrStr, err := env.vm.FormatLocalAddress(addr)
  2778  			require.NoError(err)
  2779  			changeAddrStr, err := env.vm.FormatLocalAddress(testChangeAddr)
  2780  			require.NoError(err)
  2781  			_, fromAddrsStr := sampleAddrs(t, env.vm.AddressManager, addrs)
  2782  
  2783  			args := &SendMultipleArgs{
  2784  				JSONSpendHeader: api.JSONSpendHeader{
  2785  					UserPass: api.UserPass{
  2786  						Username: username,
  2787  						Password: password,
  2788  					},
  2789  					JSONFromAddrs:  api.JSONFromAddrs{From: fromAddrsStr},
  2790  					JSONChangeAddr: api.JSONChangeAddr{ChangeAddr: changeAddrStr},
  2791  				},
  2792  				Outputs: []SendOutput{
  2793  					{
  2794  						Amount:  500,
  2795  						AssetID: assetID.String(),
  2796  						To:      addrStr,
  2797  					},
  2798  					{
  2799  						Amount:  1000,
  2800  						AssetID: assetID.String(),
  2801  						To:      addrStr,
  2802  					},
  2803  				},
  2804  			}
  2805  			reply := &api.JSONTxIDChangeAddr{}
  2806  			require.NoError(service.SendMultiple(nil, args, reply))
  2807  			require.Equal(changeAddrStr, reply.ChangeAddr)
  2808  
  2809  			buildAndAccept(require, env.vm, env.issuer, reply.TxID)
  2810  		})
  2811  	}
  2812  }
  2813  
  2814  func TestCreateAndListAddresses(t *testing.T) {
  2815  	require := require.New(t)
  2816  
  2817  	env := setup(t, &envConfig{
  2818  		keystoreUsers: []*user{{
  2819  			username: username,
  2820  			password: password,
  2821  		}},
  2822  	})
  2823  	service := &Service{vm: env.vm}
  2824  	env.vm.ctx.Lock.Unlock()
  2825  
  2826  	createArgs := &api.UserPass{
  2827  		Username: username,
  2828  		Password: password,
  2829  	}
  2830  	createReply := &api.JSONAddress{}
  2831  
  2832  	require.NoError(service.CreateAddress(nil, createArgs, createReply))
  2833  
  2834  	newAddr := createReply.Address
  2835  
  2836  	listArgs := &api.UserPass{
  2837  		Username: username,
  2838  		Password: password,
  2839  	}
  2840  	listReply := &api.JSONAddresses{}
  2841  
  2842  	require.NoError(service.ListAddresses(nil, listArgs, listReply))
  2843  	require.Contains(listReply.Addresses, newAddr)
  2844  }
  2845  
  2846  func TestImport(t *testing.T) {
  2847  	for _, tc := range testCases {
  2848  		t.Run(tc.name, func(t *testing.T) {
  2849  			require := require.New(t)
  2850  
  2851  			env := setup(t, &envConfig{
  2852  				isCustomFeeAsset: !tc.avaxAsset,
  2853  				keystoreUsers: []*user{{
  2854  					username:    username,
  2855  					password:    password,
  2856  					initialKeys: keys,
  2857  				}},
  2858  			})
  2859  			service := &Service{vm: env.vm}
  2860  			env.vm.ctx.Lock.Unlock()
  2861  
  2862  			assetID := env.genesisTx.ID()
  2863  			addr0 := keys[0].PublicKey().Address()
  2864  
  2865  			utxo := &avax.UTXO{
  2866  				UTXOID: avax.UTXOID{TxID: ids.Empty},
  2867  				Asset:  avax.Asset{ID: assetID},
  2868  				Out: &secp256k1fx.TransferOutput{
  2869  					Amt: 7,
  2870  					OutputOwners: secp256k1fx.OutputOwners{
  2871  						Threshold: 1,
  2872  						Addrs:     []ids.ShortID{addr0},
  2873  					},
  2874  				},
  2875  			}
  2876  			utxoBytes, err := env.vm.parser.Codec().Marshal(txs.CodecVersion, utxo)
  2877  			require.NoError(err)
  2878  
  2879  			peerSharedMemory := env.sharedMemory.NewSharedMemory(constants.PlatformChainID)
  2880  			utxoID := utxo.InputID()
  2881  			require.NoError(peerSharedMemory.Apply(map[ids.ID]*atomic.Requests{
  2882  				env.vm.ctx.ChainID: {
  2883  					PutRequests: []*atomic.Element{{
  2884  						Key:   utxoID[:],
  2885  						Value: utxoBytes,
  2886  						Traits: [][]byte{
  2887  							addr0.Bytes(),
  2888  						},
  2889  					}},
  2890  				},
  2891  			}))
  2892  
  2893  			addrStr, err := env.vm.FormatLocalAddress(keys[0].PublicKey().Address())
  2894  			require.NoError(err)
  2895  			args := &ImportArgs{
  2896  				UserPass: api.UserPass{
  2897  					Username: username,
  2898  					Password: password,
  2899  				},
  2900  				SourceChain: "P",
  2901  				To:          addrStr,
  2902  			}
  2903  			reply := &api.JSONTxID{}
  2904  			require.NoError(service.Import(nil, args, reply))
  2905  		})
  2906  	}
  2907  }
  2908  
  2909  func TestServiceGetBlock(t *testing.T) {
  2910  	ctrl := gomock.NewController(t)
  2911  
  2912  	blockID := ids.GenerateTestID()
  2913  
  2914  	type test struct {
  2915  		name                        string
  2916  		serviceAndExpectedBlockFunc func(t *testing.T, ctrl *gomock.Controller) (*Service, interface{})
  2917  		encoding                    formatting.Encoding
  2918  		expectedErr                 error
  2919  	}
  2920  
  2921  	tests := []test{
  2922  		{
  2923  			name: "chain not linearized",
  2924  			serviceAndExpectedBlockFunc: func(*testing.T, *gomock.Controller) (*Service, interface{}) {
  2925  				return &Service{
  2926  					vm: &VM{
  2927  						ctx: &snow.Context{
  2928  							Log: logging.NoLog{},
  2929  						},
  2930  					},
  2931  				}, nil
  2932  			},
  2933  			encoding:    formatting.Hex,
  2934  			expectedErr: errNotLinearized,
  2935  		},
  2936  		{
  2937  			name: "block not found",
  2938  			serviceAndExpectedBlockFunc: func(_ *testing.T, ctrl *gomock.Controller) (*Service, interface{}) {
  2939  				manager := executor.NewMockManager(ctrl)
  2940  				manager.EXPECT().GetStatelessBlock(blockID).Return(nil, database.ErrNotFound)
  2941  				return &Service{
  2942  					vm: &VM{
  2943  						chainManager: manager,
  2944  						ctx: &snow.Context{
  2945  							Log: logging.NoLog{},
  2946  						},
  2947  					},
  2948  				}, nil
  2949  			},
  2950  			encoding:    formatting.Hex,
  2951  			expectedErr: database.ErrNotFound,
  2952  		},
  2953  		{
  2954  			name: "JSON format",
  2955  			serviceAndExpectedBlockFunc: func(_ *testing.T, ctrl *gomock.Controller) (*Service, interface{}) {
  2956  				block := block.NewMockBlock(ctrl)
  2957  				block.EXPECT().InitCtx(gomock.Any())
  2958  				block.EXPECT().Txs().Return(nil)
  2959  
  2960  				manager := executor.NewMockManager(ctrl)
  2961  				manager.EXPECT().GetStatelessBlock(blockID).Return(block, nil)
  2962  				return &Service{
  2963  					vm: &VM{
  2964  						chainManager: manager,
  2965  						ctx: &snow.Context{
  2966  							Log: logging.NoLog{},
  2967  						},
  2968  					},
  2969  				}, block
  2970  			},
  2971  			encoding:    formatting.JSON,
  2972  			expectedErr: nil,
  2973  		},
  2974  		{
  2975  			name: "hex format",
  2976  			serviceAndExpectedBlockFunc: func(t *testing.T, ctrl *gomock.Controller) (*Service, interface{}) {
  2977  				block := block.NewMockBlock(ctrl)
  2978  				blockBytes := []byte("hi mom")
  2979  				block.EXPECT().Bytes().Return(blockBytes)
  2980  
  2981  				expected, err := formatting.Encode(formatting.Hex, blockBytes)
  2982  				require.NoError(t, err)
  2983  
  2984  				manager := executor.NewMockManager(ctrl)
  2985  				manager.EXPECT().GetStatelessBlock(blockID).Return(block, nil)
  2986  				return &Service{
  2987  					vm: &VM{
  2988  						chainManager: manager,
  2989  						ctx: &snow.Context{
  2990  							Log: logging.NoLog{},
  2991  						},
  2992  					},
  2993  				}, expected
  2994  			},
  2995  			encoding:    formatting.Hex,
  2996  			expectedErr: nil,
  2997  		},
  2998  		{
  2999  			name: "hexc format",
  3000  			serviceAndExpectedBlockFunc: func(t *testing.T, ctrl *gomock.Controller) (*Service, interface{}) {
  3001  				block := block.NewMockBlock(ctrl)
  3002  				blockBytes := []byte("hi mom")
  3003  				block.EXPECT().Bytes().Return(blockBytes)
  3004  
  3005  				expected, err := formatting.Encode(formatting.HexC, blockBytes)
  3006  				require.NoError(t, err)
  3007  
  3008  				manager := executor.NewMockManager(ctrl)
  3009  				manager.EXPECT().GetStatelessBlock(blockID).Return(block, nil)
  3010  				return &Service{
  3011  					vm: &VM{
  3012  						chainManager: manager,
  3013  						ctx: &snow.Context{
  3014  							Log: logging.NoLog{},
  3015  						},
  3016  					},
  3017  				}, expected
  3018  			},
  3019  			encoding:    formatting.HexC,
  3020  			expectedErr: nil,
  3021  		},
  3022  		{
  3023  			name: "hexnc format",
  3024  			serviceAndExpectedBlockFunc: func(t *testing.T, ctrl *gomock.Controller) (*Service, interface{}) {
  3025  				block := block.NewMockBlock(ctrl)
  3026  				blockBytes := []byte("hi mom")
  3027  				block.EXPECT().Bytes().Return(blockBytes)
  3028  
  3029  				expected, err := formatting.Encode(formatting.HexNC, blockBytes)
  3030  				require.NoError(t, err)
  3031  
  3032  				manager := executor.NewMockManager(ctrl)
  3033  				manager.EXPECT().GetStatelessBlock(blockID).Return(block, nil)
  3034  				return &Service{
  3035  					vm: &VM{
  3036  						chainManager: manager,
  3037  						ctx: &snow.Context{
  3038  							Log: logging.NoLog{},
  3039  						},
  3040  					},
  3041  				}, expected
  3042  			},
  3043  			encoding:    formatting.HexNC,
  3044  			expectedErr: nil,
  3045  		},
  3046  	}
  3047  
  3048  	for _, tt := range tests {
  3049  		t.Run(tt.name, func(t *testing.T) {
  3050  			require := require.New(t)
  3051  
  3052  			service, expected := tt.serviceAndExpectedBlockFunc(t, ctrl)
  3053  
  3054  			args := &api.GetBlockArgs{
  3055  				BlockID:  blockID,
  3056  				Encoding: tt.encoding,
  3057  			}
  3058  			reply := &api.GetBlockResponse{}
  3059  			err := service.GetBlock(nil, args, reply)
  3060  			require.ErrorIs(err, tt.expectedErr)
  3061  			if tt.expectedErr != nil {
  3062  				return
  3063  			}
  3064  			require.Equal(tt.encoding, reply.Encoding)
  3065  
  3066  			expectedJSON, err := json.Marshal(expected)
  3067  			require.NoError(err)
  3068  
  3069  			require.Equal(json.RawMessage(expectedJSON), reply.Block)
  3070  		})
  3071  	}
  3072  }
  3073  
  3074  func TestServiceGetBlockByHeight(t *testing.T) {
  3075  	ctrl := gomock.NewController(t)
  3076  
  3077  	blockID := ids.GenerateTestID()
  3078  	blockHeight := uint64(1337)
  3079  
  3080  	type test struct {
  3081  		name                        string
  3082  		serviceAndExpectedBlockFunc func(t *testing.T, ctrl *gomock.Controller) (*Service, interface{})
  3083  		encoding                    formatting.Encoding
  3084  		expectedErr                 error
  3085  	}
  3086  
  3087  	tests := []test{
  3088  		{
  3089  			name: "chain not linearized",
  3090  			serviceAndExpectedBlockFunc: func(*testing.T, *gomock.Controller) (*Service, interface{}) {
  3091  				return &Service{
  3092  					vm: &VM{
  3093  						ctx: &snow.Context{
  3094  							Log: logging.NoLog{},
  3095  						},
  3096  					},
  3097  				}, nil
  3098  			},
  3099  			encoding:    formatting.Hex,
  3100  			expectedErr: errNotLinearized,
  3101  		},
  3102  		{
  3103  			name: "block height not found",
  3104  			serviceAndExpectedBlockFunc: func(_ *testing.T, ctrl *gomock.Controller) (*Service, interface{}) {
  3105  				state := state.NewMockState(ctrl)
  3106  				state.EXPECT().GetBlockIDAtHeight(blockHeight).Return(ids.Empty, database.ErrNotFound)
  3107  
  3108  				manager := executor.NewMockManager(ctrl)
  3109  				return &Service{
  3110  					vm: &VM{
  3111  						state:        state,
  3112  						chainManager: manager,
  3113  						ctx: &snow.Context{
  3114  							Log: logging.NoLog{},
  3115  						},
  3116  					},
  3117  				}, nil
  3118  			},
  3119  			encoding:    formatting.Hex,
  3120  			expectedErr: database.ErrNotFound,
  3121  		},
  3122  		{
  3123  			name: "block not found",
  3124  			serviceAndExpectedBlockFunc: func(_ *testing.T, ctrl *gomock.Controller) (*Service, interface{}) {
  3125  				state := state.NewMockState(ctrl)
  3126  				state.EXPECT().GetBlockIDAtHeight(blockHeight).Return(blockID, nil)
  3127  
  3128  				manager := executor.NewMockManager(ctrl)
  3129  				manager.EXPECT().GetStatelessBlock(blockID).Return(nil, database.ErrNotFound)
  3130  				return &Service{
  3131  					vm: &VM{
  3132  						state:        state,
  3133  						chainManager: manager,
  3134  						ctx: &snow.Context{
  3135  							Log: logging.NoLog{},
  3136  						},
  3137  					},
  3138  				}, nil
  3139  			},
  3140  			encoding:    formatting.Hex,
  3141  			expectedErr: database.ErrNotFound,
  3142  		},
  3143  		{
  3144  			name: "JSON format",
  3145  			serviceAndExpectedBlockFunc: func(_ *testing.T, ctrl *gomock.Controller) (*Service, interface{}) {
  3146  				block := block.NewMockBlock(ctrl)
  3147  				block.EXPECT().InitCtx(gomock.Any())
  3148  				block.EXPECT().Txs().Return(nil)
  3149  
  3150  				state := state.NewMockState(ctrl)
  3151  				state.EXPECT().GetBlockIDAtHeight(blockHeight).Return(blockID, nil)
  3152  
  3153  				manager := executor.NewMockManager(ctrl)
  3154  				manager.EXPECT().GetStatelessBlock(blockID).Return(block, nil)
  3155  				return &Service{
  3156  					vm: &VM{
  3157  						state:        state,
  3158  						chainManager: manager,
  3159  						ctx: &snow.Context{
  3160  							Log: logging.NoLog{},
  3161  						},
  3162  					},
  3163  				}, block
  3164  			},
  3165  			encoding:    formatting.JSON,
  3166  			expectedErr: nil,
  3167  		},
  3168  		{
  3169  			name: "hex format",
  3170  			serviceAndExpectedBlockFunc: func(t *testing.T, ctrl *gomock.Controller) (*Service, interface{}) {
  3171  				block := block.NewMockBlock(ctrl)
  3172  				blockBytes := []byte("hi mom")
  3173  				block.EXPECT().Bytes().Return(blockBytes)
  3174  
  3175  				state := state.NewMockState(ctrl)
  3176  				state.EXPECT().GetBlockIDAtHeight(blockHeight).Return(blockID, nil)
  3177  
  3178  				expected, err := formatting.Encode(formatting.Hex, blockBytes)
  3179  				require.NoError(t, err)
  3180  
  3181  				manager := executor.NewMockManager(ctrl)
  3182  				manager.EXPECT().GetStatelessBlock(blockID).Return(block, nil)
  3183  				return &Service{
  3184  					vm: &VM{
  3185  						state:        state,
  3186  						chainManager: manager,
  3187  						ctx: &snow.Context{
  3188  							Log: logging.NoLog{},
  3189  						},
  3190  					},
  3191  				}, expected
  3192  			},
  3193  			encoding:    formatting.Hex,
  3194  			expectedErr: nil,
  3195  		},
  3196  		{
  3197  			name: "hexc format",
  3198  			serviceAndExpectedBlockFunc: func(t *testing.T, ctrl *gomock.Controller) (*Service, interface{}) {
  3199  				block := block.NewMockBlock(ctrl)
  3200  				blockBytes := []byte("hi mom")
  3201  				block.EXPECT().Bytes().Return(blockBytes)
  3202  
  3203  				state := state.NewMockState(ctrl)
  3204  				state.EXPECT().GetBlockIDAtHeight(blockHeight).Return(blockID, nil)
  3205  
  3206  				expected, err := formatting.Encode(formatting.HexC, blockBytes)
  3207  				require.NoError(t, err)
  3208  
  3209  				manager := executor.NewMockManager(ctrl)
  3210  				manager.EXPECT().GetStatelessBlock(blockID).Return(block, nil)
  3211  				return &Service{
  3212  					vm: &VM{
  3213  						state:        state,
  3214  						chainManager: manager,
  3215  						ctx: &snow.Context{
  3216  							Log: logging.NoLog{},
  3217  						},
  3218  					},
  3219  				}, expected
  3220  			},
  3221  			encoding:    formatting.HexC,
  3222  			expectedErr: nil,
  3223  		},
  3224  		{
  3225  			name: "hexnc format",
  3226  			serviceAndExpectedBlockFunc: func(t *testing.T, ctrl *gomock.Controller) (*Service, interface{}) {
  3227  				block := block.NewMockBlock(ctrl)
  3228  				blockBytes := []byte("hi mom")
  3229  				block.EXPECT().Bytes().Return(blockBytes)
  3230  
  3231  				state := state.NewMockState(ctrl)
  3232  				state.EXPECT().GetBlockIDAtHeight(blockHeight).Return(blockID, nil)
  3233  
  3234  				expected, err := formatting.Encode(formatting.HexNC, blockBytes)
  3235  				require.NoError(t, err)
  3236  
  3237  				manager := executor.NewMockManager(ctrl)
  3238  				manager.EXPECT().GetStatelessBlock(blockID).Return(block, nil)
  3239  				return &Service{
  3240  					vm: &VM{
  3241  						state:        state,
  3242  						chainManager: manager,
  3243  						ctx: &snow.Context{
  3244  							Log: logging.NoLog{},
  3245  						},
  3246  					},
  3247  				}, expected
  3248  			},
  3249  			encoding:    formatting.HexNC,
  3250  			expectedErr: nil,
  3251  		},
  3252  	}
  3253  
  3254  	for _, tt := range tests {
  3255  		t.Run(tt.name, func(t *testing.T) {
  3256  			require := require.New(t)
  3257  
  3258  			service, expected := tt.serviceAndExpectedBlockFunc(t, ctrl)
  3259  
  3260  			args := &api.GetBlockByHeightArgs{
  3261  				Height:   avajson.Uint64(blockHeight),
  3262  				Encoding: tt.encoding,
  3263  			}
  3264  			reply := &api.GetBlockResponse{}
  3265  			err := service.GetBlockByHeight(nil, args, reply)
  3266  			require.ErrorIs(err, tt.expectedErr)
  3267  			if tt.expectedErr != nil {
  3268  				return
  3269  			}
  3270  			require.Equal(tt.encoding, reply.Encoding)
  3271  
  3272  			expectedJSON, err := json.Marshal(expected)
  3273  			require.NoError(err)
  3274  
  3275  			require.Equal(json.RawMessage(expectedJSON), reply.Block)
  3276  		})
  3277  	}
  3278  }
  3279  
  3280  func TestServiceGetHeight(t *testing.T) {
  3281  	ctrl := gomock.NewController(t)
  3282  
  3283  	blockID := ids.GenerateTestID()
  3284  	blockHeight := uint64(1337)
  3285  
  3286  	type test struct {
  3287  		name        string
  3288  		serviceFunc func(ctrl *gomock.Controller) *Service
  3289  		expectedErr error
  3290  	}
  3291  
  3292  	tests := []test{
  3293  		{
  3294  			name: "chain not linearized",
  3295  			serviceFunc: func(*gomock.Controller) *Service {
  3296  				return &Service{
  3297  					vm: &VM{
  3298  						ctx: &snow.Context{
  3299  							Log: logging.NoLog{},
  3300  						},
  3301  					},
  3302  				}
  3303  			},
  3304  			expectedErr: errNotLinearized,
  3305  		},
  3306  		{
  3307  			name: "block not found",
  3308  			serviceFunc: func(ctrl *gomock.Controller) *Service {
  3309  				state := state.NewMockState(ctrl)
  3310  				state.EXPECT().GetLastAccepted().Return(blockID)
  3311  
  3312  				manager := executor.NewMockManager(ctrl)
  3313  				manager.EXPECT().GetStatelessBlock(blockID).Return(nil, database.ErrNotFound)
  3314  				return &Service{
  3315  					vm: &VM{
  3316  						state:        state,
  3317  						chainManager: manager,
  3318  						ctx: &snow.Context{
  3319  							Log: logging.NoLog{},
  3320  						},
  3321  					},
  3322  				}
  3323  			},
  3324  			expectedErr: database.ErrNotFound,
  3325  		},
  3326  		{
  3327  			name: "happy path",
  3328  			serviceFunc: func(ctrl *gomock.Controller) *Service {
  3329  				state := state.NewMockState(ctrl)
  3330  				state.EXPECT().GetLastAccepted().Return(blockID)
  3331  
  3332  				block := block.NewMockBlock(ctrl)
  3333  				block.EXPECT().Height().Return(blockHeight)
  3334  
  3335  				manager := executor.NewMockManager(ctrl)
  3336  				manager.EXPECT().GetStatelessBlock(blockID).Return(block, nil)
  3337  				return &Service{
  3338  					vm: &VM{
  3339  						state:        state,
  3340  						chainManager: manager,
  3341  						ctx: &snow.Context{
  3342  							Log: logging.NoLog{},
  3343  						},
  3344  					},
  3345  				}
  3346  			},
  3347  			expectedErr: nil,
  3348  		},
  3349  	}
  3350  
  3351  	for _, tt := range tests {
  3352  		t.Run(tt.name, func(t *testing.T) {
  3353  			require := require.New(t)
  3354  			service := tt.serviceFunc(ctrl)
  3355  
  3356  			reply := &api.GetHeightResponse{}
  3357  			err := service.GetHeight(nil, nil, reply)
  3358  			require.ErrorIs(err, tt.expectedErr)
  3359  			if tt.expectedErr != nil {
  3360  				return
  3361  			}
  3362  			require.Equal(avajson.Uint64(blockHeight), reply.Height)
  3363  		})
  3364  	}
  3365  }