github.com/MetalBlockchain/metalgo@v1.11.9/vms/avm/txs/executor/semantic_verifier_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 executor 5 6 import ( 7 "reflect" 8 "testing" 9 10 "github.com/stretchr/testify/require" 11 "go.uber.org/mock/gomock" 12 13 "github.com/MetalBlockchain/metalgo/chains/atomic" 14 "github.com/MetalBlockchain/metalgo/database" 15 "github.com/MetalBlockchain/metalgo/database/memdb" 16 "github.com/MetalBlockchain/metalgo/database/prefixdb" 17 "github.com/MetalBlockchain/metalgo/ids" 18 "github.com/MetalBlockchain/metalgo/snow/snowtest" 19 "github.com/MetalBlockchain/metalgo/snow/validators" 20 "github.com/MetalBlockchain/metalgo/utils/constants" 21 "github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1" 22 "github.com/MetalBlockchain/metalgo/utils/logging" 23 "github.com/MetalBlockchain/metalgo/utils/timer/mockable" 24 "github.com/MetalBlockchain/metalgo/vms/avm/fxs" 25 "github.com/MetalBlockchain/metalgo/vms/avm/state" 26 "github.com/MetalBlockchain/metalgo/vms/avm/txs" 27 "github.com/MetalBlockchain/metalgo/vms/components/avax" 28 "github.com/MetalBlockchain/metalgo/vms/components/verify" 29 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 30 ) 31 32 func TestSemanticVerifierBaseTx(t *testing.T) { 33 ctx := snowtest.Context(t, snowtest.XChainID) 34 35 typeToFxIndex := make(map[reflect.Type]int) 36 secpFx := &secp256k1fx.Fx{} 37 parser, err := txs.NewCustomParser( 38 typeToFxIndex, 39 new(mockable.Clock), 40 logging.NoWarn{}, 41 []fxs.Fx{ 42 secpFx, 43 }, 44 ) 45 require.NoError(t, err) 46 47 codec := parser.Codec() 48 txID := ids.GenerateTestID() 49 utxoID := avax.UTXOID{ 50 TxID: txID, 51 OutputIndex: 2, 52 } 53 asset := avax.Asset{ 54 ID: ids.GenerateTestID(), 55 } 56 inputSigner := secp256k1fx.Input{ 57 SigIndices: []uint32{ 58 0, 59 }, 60 } 61 fxInput := secp256k1fx.TransferInput{ 62 Amt: 12345, 63 Input: inputSigner, 64 } 65 input := avax.TransferableInput{ 66 UTXOID: utxoID, 67 Asset: asset, 68 In: &fxInput, 69 } 70 baseTx := txs.BaseTx{ 71 BaseTx: avax.BaseTx{ 72 Ins: []*avax.TransferableInput{ 73 &input, 74 }, 75 }, 76 } 77 78 backend := &Backend{ 79 Ctx: ctx, 80 Config: &feeConfig, 81 Fxs: []*fxs.ParsedFx{ 82 { 83 ID: secp256k1fx.ID, 84 Fx: secpFx, 85 }, 86 }, 87 TypeToFxIndex: typeToFxIndex, 88 Codec: codec, 89 FeeAssetID: ids.GenerateTestID(), 90 Bootstrapped: true, 91 } 92 require.NoError(t, secpFx.Bootstrapped()) 93 94 outputOwners := secp256k1fx.OutputOwners{ 95 Threshold: 1, 96 Addrs: []ids.ShortID{ 97 keys[0].Address(), 98 }, 99 } 100 output := secp256k1fx.TransferOutput{ 101 Amt: 12345, 102 OutputOwners: outputOwners, 103 } 104 utxo := avax.UTXO{ 105 UTXOID: utxoID, 106 Asset: asset, 107 Out: &output, 108 } 109 unsignedCreateAssetTx := txs.CreateAssetTx{ 110 States: []*txs.InitialState{{ 111 FxIndex: 0, 112 }}, 113 } 114 createAssetTx := txs.Tx{ 115 Unsigned: &unsignedCreateAssetTx, 116 } 117 118 tests := []struct { 119 name string 120 stateFunc func(*gomock.Controller) state.Chain 121 txFunc func(*require.Assertions) *txs.Tx 122 err error 123 }{ 124 { 125 name: "valid", 126 stateFunc: func(ctrl *gomock.Controller) state.Chain { 127 state := state.NewMockChain(ctrl) 128 129 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil) 130 state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil) 131 132 return state 133 }, 134 txFunc: func(require *require.Assertions) *txs.Tx { 135 tx := &txs.Tx{ 136 Unsigned: &baseTx, 137 } 138 require.NoError(tx.SignSECP256K1Fx( 139 codec, 140 [][]*secp256k1.PrivateKey{ 141 {keys[0]}, 142 }, 143 )) 144 return tx 145 }, 146 err: nil, 147 }, 148 { 149 name: "assetID mismatch", 150 stateFunc: func(ctrl *gomock.Controller) state.Chain { 151 state := state.NewMockChain(ctrl) 152 153 utxo := utxo 154 utxo.Asset.ID = ids.GenerateTestID() 155 156 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil) 157 158 return state 159 }, 160 txFunc: func(require *require.Assertions) *txs.Tx { 161 tx := &txs.Tx{ 162 Unsigned: &baseTx, 163 } 164 require.NoError(tx.SignSECP256K1Fx( 165 codec, 166 [][]*secp256k1.PrivateKey{ 167 {keys[0]}, 168 }, 169 )) 170 return tx 171 }, 172 err: errAssetIDMismatch, 173 }, 174 { 175 name: "not allowed input feature extension", 176 stateFunc: func(ctrl *gomock.Controller) state.Chain { 177 state := state.NewMockChain(ctrl) 178 179 unsignedCreateAssetTx := unsignedCreateAssetTx 180 unsignedCreateAssetTx.States = nil 181 182 createAssetTx := txs.Tx{ 183 Unsigned: &unsignedCreateAssetTx, 184 } 185 186 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil) 187 state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil) 188 189 return state 190 }, 191 txFunc: func(require *require.Assertions) *txs.Tx { 192 tx := &txs.Tx{ 193 Unsigned: &baseTx, 194 } 195 require.NoError(tx.SignSECP256K1Fx( 196 codec, 197 [][]*secp256k1.PrivateKey{ 198 {keys[0]}, 199 }, 200 )) 201 return tx 202 }, 203 err: errIncompatibleFx, 204 }, 205 { 206 name: "invalid signature", 207 stateFunc: func(ctrl *gomock.Controller) state.Chain { 208 state := state.NewMockChain(ctrl) 209 210 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil) 211 state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil) 212 213 return state 214 }, 215 txFunc: func(require *require.Assertions) *txs.Tx { 216 tx := &txs.Tx{ 217 Unsigned: &baseTx, 218 } 219 require.NoError(tx.SignSECP256K1Fx( 220 codec, 221 [][]*secp256k1.PrivateKey{ 222 {keys[1]}, 223 }, 224 )) 225 return tx 226 }, 227 err: secp256k1fx.ErrWrongSig, 228 }, 229 { 230 name: "missing UTXO", 231 stateFunc: func(ctrl *gomock.Controller) state.Chain { 232 state := state.NewMockChain(ctrl) 233 234 state.EXPECT().GetUTXO(utxoID.InputID()).Return(nil, database.ErrNotFound) 235 236 return state 237 }, 238 txFunc: func(require *require.Assertions) *txs.Tx { 239 tx := &txs.Tx{ 240 Unsigned: &baseTx, 241 } 242 require.NoError(tx.SignSECP256K1Fx( 243 codec, 244 [][]*secp256k1.PrivateKey{ 245 {keys[0]}, 246 }, 247 )) 248 return tx 249 }, 250 err: database.ErrNotFound, 251 }, 252 { 253 name: "invalid UTXO amount", 254 stateFunc: func(ctrl *gomock.Controller) state.Chain { 255 state := state.NewMockChain(ctrl) 256 257 output := output 258 output.Amt-- 259 260 utxo := utxo 261 utxo.Out = &output 262 263 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil) 264 state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil) 265 266 return state 267 }, 268 txFunc: func(require *require.Assertions) *txs.Tx { 269 tx := &txs.Tx{ 270 Unsigned: &baseTx, 271 } 272 require.NoError(tx.SignSECP256K1Fx( 273 codec, 274 [][]*secp256k1.PrivateKey{ 275 {keys[0]}, 276 }, 277 )) 278 return tx 279 }, 280 err: secp256k1fx.ErrMismatchedAmounts, 281 }, 282 { 283 name: "not allowed output feature extension", 284 stateFunc: func(ctrl *gomock.Controller) state.Chain { 285 state := state.NewMockChain(ctrl) 286 287 unsignedCreateAssetTx := unsignedCreateAssetTx 288 unsignedCreateAssetTx.States = nil 289 290 createAssetTx := txs.Tx{ 291 Unsigned: &unsignedCreateAssetTx, 292 } 293 294 state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil) 295 296 return state 297 }, 298 txFunc: func(require *require.Assertions) *txs.Tx { 299 baseTx := baseTx 300 baseTx.Ins = nil 301 baseTx.Outs = []*avax.TransferableOutput{ 302 { 303 Asset: asset, 304 Out: &output, 305 }, 306 } 307 tx := &txs.Tx{ 308 Unsigned: &baseTx, 309 } 310 require.NoError(tx.SignSECP256K1Fx( 311 codec, 312 [][]*secp256k1.PrivateKey{}, 313 )) 314 return tx 315 }, 316 err: errIncompatibleFx, 317 }, 318 { 319 name: "unknown asset", 320 stateFunc: func(ctrl *gomock.Controller) state.Chain { 321 state := state.NewMockChain(ctrl) 322 323 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil) 324 state.EXPECT().GetTx(asset.ID).Return(nil, database.ErrNotFound) 325 326 return state 327 }, 328 txFunc: func(require *require.Assertions) *txs.Tx { 329 tx := &txs.Tx{ 330 Unsigned: &baseTx, 331 } 332 require.NoError(tx.SignSECP256K1Fx( 333 codec, 334 [][]*secp256k1.PrivateKey{ 335 {keys[0]}, 336 }, 337 )) 338 return tx 339 }, 340 err: database.ErrNotFound, 341 }, 342 { 343 name: "not an asset", 344 stateFunc: func(ctrl *gomock.Controller) state.Chain { 345 state := state.NewMockChain(ctrl) 346 347 tx := txs.Tx{ 348 Unsigned: &baseTx, 349 } 350 351 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil) 352 state.EXPECT().GetTx(asset.ID).Return(&tx, nil) 353 354 return state 355 }, 356 txFunc: func(require *require.Assertions) *txs.Tx { 357 tx := &txs.Tx{ 358 Unsigned: &baseTx, 359 } 360 require.NoError(tx.SignSECP256K1Fx( 361 codec, 362 [][]*secp256k1.PrivateKey{ 363 {keys[0]}, 364 }, 365 )) 366 return tx 367 }, 368 err: errNotAnAsset, 369 }, 370 } 371 for _, test := range tests { 372 t.Run(test.name, func(t *testing.T) { 373 require := require.New(t) 374 ctrl := gomock.NewController(t) 375 376 state := test.stateFunc(ctrl) 377 tx := test.txFunc(require) 378 379 err = tx.Unsigned.Visit(&SemanticVerifier{ 380 Backend: backend, 381 State: state, 382 Tx: tx, 383 }) 384 require.ErrorIs(err, test.err) 385 }) 386 } 387 } 388 389 func TestSemanticVerifierExportTx(t *testing.T) { 390 ctx := snowtest.Context(t, snowtest.XChainID) 391 392 typeToFxIndex := make(map[reflect.Type]int) 393 secpFx := &secp256k1fx.Fx{} 394 parser, err := txs.NewCustomParser( 395 typeToFxIndex, 396 new(mockable.Clock), 397 logging.NoWarn{}, 398 []fxs.Fx{ 399 secpFx, 400 }, 401 ) 402 require.NoError(t, err) 403 404 codec := parser.Codec() 405 txID := ids.GenerateTestID() 406 utxoID := avax.UTXOID{ 407 TxID: txID, 408 OutputIndex: 2, 409 } 410 asset := avax.Asset{ 411 ID: ids.GenerateTestID(), 412 } 413 inputSigner := secp256k1fx.Input{ 414 SigIndices: []uint32{ 415 0, 416 }, 417 } 418 fxInput := secp256k1fx.TransferInput{ 419 Amt: 12345, 420 Input: inputSigner, 421 } 422 input := avax.TransferableInput{ 423 UTXOID: utxoID, 424 Asset: asset, 425 In: &fxInput, 426 } 427 baseTx := txs.BaseTx{ 428 BaseTx: avax.BaseTx{ 429 Ins: []*avax.TransferableInput{ 430 &input, 431 }, 432 }, 433 } 434 exportTx := txs.ExportTx{ 435 BaseTx: baseTx, 436 DestinationChain: ctx.CChainID, 437 } 438 439 backend := &Backend{ 440 Ctx: ctx, 441 Config: &feeConfig, 442 Fxs: []*fxs.ParsedFx{ 443 { 444 ID: secp256k1fx.ID, 445 Fx: secpFx, 446 }, 447 }, 448 TypeToFxIndex: typeToFxIndex, 449 Codec: codec, 450 FeeAssetID: ids.GenerateTestID(), 451 Bootstrapped: true, 452 } 453 require.NoError(t, secpFx.Bootstrapped()) 454 455 outputOwners := secp256k1fx.OutputOwners{ 456 Threshold: 1, 457 Addrs: []ids.ShortID{ 458 keys[0].Address(), 459 }, 460 } 461 output := secp256k1fx.TransferOutput{ 462 Amt: 12345, 463 OutputOwners: outputOwners, 464 } 465 utxo := avax.UTXO{ 466 UTXOID: utxoID, 467 Asset: asset, 468 Out: &output, 469 } 470 unsignedCreateAssetTx := txs.CreateAssetTx{ 471 States: []*txs.InitialState{{ 472 FxIndex: 0, 473 }}, 474 } 475 createAssetTx := txs.Tx{ 476 Unsigned: &unsignedCreateAssetTx, 477 } 478 479 tests := []struct { 480 name string 481 stateFunc func(*gomock.Controller) state.Chain 482 txFunc func(*require.Assertions) *txs.Tx 483 err error 484 }{ 485 { 486 name: "valid", 487 stateFunc: func(ctrl *gomock.Controller) state.Chain { 488 state := state.NewMockChain(ctrl) 489 490 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil) 491 state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil) 492 493 return state 494 }, 495 txFunc: func(require *require.Assertions) *txs.Tx { 496 tx := &txs.Tx{ 497 Unsigned: &exportTx, 498 } 499 require.NoError(tx.SignSECP256K1Fx( 500 codec, 501 [][]*secp256k1.PrivateKey{ 502 {keys[0]}, 503 }, 504 )) 505 return tx 506 }, 507 err: nil, 508 }, 509 { 510 name: "assetID mismatch", 511 stateFunc: func(ctrl *gomock.Controller) state.Chain { 512 state := state.NewMockChain(ctrl) 513 514 utxo := utxo 515 utxo.Asset.ID = ids.GenerateTestID() 516 517 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil) 518 519 return state 520 }, 521 txFunc: func(require *require.Assertions) *txs.Tx { 522 tx := &txs.Tx{ 523 Unsigned: &exportTx, 524 } 525 require.NoError(tx.SignSECP256K1Fx( 526 codec, 527 [][]*secp256k1.PrivateKey{ 528 {keys[0]}, 529 }, 530 )) 531 return tx 532 }, 533 err: errAssetIDMismatch, 534 }, 535 { 536 name: "not allowed input feature extension", 537 stateFunc: func(ctrl *gomock.Controller) state.Chain { 538 state := state.NewMockChain(ctrl) 539 540 unsignedCreateAssetTx := unsignedCreateAssetTx 541 unsignedCreateAssetTx.States = nil 542 543 createAssetTx := txs.Tx{ 544 Unsigned: &unsignedCreateAssetTx, 545 } 546 547 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil) 548 state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil) 549 550 return state 551 }, 552 txFunc: func(require *require.Assertions) *txs.Tx { 553 tx := &txs.Tx{ 554 Unsigned: &exportTx, 555 } 556 require.NoError(tx.SignSECP256K1Fx( 557 codec, 558 [][]*secp256k1.PrivateKey{ 559 {keys[0]}, 560 }, 561 )) 562 return tx 563 }, 564 err: errIncompatibleFx, 565 }, 566 { 567 name: "invalid signature", 568 stateFunc: func(ctrl *gomock.Controller) state.Chain { 569 state := state.NewMockChain(ctrl) 570 571 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil) 572 state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil) 573 574 return state 575 }, 576 txFunc: func(require *require.Assertions) *txs.Tx { 577 tx := &txs.Tx{ 578 Unsigned: &exportTx, 579 } 580 require.NoError(tx.SignSECP256K1Fx( 581 codec, 582 [][]*secp256k1.PrivateKey{ 583 {keys[1]}, 584 }, 585 )) 586 return tx 587 }, 588 err: secp256k1fx.ErrWrongSig, 589 }, 590 { 591 name: "missing UTXO", 592 stateFunc: func(ctrl *gomock.Controller) state.Chain { 593 state := state.NewMockChain(ctrl) 594 595 state.EXPECT().GetUTXO(utxoID.InputID()).Return(nil, database.ErrNotFound) 596 597 return state 598 }, 599 txFunc: func(require *require.Assertions) *txs.Tx { 600 tx := &txs.Tx{ 601 Unsigned: &exportTx, 602 } 603 require.NoError(tx.SignSECP256K1Fx( 604 codec, 605 [][]*secp256k1.PrivateKey{ 606 {keys[0]}, 607 }, 608 )) 609 return tx 610 }, 611 err: database.ErrNotFound, 612 }, 613 { 614 name: "invalid UTXO amount", 615 stateFunc: func(ctrl *gomock.Controller) state.Chain { 616 state := state.NewMockChain(ctrl) 617 618 output := output 619 output.Amt-- 620 621 utxo := utxo 622 utxo.Out = &output 623 624 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil) 625 state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil) 626 627 return state 628 }, 629 txFunc: func(require *require.Assertions) *txs.Tx { 630 tx := &txs.Tx{ 631 Unsigned: &exportTx, 632 } 633 require.NoError(tx.SignSECP256K1Fx( 634 codec, 635 [][]*secp256k1.PrivateKey{ 636 {keys[0]}, 637 }, 638 )) 639 return tx 640 }, 641 err: secp256k1fx.ErrMismatchedAmounts, 642 }, 643 { 644 name: "not allowed output feature extension", 645 stateFunc: func(ctrl *gomock.Controller) state.Chain { 646 state := state.NewMockChain(ctrl) 647 648 unsignedCreateAssetTx := unsignedCreateAssetTx 649 unsignedCreateAssetTx.States = nil 650 651 createAssetTx := txs.Tx{ 652 Unsigned: &unsignedCreateAssetTx, 653 } 654 655 state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil) 656 657 return state 658 }, 659 txFunc: func(require *require.Assertions) *txs.Tx { 660 exportTx := exportTx 661 exportTx.Ins = nil 662 exportTx.ExportedOuts = []*avax.TransferableOutput{ 663 { 664 Asset: asset, 665 Out: &output, 666 }, 667 } 668 tx := &txs.Tx{ 669 Unsigned: &exportTx, 670 } 671 require.NoError(tx.SignSECP256K1Fx( 672 codec, 673 [][]*secp256k1.PrivateKey{}, 674 )) 675 return tx 676 }, 677 err: errIncompatibleFx, 678 }, 679 { 680 name: "unknown asset", 681 stateFunc: func(ctrl *gomock.Controller) state.Chain { 682 state := state.NewMockChain(ctrl) 683 684 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil) 685 state.EXPECT().GetTx(asset.ID).Return(nil, database.ErrNotFound) 686 687 return state 688 }, 689 txFunc: func(require *require.Assertions) *txs.Tx { 690 tx := &txs.Tx{ 691 Unsigned: &exportTx, 692 } 693 require.NoError(tx.SignSECP256K1Fx( 694 codec, 695 [][]*secp256k1.PrivateKey{ 696 {keys[0]}, 697 }, 698 )) 699 return tx 700 }, 701 err: database.ErrNotFound, 702 }, 703 { 704 name: "not an asset", 705 stateFunc: func(ctrl *gomock.Controller) state.Chain { 706 state := state.NewMockChain(ctrl) 707 708 tx := txs.Tx{ 709 Unsigned: &baseTx, 710 } 711 712 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil) 713 state.EXPECT().GetTx(asset.ID).Return(&tx, nil) 714 715 return state 716 }, 717 txFunc: func(require *require.Assertions) *txs.Tx { 718 tx := &txs.Tx{ 719 Unsigned: &exportTx, 720 } 721 require.NoError(tx.SignSECP256K1Fx( 722 codec, 723 [][]*secp256k1.PrivateKey{ 724 {keys[0]}, 725 }, 726 )) 727 return tx 728 }, 729 err: errNotAnAsset, 730 }, 731 } 732 for _, test := range tests { 733 t.Run(test.name, func(t *testing.T) { 734 require := require.New(t) 735 ctrl := gomock.NewController(t) 736 737 state := test.stateFunc(ctrl) 738 tx := test.txFunc(require) 739 740 err = tx.Unsigned.Visit(&SemanticVerifier{ 741 Backend: backend, 742 State: state, 743 Tx: tx, 744 }) 745 require.ErrorIs(err, test.err) 746 }) 747 } 748 } 749 750 func TestSemanticVerifierExportTxDifferentSubnet(t *testing.T) { 751 require := require.New(t) 752 ctrl := gomock.NewController(t) 753 754 ctx := snowtest.Context(t, snowtest.XChainID) 755 756 validatorState := validators.NewMockState(ctrl) 757 validatorState.EXPECT().GetSubnetID(gomock.Any(), ctx.CChainID).AnyTimes().Return(ids.GenerateTestID(), nil) 758 ctx.ValidatorState = validatorState 759 760 typeToFxIndex := make(map[reflect.Type]int) 761 secpFx := &secp256k1fx.Fx{} 762 parser, err := txs.NewCustomParser( 763 typeToFxIndex, 764 new(mockable.Clock), 765 logging.NoWarn{}, 766 []fxs.Fx{ 767 secpFx, 768 }, 769 ) 770 require.NoError(err) 771 772 codec := parser.Codec() 773 txID := ids.GenerateTestID() 774 utxoID := avax.UTXOID{ 775 TxID: txID, 776 OutputIndex: 2, 777 } 778 asset := avax.Asset{ 779 ID: ids.GenerateTestID(), 780 } 781 inputSigner := secp256k1fx.Input{ 782 SigIndices: []uint32{ 783 0, 784 }, 785 } 786 fxInput := secp256k1fx.TransferInput{ 787 Amt: 12345, 788 Input: inputSigner, 789 } 790 input := avax.TransferableInput{ 791 UTXOID: utxoID, 792 Asset: asset, 793 In: &fxInput, 794 } 795 baseTx := txs.BaseTx{ 796 BaseTx: avax.BaseTx{ 797 Ins: []*avax.TransferableInput{ 798 &input, 799 }, 800 }, 801 } 802 exportTx := txs.ExportTx{ 803 BaseTx: baseTx, 804 DestinationChain: ctx.CChainID, 805 } 806 807 backend := &Backend{ 808 Ctx: ctx, 809 Config: &feeConfig, 810 Fxs: []*fxs.ParsedFx{ 811 { 812 ID: secp256k1fx.ID, 813 Fx: secpFx, 814 }, 815 }, 816 TypeToFxIndex: typeToFxIndex, 817 Codec: codec, 818 FeeAssetID: ids.GenerateTestID(), 819 Bootstrapped: true, 820 } 821 require.NoError(secpFx.Bootstrapped()) 822 823 outputOwners := secp256k1fx.OutputOwners{ 824 Threshold: 1, 825 Addrs: []ids.ShortID{ 826 keys[0].Address(), 827 }, 828 } 829 output := secp256k1fx.TransferOutput{ 830 Amt: 12345, 831 OutputOwners: outputOwners, 832 } 833 utxo := avax.UTXO{ 834 UTXOID: utxoID, 835 Asset: asset, 836 Out: &output, 837 } 838 unsignedCreateAssetTx := txs.CreateAssetTx{ 839 States: []*txs.InitialState{{ 840 FxIndex: 0, 841 }}, 842 } 843 createAssetTx := txs.Tx{ 844 Unsigned: &unsignedCreateAssetTx, 845 } 846 847 state := state.NewMockChain(ctrl) 848 849 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil) 850 state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil) 851 852 tx := &txs.Tx{ 853 Unsigned: &exportTx, 854 } 855 require.NoError(tx.SignSECP256K1Fx( 856 codec, 857 [][]*secp256k1.PrivateKey{ 858 {keys[0]}, 859 }, 860 )) 861 862 err = tx.Unsigned.Visit(&SemanticVerifier{ 863 Backend: backend, 864 State: state, 865 Tx: tx, 866 }) 867 require.ErrorIs(err, verify.ErrMismatchedSubnetIDs) 868 } 869 870 func TestSemanticVerifierImportTx(t *testing.T) { 871 ctx := snowtest.Context(t, snowtest.XChainID) 872 873 m := atomic.NewMemory(prefixdb.New([]byte{0}, memdb.New())) 874 ctx.SharedMemory = m.NewSharedMemory(ctx.ChainID) 875 876 typeToFxIndex := make(map[reflect.Type]int) 877 fx := &secp256k1fx.Fx{} 878 parser, err := txs.NewCustomParser( 879 typeToFxIndex, 880 new(mockable.Clock), 881 logging.NoWarn{}, 882 []fxs.Fx{ 883 fx, 884 }, 885 ) 886 require.NoError(t, err) 887 888 codec := parser.Codec() 889 utxoID := avax.UTXOID{ 890 TxID: ids.GenerateTestID(), 891 OutputIndex: 2, 892 } 893 894 asset := avax.Asset{ 895 ID: ids.GenerateTestID(), 896 } 897 outputOwners := secp256k1fx.OutputOwners{ 898 Threshold: 1, 899 Addrs: []ids.ShortID{ 900 keys[0].Address(), 901 }, 902 } 903 baseTx := txs.BaseTx{ 904 BaseTx: avax.BaseTx{ 905 NetworkID: constants.UnitTestID, 906 BlockchainID: ctx.ChainID, 907 Outs: []*avax.TransferableOutput{{ 908 Asset: asset, 909 Out: &secp256k1fx.TransferOutput{ 910 Amt: 1000, 911 OutputOwners: outputOwners, 912 }, 913 }}, 914 }, 915 } 916 input := avax.TransferableInput{ 917 UTXOID: utxoID, 918 Asset: asset, 919 In: &secp256k1fx.TransferInput{ 920 Amt: 12345, 921 Input: secp256k1fx.Input{ 922 SigIndices: []uint32{0}, 923 }, 924 }, 925 } 926 unsignedImportTx := txs.ImportTx{ 927 BaseTx: baseTx, 928 SourceChain: ctx.CChainID, 929 ImportedIns: []*avax.TransferableInput{ 930 &input, 931 }, 932 } 933 importTx := &txs.Tx{ 934 Unsigned: &unsignedImportTx, 935 } 936 require.NoError(t, importTx.SignSECP256K1Fx( 937 codec, 938 [][]*secp256k1.PrivateKey{ 939 {keys[0]}, 940 }, 941 )) 942 943 backend := &Backend{ 944 Ctx: ctx, 945 Config: &feeConfig, 946 Fxs: []*fxs.ParsedFx{ 947 { 948 ID: secp256k1fx.ID, 949 Fx: fx, 950 }, 951 }, 952 TypeToFxIndex: typeToFxIndex, 953 Codec: codec, 954 FeeAssetID: ids.GenerateTestID(), 955 Bootstrapped: true, 956 } 957 require.NoError(t, fx.Bootstrapped()) 958 959 output := secp256k1fx.TransferOutput{ 960 Amt: 12345, 961 OutputOwners: outputOwners, 962 } 963 utxo := avax.UTXO{ 964 UTXOID: utxoID, 965 Asset: asset, 966 Out: &output, 967 } 968 utxoBytes, err := codec.Marshal(txs.CodecVersion, utxo) 969 require.NoError(t, err) 970 971 peerSharedMemory := m.NewSharedMemory(ctx.CChainID) 972 inputID := utxo.InputID() 973 require.NoError(t, peerSharedMemory.Apply(map[ids.ID]*atomic.Requests{ctx.ChainID: {PutRequests: []*atomic.Element{{ 974 Key: inputID[:], 975 Value: utxoBytes, 976 Traits: [][]byte{ 977 keys[0].PublicKey().Address().Bytes(), 978 }, 979 }}}})) 980 981 unsignedCreateAssetTx := txs.CreateAssetTx{ 982 States: []*txs.InitialState{{ 983 FxIndex: 0, 984 }}, 985 } 986 createAssetTx := txs.Tx{ 987 Unsigned: &unsignedCreateAssetTx, 988 } 989 tests := []struct { 990 name string 991 stateFunc func(*gomock.Controller) state.Chain 992 txFunc func(*require.Assertions) *txs.Tx 993 expectedErr error 994 }{ 995 { 996 name: "valid", 997 stateFunc: func(ctrl *gomock.Controller) state.Chain { 998 state := state.NewMockChain(ctrl) 999 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil).AnyTimes() 1000 state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil).AnyTimes() 1001 return state 1002 }, 1003 txFunc: func(*require.Assertions) *txs.Tx { 1004 return importTx 1005 }, 1006 expectedErr: nil, 1007 }, 1008 { 1009 name: "not allowed input feature extension", 1010 stateFunc: func(ctrl *gomock.Controller) state.Chain { 1011 state := state.NewMockChain(ctrl) 1012 unsignedCreateAssetTx := unsignedCreateAssetTx 1013 unsignedCreateAssetTx.States = nil 1014 createAssetTx := txs.Tx{ 1015 Unsigned: &unsignedCreateAssetTx, 1016 } 1017 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil).AnyTimes() 1018 state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil).AnyTimes() 1019 return state 1020 }, 1021 txFunc: func(*require.Assertions) *txs.Tx { 1022 return importTx 1023 }, 1024 expectedErr: errIncompatibleFx, 1025 }, 1026 { 1027 name: "invalid signature", 1028 stateFunc: func(ctrl *gomock.Controller) state.Chain { 1029 state := state.NewMockChain(ctrl) 1030 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil).AnyTimes() 1031 state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil).AnyTimes() 1032 return state 1033 }, 1034 txFunc: func(require *require.Assertions) *txs.Tx { 1035 tx := &txs.Tx{ 1036 Unsigned: &unsignedImportTx, 1037 } 1038 require.NoError(tx.SignSECP256K1Fx( 1039 codec, 1040 [][]*secp256k1.PrivateKey{ 1041 {keys[1]}, 1042 }, 1043 )) 1044 return tx 1045 }, 1046 expectedErr: secp256k1fx.ErrWrongSig, 1047 }, 1048 { 1049 name: "not allowed output feature extension", 1050 stateFunc: func(ctrl *gomock.Controller) state.Chain { 1051 state := state.NewMockChain(ctrl) 1052 unsignedCreateAssetTx := unsignedCreateAssetTx 1053 unsignedCreateAssetTx.States = nil 1054 createAssetTx := txs.Tx{ 1055 Unsigned: &unsignedCreateAssetTx, 1056 } 1057 state.EXPECT().GetTx(asset.ID).Return(&createAssetTx, nil).AnyTimes() 1058 return state 1059 }, 1060 txFunc: func(require *require.Assertions) *txs.Tx { 1061 importTx := unsignedImportTx 1062 importTx.Ins = nil 1063 importTx.ImportedIns = []*avax.TransferableInput{ 1064 &input, 1065 } 1066 tx := &txs.Tx{ 1067 Unsigned: &importTx, 1068 } 1069 require.NoError(tx.SignSECP256K1Fx( 1070 codec, 1071 nil, 1072 )) 1073 return tx 1074 }, 1075 expectedErr: errIncompatibleFx, 1076 }, 1077 { 1078 name: "unknown asset", 1079 stateFunc: func(ctrl *gomock.Controller) state.Chain { 1080 state := state.NewMockChain(ctrl) 1081 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil).AnyTimes() 1082 state.EXPECT().GetTx(asset.ID).Return(nil, database.ErrNotFound) 1083 return state 1084 }, 1085 txFunc: func(*require.Assertions) *txs.Tx { 1086 return importTx 1087 }, 1088 expectedErr: database.ErrNotFound, 1089 }, 1090 { 1091 name: "not an asset", 1092 stateFunc: func(ctrl *gomock.Controller) state.Chain { 1093 state := state.NewMockChain(ctrl) 1094 tx := txs.Tx{ 1095 Unsigned: &baseTx, 1096 } 1097 state.EXPECT().GetUTXO(utxoID.InputID()).Return(&utxo, nil).AnyTimes() 1098 state.EXPECT().GetTx(asset.ID).Return(&tx, nil) 1099 return state 1100 }, 1101 txFunc: func(*require.Assertions) *txs.Tx { 1102 return importTx 1103 }, 1104 expectedErr: errNotAnAsset, 1105 }, 1106 } 1107 for _, test := range tests { 1108 t.Run(test.name, func(t *testing.T) { 1109 require := require.New(t) 1110 ctrl := gomock.NewController(t) 1111 1112 state := test.stateFunc(ctrl) 1113 tx := test.txFunc(require) 1114 err := tx.Unsigned.Visit(&SemanticVerifier{ 1115 Backend: backend, 1116 State: state, 1117 Tx: tx, 1118 }) 1119 require.ErrorIs(err, test.expectedErr) 1120 }) 1121 } 1122 }