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