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