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 }