github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/txs/remove_subnet_validator_tx_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 txs
     5  
     6  import (
     7  	"encoding/json"
     8  	"errors"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/require"
    12  	"go.uber.org/mock/gomock"
    13  
    14  	"github.com/MetalBlockchain/metalgo/ids"
    15  	"github.com/MetalBlockchain/metalgo/snow"
    16  	"github.com/MetalBlockchain/metalgo/utils"
    17  	"github.com/MetalBlockchain/metalgo/utils/constants"
    18  	"github.com/MetalBlockchain/metalgo/utils/units"
    19  	"github.com/MetalBlockchain/metalgo/vms/components/avax"
    20  	"github.com/MetalBlockchain/metalgo/vms/components/verify"
    21  	"github.com/MetalBlockchain/metalgo/vms/platformvm/stakeable"
    22  	"github.com/MetalBlockchain/metalgo/vms/secp256k1fx"
    23  	"github.com/MetalBlockchain/metalgo/vms/types"
    24  )
    25  
    26  var errInvalidSubnetAuth = errors.New("invalid subnet auth")
    27  
    28  func TestRemoveSubnetValidatorTxSerialization(t *testing.T) {
    29  	require := require.New(t)
    30  
    31  	addr := ids.ShortID{
    32  		0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb,
    33  		0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb,
    34  		0x44, 0x55, 0x66, 0x77,
    35  	}
    36  
    37  	avaxAssetID, err := ids.FromString("FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z")
    38  	require.NoError(err)
    39  
    40  	customAssetID := ids.ID{
    41  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
    42  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
    43  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
    44  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
    45  	}
    46  
    47  	txID := ids.ID{
    48  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
    49  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
    50  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
    51  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
    52  	}
    53  	nodeID := ids.BuildTestNodeID([]byte{
    54  		0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
    55  		0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
    56  		0x11, 0x22, 0x33, 0x44,
    57  	})
    58  	subnetID := ids.ID{
    59  		0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
    60  		0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
    61  		0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
    62  		0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
    63  	}
    64  
    65  	simpleRemoveValidatorTx := &RemoveSubnetValidatorTx{
    66  		BaseTx: BaseTx{
    67  			BaseTx: avax.BaseTx{
    68  				NetworkID:    constants.MainnetID,
    69  				BlockchainID: constants.PlatformChainID,
    70  				Outs:         []*avax.TransferableOutput{},
    71  				Ins: []*avax.TransferableInput{
    72  					{
    73  						UTXOID: avax.UTXOID{
    74  							TxID:        txID,
    75  							OutputIndex: 1,
    76  						},
    77  						Asset: avax.Asset{
    78  							ID: avaxAssetID,
    79  						},
    80  						In: &secp256k1fx.TransferInput{
    81  							Amt: units.MilliAvax,
    82  							Input: secp256k1fx.Input{
    83  								SigIndices: []uint32{5},
    84  							},
    85  						},
    86  					},
    87  				},
    88  				Memo: types.JSONByteSlice{},
    89  			},
    90  		},
    91  		NodeID: nodeID,
    92  		Subnet: subnetID,
    93  		SubnetAuth: &secp256k1fx.Input{
    94  			SigIndices: []uint32{3},
    95  		},
    96  	}
    97  	require.NoError(simpleRemoveValidatorTx.SyntacticVerify(&snow.Context{
    98  		NetworkID:   1,
    99  		ChainID:     constants.PlatformChainID,
   100  		AVAXAssetID: avaxAssetID,
   101  	}))
   102  
   103  	expectedUnsignedSimpleRemoveValidatorTxBytes := []byte{
   104  		// Codec version
   105  		0x00, 0x00,
   106  		// RemoveSubnetValidatorTx Type ID
   107  		0x00, 0x00, 0x00, 0x17,
   108  		// Mainnet network ID
   109  		0x00, 0x00, 0x00, 0x01,
   110  		// P-chain blockchain ID
   111  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   112  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   113  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   114  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   115  		// Number of outputs
   116  		0x00, 0x00, 0x00, 0x00,
   117  		// Number of inputs
   118  		0x00, 0x00, 0x00, 0x01,
   119  		// Inputs[0]
   120  		// TxID
   121  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   122  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   123  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   124  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   125  		// Tx output index
   126  		0x00, 0x00, 0x00, 0x01,
   127  		// Mainnet AVAX assetID
   128  		0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a,
   129  		0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78,
   130  		0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf,
   131  		0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff,
   132  		// secp256k1fx transfer input type ID
   133  		0x00, 0x00, 0x00, 0x05,
   134  		// input amount = 1 MilliAvax
   135  		0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x42, 0x40,
   136  		// number of signatures needed in input
   137  		0x00, 0x00, 0x00, 0x01,
   138  		// index of signer
   139  		0x00, 0x00, 0x00, 0x05,
   140  		// length of memo field
   141  		0x00, 0x00, 0x00, 0x00,
   142  		// nodeID to remove
   143  		0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
   144  		0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
   145  		0x11, 0x22, 0x33, 0x44,
   146  		// subnetID to remove from
   147  		0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
   148  		0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
   149  		0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
   150  		0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
   151  		// secp256k1fx authorization type ID
   152  		0x00, 0x00, 0x00, 0x0a,
   153  		// number of signatures needed in authorization
   154  		0x00, 0x00, 0x00, 0x01,
   155  		// index of signer
   156  		0x00, 0x00, 0x00, 0x03,
   157  	}
   158  	var unsignedSimpleRemoveValidatorTx UnsignedTx = simpleRemoveValidatorTx
   159  	unsignedSimpleRemoveValidatorTxBytes, err := Codec.Marshal(CodecVersion, &unsignedSimpleRemoveValidatorTx)
   160  	require.NoError(err)
   161  	require.Equal(expectedUnsignedSimpleRemoveValidatorTxBytes, unsignedSimpleRemoveValidatorTxBytes)
   162  
   163  	complexRemoveValidatorTx := &RemoveSubnetValidatorTx{
   164  		BaseTx: BaseTx{
   165  			BaseTx: avax.BaseTx{
   166  				NetworkID:    constants.MainnetID,
   167  				BlockchainID: constants.PlatformChainID,
   168  				Outs: []*avax.TransferableOutput{
   169  					{
   170  						Asset: avax.Asset{
   171  							ID: avaxAssetID,
   172  						},
   173  						Out: &stakeable.LockOut{
   174  							Locktime: 87654321,
   175  							TransferableOut: &secp256k1fx.TransferOutput{
   176  								Amt: 1,
   177  								OutputOwners: secp256k1fx.OutputOwners{
   178  									Locktime:  12345678,
   179  									Threshold: 0,
   180  									Addrs:     []ids.ShortID{},
   181  								},
   182  							},
   183  						},
   184  					},
   185  					{
   186  						Asset: avax.Asset{
   187  							ID: customAssetID,
   188  						},
   189  						Out: &stakeable.LockOut{
   190  							Locktime: 876543210,
   191  							TransferableOut: &secp256k1fx.TransferOutput{
   192  								Amt: 0xffffffffffffffff,
   193  								OutputOwners: secp256k1fx.OutputOwners{
   194  									Locktime:  0,
   195  									Threshold: 1,
   196  									Addrs: []ids.ShortID{
   197  										addr,
   198  									},
   199  								},
   200  							},
   201  						},
   202  					},
   203  				},
   204  				Ins: []*avax.TransferableInput{
   205  					{
   206  						UTXOID: avax.UTXOID{
   207  							TxID:        txID,
   208  							OutputIndex: 1,
   209  						},
   210  						Asset: avax.Asset{
   211  							ID: avaxAssetID,
   212  						},
   213  						In: &secp256k1fx.TransferInput{
   214  							Amt: units.Avax,
   215  							Input: secp256k1fx.Input{
   216  								SigIndices: []uint32{2, 5},
   217  							},
   218  						},
   219  					},
   220  					{
   221  						UTXOID: avax.UTXOID{
   222  							TxID:        txID,
   223  							OutputIndex: 2,
   224  						},
   225  						Asset: avax.Asset{
   226  							ID: customAssetID,
   227  						},
   228  						In: &stakeable.LockIn{
   229  							Locktime: 876543210,
   230  							TransferableIn: &secp256k1fx.TransferInput{
   231  								Amt: 0xefffffffffffffff,
   232  								Input: secp256k1fx.Input{
   233  									SigIndices: []uint32{0},
   234  								},
   235  							},
   236  						},
   237  					},
   238  					{
   239  						UTXOID: avax.UTXOID{
   240  							TxID:        txID,
   241  							OutputIndex: 3,
   242  						},
   243  						Asset: avax.Asset{
   244  							ID: customAssetID,
   245  						},
   246  						In: &secp256k1fx.TransferInput{
   247  							Amt: 0x1000000000000000,
   248  							Input: secp256k1fx.Input{
   249  								SigIndices: []uint32{},
   250  							},
   251  						},
   252  					},
   253  				},
   254  				Memo: types.JSONByteSlice("😅\nwell that's\x01\x23\x45!"),
   255  			},
   256  		},
   257  		NodeID: nodeID,
   258  		Subnet: subnetID,
   259  		SubnetAuth: &secp256k1fx.Input{
   260  			SigIndices: []uint32{},
   261  		},
   262  	}
   263  	avax.SortTransferableOutputs(complexRemoveValidatorTx.Outs, Codec)
   264  	utils.Sort(complexRemoveValidatorTx.Ins)
   265  	require.NoError(complexRemoveValidatorTx.SyntacticVerify(&snow.Context{
   266  		NetworkID:   1,
   267  		ChainID:     constants.PlatformChainID,
   268  		AVAXAssetID: avaxAssetID,
   269  	}))
   270  
   271  	expectedUnsignedComplexRemoveValidatorTxBytes := []byte{
   272  		// Codec version
   273  		0x00, 0x00,
   274  		// RemoveSubnetValidatorTx Type ID
   275  		0x00, 0x00, 0x00, 0x17,
   276  		// Mainnet network ID
   277  		0x00, 0x00, 0x00, 0x01,
   278  		// P-chain blockchain ID
   279  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   280  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   281  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   282  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   283  		// Number of outputs
   284  		0x00, 0x00, 0x00, 0x02,
   285  		// Outputs[0]
   286  		// Mainnet AVAX assetID
   287  		0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a,
   288  		0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78,
   289  		0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf,
   290  		0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff,
   291  		// Stakeable locked output type ID
   292  		0x00, 0x00, 0x00, 0x16,
   293  		// Locktime
   294  		0x00, 0x00, 0x00, 0x00, 0x05, 0x39, 0x7f, 0xb1,
   295  		// secp256k1fx transfer output type ID
   296  		0x00, 0x00, 0x00, 0x07,
   297  		// amount
   298  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
   299  		// secp256k1fx output locktime
   300  		0x00, 0x00, 0x00, 0x00, 0x00, 0xbc, 0x61, 0x4e,
   301  		// threshold
   302  		0x00, 0x00, 0x00, 0x00,
   303  		// number of addresses
   304  		0x00, 0x00, 0x00, 0x00,
   305  		// Outputs[1]
   306  		// custom asset ID
   307  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   308  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   309  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   310  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   311  		// Stakeable locked output type ID
   312  		0x00, 0x00, 0x00, 0x16,
   313  		// Locktime
   314  		0x00, 0x00, 0x00, 0x00, 0x34, 0x3e, 0xfc, 0xea,
   315  		// secp256k1fx transfer output type ID
   316  		0x00, 0x00, 0x00, 0x07,
   317  		// amount
   318  		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
   319  		// secp256k1fx output locktime
   320  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   321  		// threshold
   322  		0x00, 0x00, 0x00, 0x01,
   323  		// number of addresses
   324  		0x00, 0x00, 0x00, 0x01,
   325  		// address[0]
   326  		0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb,
   327  		0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb,
   328  		0x44, 0x55, 0x66, 0x77,
   329  		// number of inputs
   330  		0x00, 0x00, 0x00, 0x03,
   331  		// inputs[0]
   332  		// TxID
   333  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   334  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   335  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   336  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   337  		// Tx output index
   338  		0x00, 0x00, 0x00, 0x01,
   339  		// Mainnet AVAX assetID
   340  		0x21, 0xe6, 0x73, 0x17, 0xcb, 0xc4, 0xbe, 0x2a,
   341  		0xeb, 0x00, 0x67, 0x7a, 0xd6, 0x46, 0x27, 0x78,
   342  		0xa8, 0xf5, 0x22, 0x74, 0xb9, 0xd6, 0x05, 0xdf,
   343  		0x25, 0x91, 0xb2, 0x30, 0x27, 0xa8, 0x7d, 0xff,
   344  		// secp256k1fx transfer input type ID
   345  		0x00, 0x00, 0x00, 0x05,
   346  		// input amount = 1 Avax
   347  		0x00, 0x00, 0x00, 0x00, 0x3b, 0x9a, 0xca, 0x00,
   348  		// number of signatures needed in input
   349  		0x00, 0x00, 0x00, 0x02,
   350  		// index of first signer
   351  		0x00, 0x00, 0x00, 0x02,
   352  		// index of second signer
   353  		0x00, 0x00, 0x00, 0x05,
   354  		// inputs[1]
   355  		// TxID
   356  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   357  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   358  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   359  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   360  		// Tx output index
   361  		0x00, 0x00, 0x00, 0x02,
   362  		// Custom asset ID
   363  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   364  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   365  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   366  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   367  		// Stakeable locked input type ID
   368  		0x00, 0x00, 0x00, 0x15,
   369  		// Locktime
   370  		0x00, 0x00, 0x00, 0x00, 0x34, 0x3e, 0xfc, 0xea,
   371  		// secp256k1fx transfer input type ID
   372  		0x00, 0x00, 0x00, 0x05,
   373  		// input amount
   374  		0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
   375  		// number of signatures needed in input
   376  		0x00, 0x00, 0x00, 0x01,
   377  		// index of signer
   378  		0x00, 0x00, 0x00, 0x00,
   379  		// inputs[2]
   380  		// TxID
   381  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   382  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   383  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   384  		0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
   385  		// Tx output index
   386  		0x00, 0x00, 0x00, 0x03,
   387  		// custom asset ID
   388  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   389  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   390  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   391  		0x99, 0x77, 0x55, 0x77, 0x11, 0x33, 0x55, 0x31,
   392  		// secp256k1fx transfer input type ID
   393  		0x00, 0x00, 0x00, 0x05,
   394  		// input amount
   395  		0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   396  		// number of signatures needed in input
   397  		0x00, 0x00, 0x00, 0x00,
   398  		// length of memo
   399  		0x00, 0x00, 0x00, 0x14,
   400  		// memo
   401  		0xf0, 0x9f, 0x98, 0x85, 0x0a, 0x77, 0x65, 0x6c,
   402  		0x6c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x27, 0x73,
   403  		0x01, 0x23, 0x45, 0x21,
   404  		// nodeID to remove
   405  		0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
   406  		0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
   407  		0x11, 0x22, 0x33, 0x44,
   408  		// subnetID to remove from
   409  		0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
   410  		0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
   411  		0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
   412  		0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
   413  		// secp256k1fx authorization type ID
   414  		0x00, 0x00, 0x00, 0x0a,
   415  		// number of signatures needed in authorization
   416  		0x00, 0x00, 0x00, 0x00,
   417  	}
   418  	var unsignedComplexRemoveValidatorTx UnsignedTx = complexRemoveValidatorTx
   419  	unsignedComplexRemoveValidatorTxBytes, err := Codec.Marshal(CodecVersion, &unsignedComplexRemoveValidatorTx)
   420  	require.NoError(err)
   421  	require.Equal(expectedUnsignedComplexRemoveValidatorTxBytes, unsignedComplexRemoveValidatorTxBytes)
   422  
   423  	aliaser := ids.NewAliaser()
   424  	require.NoError(aliaser.Alias(constants.PlatformChainID, "P"))
   425  
   426  	unsignedComplexRemoveValidatorTx.InitCtx(&snow.Context{
   427  		NetworkID:   1,
   428  		ChainID:     constants.PlatformChainID,
   429  		AVAXAssetID: avaxAssetID,
   430  		BCLookup:    aliaser,
   431  	})
   432  
   433  	unsignedComplexRemoveValidatorTxJSONBytes, err := json.MarshalIndent(unsignedComplexRemoveValidatorTx, "", "\t")
   434  	require.NoError(err)
   435  	require.Equal(`{
   436  	"networkID": 1,
   437  	"blockchainID": "11111111111111111111111111111111LpoYY",
   438  	"outputs": [
   439  		{
   440  			"assetID": "FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z",
   441  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   442  			"output": {
   443  				"locktime": 87654321,
   444  				"output": {
   445  					"addresses": [],
   446  					"amount": 1,
   447  					"locktime": 12345678,
   448  					"threshold": 0
   449  				}
   450  			}
   451  		},
   452  		{
   453  			"assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc",
   454  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   455  			"output": {
   456  				"locktime": 876543210,
   457  				"output": {
   458  					"addresses": [
   459  						"P-metal1g32kvaugnx4tk3z4vemc3xd2hdz92enhqaj6ex"
   460  					],
   461  					"amount": 18446744073709551615,
   462  					"locktime": 0,
   463  					"threshold": 1
   464  				}
   465  			}
   466  		}
   467  	],
   468  	"inputs": [
   469  		{
   470  			"txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN",
   471  			"outputIndex": 1,
   472  			"assetID": "FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z",
   473  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   474  			"input": {
   475  				"amount": 1000000000,
   476  				"signatureIndices": [
   477  					2,
   478  					5
   479  				]
   480  			}
   481  		},
   482  		{
   483  			"txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN",
   484  			"outputIndex": 2,
   485  			"assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc",
   486  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   487  			"input": {
   488  				"locktime": 876543210,
   489  				"input": {
   490  					"amount": 17293822569102704639,
   491  					"signatureIndices": [
   492  						0
   493  					]
   494  				}
   495  			}
   496  		},
   497  		{
   498  			"txID": "2wiU5PnFTjTmoAXGZutHAsPF36qGGyLHYHj9G1Aucfmb3JFFGN",
   499  			"outputIndex": 3,
   500  			"assetID": "2Ab62uWwJw1T6VvmKD36ufsiuGZuX1pGykXAvPX1LtjTRHxwcc",
   501  			"fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ",
   502  			"input": {
   503  				"amount": 1152921504606846976,
   504  				"signatureIndices": []
   505  			}
   506  		}
   507  	],
   508  	"memo": "0xf09f98850a77656c6c2074686174277301234521",
   509  	"nodeID": "NodeID-2ZbTY9GatRTrfinAoYiYLcf6CvrPAUYgo",
   510  	"subnetID": "SkB92YpWm4UpburLz9tEKZw2i67H3FF6YkjaU4BkFUDTG9Xm",
   511  	"subnetAuthorization": {
   512  		"signatureIndices": []
   513  	}
   514  }`, string(unsignedComplexRemoveValidatorTxJSONBytes))
   515  }
   516  
   517  func TestRemoveSubnetValidatorTxSyntacticVerify(t *testing.T) {
   518  	type test struct {
   519  		name        string
   520  		txFunc      func(*gomock.Controller) *RemoveSubnetValidatorTx
   521  		expectedErr error
   522  	}
   523  
   524  	var (
   525  		networkID = uint32(1337)
   526  		chainID   = ids.GenerateTestID()
   527  	)
   528  
   529  	ctx := &snow.Context{
   530  		ChainID:   chainID,
   531  		NetworkID: networkID,
   532  	}
   533  
   534  	// A BaseTx that already passed syntactic verification.
   535  	verifiedBaseTx := BaseTx{
   536  		SyntacticallyVerified: true,
   537  	}
   538  	// Sanity check.
   539  	require.NoError(t, verifiedBaseTx.SyntacticVerify(ctx))
   540  
   541  	// A BaseTx that passes syntactic verification.
   542  	validBaseTx := BaseTx{
   543  		BaseTx: avax.BaseTx{
   544  			NetworkID:    networkID,
   545  			BlockchainID: chainID,
   546  		},
   547  	}
   548  	// Sanity check.
   549  	require.NoError(t, validBaseTx.SyntacticVerify(ctx))
   550  	// Make sure we're not caching the verification result.
   551  	require.False(t, validBaseTx.SyntacticallyVerified)
   552  
   553  	// A BaseTx that fails syntactic verification.
   554  	invalidBaseTx := BaseTx{}
   555  
   556  	tests := []test{
   557  		{
   558  			name: "nil tx",
   559  			txFunc: func(*gomock.Controller) *RemoveSubnetValidatorTx {
   560  				return nil
   561  			},
   562  			expectedErr: ErrNilTx,
   563  		},
   564  		{
   565  			name: "already verified",
   566  			txFunc: func(*gomock.Controller) *RemoveSubnetValidatorTx {
   567  				return &RemoveSubnetValidatorTx{BaseTx: verifiedBaseTx}
   568  			},
   569  			expectedErr: nil,
   570  		},
   571  		{
   572  			name: "invalid BaseTx",
   573  			txFunc: func(*gomock.Controller) *RemoveSubnetValidatorTx {
   574  				return &RemoveSubnetValidatorTx{
   575  					// Set subnetID so we don't error on that check.
   576  					Subnet: ids.GenerateTestID(),
   577  					// Set NodeID so we don't error on that check.
   578  					NodeID: ids.GenerateTestNodeID(),
   579  					BaseTx: invalidBaseTx,
   580  				}
   581  			},
   582  			expectedErr: avax.ErrWrongNetworkID,
   583  		},
   584  		{
   585  			name: "invalid subnetID",
   586  			txFunc: func(*gomock.Controller) *RemoveSubnetValidatorTx {
   587  				return &RemoveSubnetValidatorTx{
   588  					BaseTx: validBaseTx,
   589  					// Set NodeID so we don't error on that check.
   590  					NodeID: ids.GenerateTestNodeID(),
   591  					Subnet: constants.PrimaryNetworkID,
   592  				}
   593  			},
   594  			expectedErr: ErrRemovePrimaryNetworkValidator,
   595  		},
   596  		{
   597  			name: "invalid subnetAuth",
   598  			txFunc: func(ctrl *gomock.Controller) *RemoveSubnetValidatorTx {
   599  				// This SubnetAuth fails verification.
   600  				invalidSubnetAuth := verify.NewMockVerifiable(ctrl)
   601  				invalidSubnetAuth.EXPECT().Verify().Return(errInvalidSubnetAuth)
   602  				return &RemoveSubnetValidatorTx{
   603  					// Set subnetID so we don't error on that check.
   604  					Subnet: ids.GenerateTestID(),
   605  					// Set NodeID so we don't error on that check.
   606  					NodeID:     ids.GenerateTestNodeID(),
   607  					BaseTx:     validBaseTx,
   608  					SubnetAuth: invalidSubnetAuth,
   609  				}
   610  			},
   611  			expectedErr: errInvalidSubnetAuth,
   612  		},
   613  		{
   614  			name: "passes verification",
   615  			txFunc: func(ctrl *gomock.Controller) *RemoveSubnetValidatorTx {
   616  				// This SubnetAuth passes verification.
   617  				validSubnetAuth := verify.NewMockVerifiable(ctrl)
   618  				validSubnetAuth.EXPECT().Verify().Return(nil)
   619  				return &RemoveSubnetValidatorTx{
   620  					// Set subnetID so we don't error on that check.
   621  					Subnet: ids.GenerateTestID(),
   622  					// Set NodeID so we don't error on that check.
   623  					NodeID:     ids.GenerateTestNodeID(),
   624  					BaseTx:     validBaseTx,
   625  					SubnetAuth: validSubnetAuth,
   626  				}
   627  			},
   628  			expectedErr: nil,
   629  		},
   630  	}
   631  
   632  	for _, tt := range tests {
   633  		t.Run(tt.name, func(t *testing.T) {
   634  			require := require.New(t)
   635  			ctrl := gomock.NewController(t)
   636  
   637  			tx := tt.txFunc(ctrl)
   638  			err := tx.SyntacticVerify(ctx)
   639  			require.ErrorIs(err, tt.expectedErr)
   640  			if tt.expectedErr != nil {
   641  				return
   642  			}
   643  			require.True(tx.SyntacticallyVerified)
   644  		})
   645  	}
   646  }