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 }