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