github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/txs/fee/complexity_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 fee
     5  
     6  import (
     7  	"encoding/hex"
     8  	"encoding/json"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/ava-labs/avalanchego/codec"
    14  	"github.com/ava-labs/avalanchego/ids"
    15  	"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
    16  	"github.com/ava-labs/avalanchego/vms/components/avax"
    17  	"github.com/ava-labs/avalanchego/vms/components/gas"
    18  	"github.com/ava-labs/avalanchego/vms/components/verify"
    19  	"github.com/ava-labs/avalanchego/vms/platformvm/fx"
    20  	"github.com/ava-labs/avalanchego/vms/platformvm/signer"
    21  	"github.com/ava-labs/avalanchego/vms/platformvm/stakeable"
    22  	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
    23  	"github.com/ava-labs/avalanchego/vms/secp256k1fx"
    24  )
    25  
    26  func TestTxComplexity_Individual(t *testing.T) {
    27  	for _, test := range txTests {
    28  		t.Run(test.name, func(t *testing.T) {
    29  			require := require.New(t)
    30  
    31  			txBytes, err := hex.DecodeString(test.tx)
    32  			require.NoError(err)
    33  
    34  			tx, err := txs.Parse(txs.Codec, txBytes)
    35  			require.NoError(err)
    36  
    37  			// If the test fails, logging the transaction can be helpful for
    38  			// debugging.
    39  			txJSON, err := json.MarshalIndent(tx, "", "\t")
    40  			require.NoError(err)
    41  			t.Log(string(txJSON))
    42  
    43  			actual, err := TxComplexity(tx.Unsigned)
    44  			require.Equal(test.expectedComplexity, actual)
    45  			require.ErrorIs(err, test.expectedComplexityErr)
    46  			if err != nil {
    47  				return
    48  			}
    49  
    50  			require.Len(txBytes, int(actual[gas.Bandwidth]))
    51  		})
    52  	}
    53  }
    54  
    55  func TestTxComplexity_Batch(t *testing.T) {
    56  	require := require.New(t)
    57  
    58  	var (
    59  		unsignedTxs        = make([]txs.UnsignedTx, 0, len(txTests))
    60  		expectedComplexity gas.Dimensions
    61  	)
    62  	for _, test := range txTests {
    63  		if test.expectedComplexityErr != nil {
    64  			continue
    65  		}
    66  
    67  		var err error
    68  		expectedComplexity, err = test.expectedComplexity.Add(&expectedComplexity)
    69  		require.NoError(err)
    70  
    71  		txBytes, err := hex.DecodeString(test.tx)
    72  		require.NoError(err)
    73  
    74  		tx, err := txs.Parse(txs.Codec, txBytes)
    75  		require.NoError(err)
    76  
    77  		unsignedTxs = append(unsignedTxs, tx.Unsigned)
    78  	}
    79  
    80  	complexity, err := TxComplexity(unsignedTxs...)
    81  	require.NoError(err)
    82  	require.Equal(expectedComplexity, complexity)
    83  }
    84  
    85  func BenchmarkTxComplexity_Individual(b *testing.B) {
    86  	for _, test := range txTests {
    87  		b.Run(test.name, func(b *testing.B) {
    88  			require := require.New(b)
    89  
    90  			txBytes, err := hex.DecodeString(test.tx)
    91  			require.NoError(err)
    92  
    93  			tx, err := txs.Parse(txs.Codec, txBytes)
    94  			require.NoError(err)
    95  
    96  			b.ResetTimer()
    97  			for i := 0; i < b.N; i++ {
    98  				_, _ = TxComplexity(tx.Unsigned)
    99  			}
   100  		})
   101  	}
   102  }
   103  
   104  func BenchmarkTxComplexity_Batch(b *testing.B) {
   105  	require := require.New(b)
   106  
   107  	unsignedTxs := make([]txs.UnsignedTx, 0, len(txTests))
   108  	for _, test := range txTests {
   109  		if test.expectedComplexityErr != nil {
   110  			continue
   111  		}
   112  
   113  		txBytes, err := hex.DecodeString(test.tx)
   114  		require.NoError(err)
   115  
   116  		tx, err := txs.Parse(txs.Codec, txBytes)
   117  		require.NoError(err)
   118  
   119  		unsignedTxs = append(unsignedTxs, tx.Unsigned)
   120  	}
   121  
   122  	b.ResetTimer()
   123  	for i := 0; i < b.N; i++ {
   124  		_, _ = TxComplexity(unsignedTxs...)
   125  	}
   126  }
   127  
   128  func TestOutputComplexity(t *testing.T) {
   129  	tests := []struct {
   130  		name        string
   131  		out         *avax.TransferableOutput
   132  		expected    gas.Dimensions
   133  		expectedErr error
   134  	}{
   135  		{
   136  			name: "any can spend",
   137  			out: &avax.TransferableOutput{
   138  				Out: &secp256k1fx.TransferOutput{
   139  					OutputOwners: secp256k1fx.OutputOwners{
   140  						Addrs: make([]ids.ShortID, 0),
   141  					},
   142  				},
   143  			},
   144  			expected: gas.Dimensions{
   145  				gas.Bandwidth: 60,
   146  				gas.DBRead:    0,
   147  				gas.DBWrite:   1,
   148  				gas.Compute:   0,
   149  			},
   150  			expectedErr: nil,
   151  		},
   152  		{
   153  			name: "one owner",
   154  			out: &avax.TransferableOutput{
   155  				Out: &secp256k1fx.TransferOutput{
   156  					OutputOwners: secp256k1fx.OutputOwners{
   157  						Addrs: make([]ids.ShortID, 1),
   158  					},
   159  				},
   160  			},
   161  			expected: gas.Dimensions{
   162  				gas.Bandwidth: 80,
   163  				gas.DBRead:    0,
   164  				gas.DBWrite:   1,
   165  				gas.Compute:   0,
   166  			},
   167  			expectedErr: nil,
   168  		},
   169  		{
   170  			name: "three owners",
   171  			out: &avax.TransferableOutput{
   172  				Out: &secp256k1fx.TransferOutput{
   173  					OutputOwners: secp256k1fx.OutputOwners{
   174  						Addrs: make([]ids.ShortID, 3),
   175  					},
   176  				},
   177  			},
   178  			expected: gas.Dimensions{
   179  				gas.Bandwidth: 120,
   180  				gas.DBRead:    0,
   181  				gas.DBWrite:   1,
   182  				gas.Compute:   0,
   183  			},
   184  			expectedErr: nil,
   185  		},
   186  		{
   187  			name: "locked stakeable",
   188  			out: &avax.TransferableOutput{
   189  				Out: &stakeable.LockOut{
   190  					TransferableOut: &secp256k1fx.TransferOutput{
   191  						OutputOwners: secp256k1fx.OutputOwners{
   192  							Addrs: make([]ids.ShortID, 3),
   193  						},
   194  					},
   195  				},
   196  			},
   197  			expected: gas.Dimensions{
   198  				gas.Bandwidth: 132,
   199  				gas.DBRead:    0,
   200  				gas.DBWrite:   1,
   201  				gas.Compute:   0,
   202  			},
   203  			expectedErr: nil,
   204  		},
   205  		{
   206  			name: "invalid output type",
   207  			out: &avax.TransferableOutput{
   208  				Out: nil,
   209  			},
   210  			expected: gas.Dimensions{
   211  				gas.Bandwidth: 0,
   212  				gas.DBRead:    0,
   213  				gas.DBWrite:   0,
   214  				gas.Compute:   0,
   215  			},
   216  			expectedErr: errUnsupportedOutput,
   217  		},
   218  	}
   219  	for _, test := range tests {
   220  		t.Run(test.name, func(t *testing.T) {
   221  			require := require.New(t)
   222  
   223  			actual, err := OutputComplexity(test.out)
   224  			require.ErrorIs(err, test.expectedErr)
   225  			require.Equal(test.expected, actual)
   226  
   227  			if err != nil {
   228  				return
   229  			}
   230  
   231  			bytes, err := txs.Codec.Marshal(txs.CodecVersion, test.out)
   232  			require.NoError(err)
   233  
   234  			numBytesWithoutCodecVersion := uint64(len(bytes) - codec.VersionSize)
   235  			require.Equal(numBytesWithoutCodecVersion, actual[gas.Bandwidth])
   236  		})
   237  	}
   238  }
   239  
   240  func TestInputComplexity(t *testing.T) {
   241  	tests := []struct {
   242  		name        string
   243  		in          *avax.TransferableInput
   244  		cred        verify.Verifiable
   245  		expected    gas.Dimensions
   246  		expectedErr error
   247  	}{
   248  		{
   249  			name: "any can spend",
   250  			in: &avax.TransferableInput{
   251  				In: &secp256k1fx.TransferInput{
   252  					Input: secp256k1fx.Input{
   253  						SigIndices: make([]uint32, 0),
   254  					},
   255  				},
   256  			},
   257  			cred: &secp256k1fx.Credential{
   258  				Sigs: make([][secp256k1.SignatureLen]byte, 0),
   259  			},
   260  			expected: gas.Dimensions{
   261  				gas.Bandwidth: 92,
   262  				gas.DBRead:    1,
   263  				gas.DBWrite:   1,
   264  				gas.Compute:   0, // TODO: implement
   265  			},
   266  			expectedErr: nil,
   267  		},
   268  		{
   269  			name: "one owner",
   270  			in: &avax.TransferableInput{
   271  				In: &secp256k1fx.TransferInput{
   272  					Input: secp256k1fx.Input{
   273  						SigIndices: make([]uint32, 1),
   274  					},
   275  				},
   276  			},
   277  			cred: &secp256k1fx.Credential{
   278  				Sigs: make([][secp256k1.SignatureLen]byte, 1),
   279  			},
   280  			expected: gas.Dimensions{
   281  				gas.Bandwidth: 161,
   282  				gas.DBRead:    1,
   283  				gas.DBWrite:   1,
   284  				gas.Compute:   0, // TODO: implement
   285  			},
   286  			expectedErr: nil,
   287  		},
   288  		{
   289  			name: "three owners",
   290  			in: &avax.TransferableInput{
   291  				In: &secp256k1fx.TransferInput{
   292  					Input: secp256k1fx.Input{
   293  						SigIndices: make([]uint32, 3),
   294  					},
   295  				},
   296  			},
   297  			cred: &secp256k1fx.Credential{
   298  				Sigs: make([][secp256k1.SignatureLen]byte, 3),
   299  			},
   300  			expected: gas.Dimensions{
   301  				gas.Bandwidth: 299,
   302  				gas.DBRead:    1,
   303  				gas.DBWrite:   1,
   304  				gas.Compute:   0, // TODO: implement
   305  			},
   306  			expectedErr: nil,
   307  		},
   308  		{
   309  			name: "locked stakeable",
   310  			in: &avax.TransferableInput{
   311  				In: &stakeable.LockIn{
   312  					TransferableIn: &secp256k1fx.TransferInput{
   313  						Input: secp256k1fx.Input{
   314  							SigIndices: make([]uint32, 3),
   315  						},
   316  					},
   317  				},
   318  			},
   319  			cred: &secp256k1fx.Credential{
   320  				Sigs: make([][secp256k1.SignatureLen]byte, 3),
   321  			},
   322  			expected: gas.Dimensions{
   323  				gas.Bandwidth: 311,
   324  				gas.DBRead:    1,
   325  				gas.DBWrite:   1,
   326  				gas.Compute:   0, // TODO: implement
   327  			},
   328  			expectedErr: nil,
   329  		},
   330  		{
   331  			name: "invalid input type",
   332  			in: &avax.TransferableInput{
   333  				In: nil,
   334  			},
   335  			cred: nil,
   336  			expected: gas.Dimensions{
   337  				gas.Bandwidth: 0,
   338  				gas.DBRead:    0,
   339  				gas.DBWrite:   0,
   340  				gas.Compute:   0,
   341  			},
   342  			expectedErr: errUnsupportedInput,
   343  		},
   344  	}
   345  	for _, test := range tests {
   346  		t.Run(test.name, func(t *testing.T) {
   347  			require := require.New(t)
   348  
   349  			actual, err := InputComplexity(test.in)
   350  			require.ErrorIs(err, test.expectedErr)
   351  			require.Equal(test.expected, actual)
   352  
   353  			if err != nil {
   354  				return
   355  			}
   356  
   357  			inputBytes, err := txs.Codec.Marshal(txs.CodecVersion, test.in)
   358  			require.NoError(err)
   359  
   360  			cred := test.cred
   361  			credentialBytes, err := txs.Codec.Marshal(txs.CodecVersion, &cred)
   362  			require.NoError(err)
   363  
   364  			numBytesWithoutCodecVersion := uint64(len(inputBytes) + len(credentialBytes) - 2*codec.VersionSize)
   365  			require.Equal(numBytesWithoutCodecVersion, actual[gas.Bandwidth])
   366  		})
   367  	}
   368  }
   369  
   370  func TestOwnerComplexity(t *testing.T) {
   371  	tests := []struct {
   372  		name        string
   373  		owner       fx.Owner
   374  		expected    gas.Dimensions
   375  		expectedErr error
   376  	}{
   377  		{
   378  			name: "any can spend",
   379  			owner: &secp256k1fx.OutputOwners{
   380  				Addrs: make([]ids.ShortID, 0),
   381  			},
   382  			expected: gas.Dimensions{
   383  				gas.Bandwidth: 16,
   384  				gas.DBRead:    0,
   385  				gas.DBWrite:   0,
   386  				gas.Compute:   0,
   387  			},
   388  			expectedErr: nil,
   389  		},
   390  		{
   391  			name: "one owner",
   392  			owner: &secp256k1fx.OutputOwners{
   393  				Addrs: make([]ids.ShortID, 1),
   394  			},
   395  			expected: gas.Dimensions{
   396  				gas.Bandwidth: 36,
   397  				gas.DBRead:    0,
   398  				gas.DBWrite:   0,
   399  				gas.Compute:   0,
   400  			},
   401  			expectedErr: nil,
   402  		},
   403  		{
   404  			name: "three owners",
   405  			owner: &secp256k1fx.OutputOwners{
   406  				Addrs: make([]ids.ShortID, 3),
   407  			},
   408  			expected: gas.Dimensions{
   409  				gas.Bandwidth: 76,
   410  				gas.DBRead:    0,
   411  				gas.DBWrite:   0,
   412  				gas.Compute:   0,
   413  			},
   414  			expectedErr: nil,
   415  		},
   416  		{
   417  			name:        "invalid owner type",
   418  			owner:       nil,
   419  			expected:    gas.Dimensions{},
   420  			expectedErr: errUnsupportedOwner,
   421  		},
   422  	}
   423  	for _, test := range tests {
   424  		t.Run(test.name, func(t *testing.T) {
   425  			require := require.New(t)
   426  
   427  			actual, err := OwnerComplexity(test.owner)
   428  			require.ErrorIs(err, test.expectedErr)
   429  			require.Equal(test.expected, actual)
   430  
   431  			if err != nil {
   432  				return
   433  			}
   434  
   435  			ownerBytes, err := txs.Codec.Marshal(txs.CodecVersion, test.owner)
   436  			require.NoError(err)
   437  
   438  			numBytesWithoutCodecVersion := uint64(len(ownerBytes) - codec.VersionSize)
   439  			require.Equal(numBytesWithoutCodecVersion, actual[gas.Bandwidth])
   440  		})
   441  	}
   442  }
   443  
   444  func TestAuthComplexity(t *testing.T) {
   445  	tests := []struct {
   446  		name        string
   447  		auth        verify.Verifiable
   448  		cred        verify.Verifiable
   449  		expected    gas.Dimensions
   450  		expectedErr error
   451  	}{
   452  		{
   453  			name: "any can spend",
   454  			auth: &secp256k1fx.Input{
   455  				SigIndices: make([]uint32, 0),
   456  			},
   457  			cred: &secp256k1fx.Credential{
   458  				Sigs: make([][secp256k1.SignatureLen]byte, 0),
   459  			},
   460  			expected: gas.Dimensions{
   461  				gas.Bandwidth: 8,
   462  				gas.DBRead:    0,
   463  				gas.DBWrite:   0,
   464  				gas.Compute:   0, // TODO: implement
   465  			},
   466  			expectedErr: nil,
   467  		},
   468  		{
   469  			name: "one owner",
   470  			auth: &secp256k1fx.Input{
   471  				SigIndices: make([]uint32, 1),
   472  			},
   473  			cred: &secp256k1fx.Credential{
   474  				Sigs: make([][secp256k1.SignatureLen]byte, 1),
   475  			},
   476  			expected: gas.Dimensions{
   477  				gas.Bandwidth: 77,
   478  				gas.DBRead:    0,
   479  				gas.DBWrite:   0,
   480  				gas.Compute:   0, // TODO: implement
   481  			},
   482  			expectedErr: nil,
   483  		},
   484  		{
   485  			name: "three owners",
   486  			auth: &secp256k1fx.Input{
   487  				SigIndices: make([]uint32, 3),
   488  			},
   489  			cred: &secp256k1fx.Credential{
   490  				Sigs: make([][secp256k1.SignatureLen]byte, 3),
   491  			},
   492  			expected: gas.Dimensions{
   493  				gas.Bandwidth: 215,
   494  				gas.DBRead:    0,
   495  				gas.DBWrite:   0,
   496  				gas.Compute:   0, // TODO: implement
   497  			},
   498  			expectedErr: nil,
   499  		},
   500  		{
   501  			name: "invalid auth type",
   502  			auth: nil,
   503  			cred: nil,
   504  			expected: gas.Dimensions{
   505  				gas.Bandwidth: 0,
   506  				gas.DBRead:    0,
   507  				gas.DBWrite:   0,
   508  				gas.Compute:   0, // TODO: implement
   509  			},
   510  			expectedErr: errUnsupportedAuth,
   511  		},
   512  	}
   513  	for _, test := range tests {
   514  		t.Run(test.name, func(t *testing.T) {
   515  			require := require.New(t)
   516  
   517  			actual, err := AuthComplexity(test.auth)
   518  			require.ErrorIs(err, test.expectedErr)
   519  			require.Equal(test.expected, actual)
   520  
   521  			if err != nil {
   522  				return
   523  			}
   524  
   525  			authBytes, err := txs.Codec.Marshal(txs.CodecVersion, test.auth)
   526  			require.NoError(err)
   527  
   528  			credentialBytes, err := txs.Codec.Marshal(txs.CodecVersion, test.cred)
   529  			require.NoError(err)
   530  
   531  			numBytesWithoutCodecVersion := uint64(len(authBytes) + len(credentialBytes) - 2*codec.VersionSize)
   532  			require.Equal(numBytesWithoutCodecVersion, actual[gas.Bandwidth])
   533  		})
   534  	}
   535  }
   536  
   537  func TestSignerComplexity(t *testing.T) {
   538  	tests := []struct {
   539  		name        string
   540  		signer      signer.Signer
   541  		expected    gas.Dimensions
   542  		expectedErr error
   543  	}{
   544  		{
   545  			name:   "empty",
   546  			signer: &signer.Empty{},
   547  			expected: gas.Dimensions{
   548  				gas.Bandwidth: 0,
   549  				gas.DBRead:    0,
   550  				gas.DBWrite:   0,
   551  				gas.Compute:   0,
   552  			},
   553  			expectedErr: nil,
   554  		},
   555  		{
   556  			name:   "bls pop",
   557  			signer: &signer.ProofOfPossession{},
   558  			expected: gas.Dimensions{
   559  				gas.Bandwidth: 144,
   560  				gas.DBRead:    0,
   561  				gas.DBWrite:   0,
   562  				gas.Compute:   0, // TODO: implement
   563  			},
   564  			expectedErr: nil,
   565  		},
   566  		{
   567  			name:   "invalid signer type",
   568  			signer: nil,
   569  			expected: gas.Dimensions{
   570  				gas.Bandwidth: 0,
   571  				gas.DBRead:    0,
   572  				gas.DBWrite:   0,
   573  				gas.Compute:   0,
   574  			},
   575  			expectedErr: errUnsupportedSigner,
   576  		},
   577  	}
   578  	for _, test := range tests {
   579  		t.Run(test.name, func(t *testing.T) {
   580  			require := require.New(t)
   581  
   582  			actual, err := SignerComplexity(test.signer)
   583  			require.ErrorIs(err, test.expectedErr)
   584  			require.Equal(test.expected, actual)
   585  
   586  			if err != nil {
   587  				return
   588  			}
   589  
   590  			signerBytes, err := txs.Codec.Marshal(txs.CodecVersion, test.signer)
   591  			require.NoError(err)
   592  
   593  			numBytesWithoutCodecVersion := uint64(len(signerBytes) - codec.VersionSize)
   594  			require.Equal(numBytesWithoutCodecVersion, actual[gas.Bandwidth])
   595  		})
   596  	}
   597  }