github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/validator_set_property_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 platformvm 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "reflect" 11 "sort" 12 "testing" 13 "time" 14 15 "github.com/leanovate/gopter" 16 "github.com/leanovate/gopter/gen" 17 "github.com/leanovate/gopter/prop" 18 "golang.org/x/exp/maps" 19 20 "github.com/MetalBlockchain/metalgo/chains" 21 "github.com/MetalBlockchain/metalgo/chains/atomic" 22 "github.com/MetalBlockchain/metalgo/database/memdb" 23 "github.com/MetalBlockchain/metalgo/database/prefixdb" 24 "github.com/MetalBlockchain/metalgo/ids" 25 "github.com/MetalBlockchain/metalgo/snow" 26 "github.com/MetalBlockchain/metalgo/snow/consensus/snowman" 27 "github.com/MetalBlockchain/metalgo/snow/engine/common" 28 "github.com/MetalBlockchain/metalgo/snow/snowtest" 29 "github.com/MetalBlockchain/metalgo/snow/uptime" 30 "github.com/MetalBlockchain/metalgo/snow/validators" 31 "github.com/MetalBlockchain/metalgo/utils/constants" 32 "github.com/MetalBlockchain/metalgo/utils/crypto/bls" 33 "github.com/MetalBlockchain/metalgo/utils/formatting" 34 "github.com/MetalBlockchain/metalgo/utils/formatting/address" 35 "github.com/MetalBlockchain/metalgo/utils/json" 36 "github.com/MetalBlockchain/metalgo/utils/timer/mockable" 37 "github.com/MetalBlockchain/metalgo/utils/units" 38 "github.com/MetalBlockchain/metalgo/vms/platformvm/api" 39 "github.com/MetalBlockchain/metalgo/vms/platformvm/block" 40 "github.com/MetalBlockchain/metalgo/vms/platformvm/config" 41 "github.com/MetalBlockchain/metalgo/vms/platformvm/reward" 42 "github.com/MetalBlockchain/metalgo/vms/platformvm/signer" 43 "github.com/MetalBlockchain/metalgo/vms/platformvm/state" 44 "github.com/MetalBlockchain/metalgo/vms/platformvm/txs" 45 "github.com/MetalBlockchain/metalgo/vms/platformvm/txs/fee" 46 "github.com/MetalBlockchain/metalgo/vms/platformvm/txs/txstest" 47 "github.com/MetalBlockchain/metalgo/vms/platformvm/upgrade" 48 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 49 50 blockexecutor "github.com/MetalBlockchain/metalgo/vms/platformvm/block/executor" 51 txexecutor "github.com/MetalBlockchain/metalgo/vms/platformvm/txs/executor" 52 walletsigner "github.com/MetalBlockchain/metalgo/wallet/chain/p/signer" 53 walletcommon "github.com/MetalBlockchain/metalgo/wallet/subnet/primary/common" 54 ) 55 56 const ( 57 startPrimaryWithBLS uint8 = iota 58 startSubnetValidator 59 60 failedValidatorSnapshotString = "could not take validators snapshot: " 61 failedBuildingEventSeqString = "failed building events sequence: " 62 ) 63 64 var errEmptyEventsList = errors.New("empty events list") 65 66 // for a given (permissioned) subnet, the test stakes and restakes multiple 67 // times a node as a primary and subnet validator. The BLS key of the node is 68 // changed across staking periods, and it can even be nil. We test that 69 // GetValidatorSet returns the correct primary and subnet validators data, with 70 // the right BLS key version at all relevant heights. 71 func TestGetValidatorsSetProperty(t *testing.T) { 72 properties := gopter.NewProperties(nil) 73 74 // to reproduce a given scenario do something like this: 75 // parameters := gopter.DefaultTestParametersWithSeed(1685887576153675816) 76 // properties := gopter.NewProperties(parameters) 77 78 properties.Property("check GetValidatorSet", prop.ForAll( 79 func(events []uint8) string { 80 vm, subnetID, err := buildVM(t) 81 if err != nil { 82 return "failed building vm: " + err.Error() 83 } 84 vm.ctx.Lock.Lock() 85 defer func() { 86 _ = vm.Shutdown(context.Background()) 87 vm.ctx.Lock.Unlock() 88 }() 89 nodeID := ids.GenerateTestNodeID() 90 91 currentTime := defaultGenesisTime 92 vm.clock.Set(currentTime) 93 vm.state.SetTimestamp(currentTime) 94 95 // build a valid sequence of validators start/end times, given the 96 // random events sequence received as test input 97 validatorsTimes, err := buildTimestampsList(events, currentTime, nodeID) 98 if err != nil { 99 return "failed building events sequence: " + err.Error() 100 } 101 102 validatorSetByHeightAndSubnet := make(map[uint64]map[ids.ID]map[ids.NodeID]*validators.GetValidatorOutput) 103 if err := takeValidatorsSnapshotAtCurrentHeight(vm, validatorSetByHeightAndSubnet); err != nil { 104 return failedValidatorSnapshotString + err.Error() 105 } 106 107 // insert validator sequence 108 var ( 109 currentPrimaryValidator = (*state.Staker)(nil) 110 currentSubnetValidator = (*state.Staker)(nil) 111 ) 112 for _, ev := range validatorsTimes { 113 // at each step we remove at least a subnet validator 114 if currentSubnetValidator != nil { 115 err := terminateSubnetValidator(vm, currentSubnetValidator) 116 if err != nil { 117 return "could not terminate current subnet validator: " + err.Error() 118 } 119 currentSubnetValidator = nil 120 121 if err := takeValidatorsSnapshotAtCurrentHeight(vm, validatorSetByHeightAndSubnet); err != nil { 122 return failedValidatorSnapshotString + err.Error() 123 } 124 } 125 126 switch ev.eventType { 127 case startSubnetValidator: 128 currentSubnetValidator, err = addSubnetValidator(vm, ev, subnetID) 129 if err != nil { 130 return "could not add subnet validator: " + err.Error() 131 } 132 if err := takeValidatorsSnapshotAtCurrentHeight(vm, validatorSetByHeightAndSubnet); err != nil { 133 return failedValidatorSnapshotString + err.Error() 134 } 135 136 case startPrimaryWithBLS: 137 // when adding a primary validator, also remove the current 138 // primary one 139 if currentPrimaryValidator != nil { 140 err := terminatePrimaryValidator(vm, currentPrimaryValidator) 141 if err != nil { 142 return "could not terminate current primary validator: " + err.Error() 143 } 144 // no need to nil current primary validator, we'll 145 // reassign immediately 146 147 if err := takeValidatorsSnapshotAtCurrentHeight(vm, validatorSetByHeightAndSubnet); err != nil { 148 return failedValidatorSnapshotString + err.Error() 149 } 150 } 151 currentPrimaryValidator, err = addPrimaryValidatorWithBLSKey(vm, ev) 152 if err != nil { 153 return "could not add primary validator with BLS key: " + err.Error() 154 } 155 if err := takeValidatorsSnapshotAtCurrentHeight(vm, validatorSetByHeightAndSubnet); err != nil { 156 return failedValidatorSnapshotString + err.Error() 157 } 158 159 default: 160 return fmt.Sprintf("unexpected staker type: %v", ev.eventType) 161 } 162 } 163 164 // Checks: let's look back at validator sets at previous heights and 165 // make sure they match the snapshots already taken 166 snapshotHeights := maps.Keys(validatorSetByHeightAndSubnet) 167 sort.Slice(snapshotHeights, func(i, j int) bool { return snapshotHeights[i] < snapshotHeights[j] }) 168 for idx, snapShotHeight := range snapshotHeights { 169 lastAcceptedHeight, err := vm.GetCurrentHeight(context.Background()) 170 if err != nil { 171 return err.Error() 172 } 173 174 nextSnapShotHeight := lastAcceptedHeight + 1 175 if idx != len(snapshotHeights)-1 { 176 nextSnapShotHeight = snapshotHeights[idx+1] 177 } 178 179 // within [snapShotHeight] and [nextSnapShotHeight], the validator set 180 // does not change and must be equal to snapshot at [snapShotHeight] 181 for height := snapShotHeight; height < nextSnapShotHeight; height++ { 182 for subnetID, validatorsSet := range validatorSetByHeightAndSubnet[snapShotHeight] { 183 res, err := vm.GetValidatorSet(context.Background(), height, subnetID) 184 if err != nil { 185 return fmt.Sprintf("failed GetValidatorSet at height %v: %v", height, err) 186 } 187 if !reflect.DeepEqual(validatorsSet, res) { 188 return "failed validators set comparison" 189 } 190 } 191 } 192 } 193 194 return "" 195 }, 196 gen.SliceOfN( 197 10, 198 gen.OneConstOf( 199 startPrimaryWithBLS, 200 startSubnetValidator, 201 ), 202 ).SuchThat(func(v interface{}) bool { 203 list := v.([]uint8) 204 return len(list) > 0 && list[0] == startPrimaryWithBLS 205 }), 206 )) 207 208 properties.TestingRun(t) 209 } 210 211 func takeValidatorsSnapshotAtCurrentHeight(vm *VM, validatorsSetByHeightAndSubnet map[uint64]map[ids.ID]map[ids.NodeID]*validators.GetValidatorOutput) error { 212 if validatorsSetByHeightAndSubnet == nil { 213 validatorsSetByHeightAndSubnet = make(map[uint64]map[ids.ID]map[ids.NodeID]*validators.GetValidatorOutput) 214 } 215 216 lastBlkID := vm.state.GetLastAccepted() 217 lastBlk, err := vm.state.GetStatelessBlock(lastBlkID) 218 if err != nil { 219 return err 220 } 221 height := lastBlk.Height() 222 validatorsSetBySubnet, ok := validatorsSetByHeightAndSubnet[height] 223 if !ok { 224 validatorsSetByHeightAndSubnet[height] = make(map[ids.ID]map[ids.NodeID]*validators.GetValidatorOutput) 225 validatorsSetBySubnet = validatorsSetByHeightAndSubnet[height] 226 } 227 228 stakerIt, err := vm.state.GetCurrentStakerIterator() 229 if err != nil { 230 return err 231 } 232 defer stakerIt.Release() 233 for stakerIt.Next() { 234 v := stakerIt.Value() 235 validatorsSet, ok := validatorsSetBySubnet[v.SubnetID] 236 if !ok { 237 validatorsSetBySubnet[v.SubnetID] = make(map[ids.NodeID]*validators.GetValidatorOutput) 238 validatorsSet = validatorsSetBySubnet[v.SubnetID] 239 } 240 241 blsKey := v.PublicKey 242 if v.SubnetID != constants.PrimaryNetworkID { 243 // pick bls key from primary validator 244 s, err := vm.state.GetCurrentValidator(constants.PlatformChainID, v.NodeID) 245 if err != nil { 246 return err 247 } 248 blsKey = s.PublicKey 249 } 250 251 validatorsSet[v.NodeID] = &validators.GetValidatorOutput{ 252 NodeID: v.NodeID, 253 PublicKey: blsKey, 254 Weight: v.Weight, 255 } 256 } 257 return nil 258 } 259 260 func addSubnetValidator(vm *VM, data *validatorInputData, subnetID ids.ID) (*state.Staker, error) { 261 factory := txstest.NewWalletFactory(vm.ctx, &vm.Config, vm.state) 262 builder, signer := factory.NewWallet(keys[0], keys[1]) 263 utx, err := builder.NewAddSubnetValidatorTx( 264 &txs.SubnetValidator{ 265 Validator: txs.Validator{ 266 NodeID: data.nodeID, 267 Start: uint64(data.startTime.Unix()), 268 End: uint64(data.endTime.Unix()), 269 Wght: vm.Config.MinValidatorStake, 270 }, 271 Subnet: subnetID, 272 }, 273 walletcommon.WithChangeOwner(&secp256k1fx.OutputOwners{ 274 Threshold: 1, 275 Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, 276 }), 277 ) 278 if err != nil { 279 return nil, fmt.Errorf("could not build AddSubnetValidatorTx: %w", err) 280 } 281 tx, err := walletsigner.SignUnsigned(context.Background(), signer, utx) 282 if err != nil { 283 return nil, fmt.Errorf("could not sign AddSubnetValidatorTx: %w", err) 284 } 285 return internalAddValidator(vm, tx) 286 } 287 288 func addPrimaryValidatorWithBLSKey(vm *VM, data *validatorInputData) (*state.Staker, error) { 289 addr := keys[0].PublicKey().Address() 290 291 sk, err := bls.NewSecretKey() 292 if err != nil { 293 return nil, fmt.Errorf("failed to generate BLS key: %w", err) 294 } 295 296 factory := txstest.NewWalletFactory(vm.ctx, &vm.Config, vm.state) 297 builder, txSigner := factory.NewWallet(keys[0], keys[1]) 298 utx, err := builder.NewAddPermissionlessValidatorTx( 299 &txs.SubnetValidator{ 300 Validator: txs.Validator{ 301 NodeID: data.nodeID, 302 Start: uint64(data.startTime.Unix()), 303 End: uint64(data.endTime.Unix()), 304 Wght: vm.Config.MinValidatorStake, 305 }, 306 Subnet: constants.PrimaryNetworkID, 307 }, 308 signer.NewProofOfPossession(sk), 309 vm.ctx.AVAXAssetID, 310 &secp256k1fx.OutputOwners{ 311 Threshold: 1, 312 Addrs: []ids.ShortID{addr}, 313 }, 314 &secp256k1fx.OutputOwners{ 315 Threshold: 1, 316 Addrs: []ids.ShortID{addr}, 317 }, 318 reward.PercentDenominator, 319 walletcommon.WithChangeOwner(&secp256k1fx.OutputOwners{ 320 Threshold: 1, 321 Addrs: []ids.ShortID{addr}, 322 }), 323 ) 324 if err != nil { 325 return nil, fmt.Errorf("could not build AddPermissionlessValidatorTx: %w", err) 326 } 327 tx, err := walletsigner.SignUnsigned(context.Background(), txSigner, utx) 328 if err != nil { 329 return nil, fmt.Errorf("could not sign AddPermissionlessValidatorTx: %w", err) 330 } 331 return internalAddValidator(vm, tx) 332 } 333 334 func internalAddValidator(vm *VM, signedTx *txs.Tx) (*state.Staker, error) { 335 vm.ctx.Lock.Unlock() 336 err := vm.issueTxFromRPC(signedTx) 337 vm.ctx.Lock.Lock() 338 339 if err != nil { 340 return nil, fmt.Errorf("could not add tx to mempool: %w", err) 341 } 342 343 blk, err := vm.Builder.BuildBlock(context.Background()) 344 if err != nil { 345 return nil, fmt.Errorf("failed building block: %w", err) 346 } 347 if err := blk.Verify(context.Background()); err != nil { 348 return nil, fmt.Errorf("failed verifying block: %w", err) 349 } 350 if err := blk.Accept(context.Background()); err != nil { 351 return nil, fmt.Errorf("failed accepting block: %w", err) 352 } 353 if err := vm.SetPreference(context.Background(), vm.manager.LastAccepted()); err != nil { 354 return nil, fmt.Errorf("failed setting preference: %w", err) 355 } 356 357 stakerTx := signedTx.Unsigned.(txs.Staker) 358 return vm.state.GetCurrentValidator(stakerTx.SubnetID(), stakerTx.NodeID()) 359 } 360 361 func terminateSubnetValidator(vm *VM, validator *state.Staker) error { 362 currentTime := validator.EndTime 363 vm.clock.Set(currentTime) 364 vm.state.SetTimestamp(currentTime) 365 366 blk, err := vm.Builder.BuildBlock(context.Background()) 367 if err != nil { 368 return fmt.Errorf("failed building block: %w", err) 369 } 370 if err := blk.Verify(context.Background()); err != nil { 371 return fmt.Errorf("failed verifying block: %w", err) 372 } 373 if err := blk.Accept(context.Background()); err != nil { 374 return fmt.Errorf("failed accepting block: %w", err) 375 } 376 if err := vm.SetPreference(context.Background(), vm.manager.LastAccepted()); err != nil { 377 return fmt.Errorf("failed setting preference: %w", err) 378 } 379 380 return nil 381 } 382 383 func terminatePrimaryValidator(vm *VM, validator *state.Staker) error { 384 currentTime := validator.EndTime 385 vm.clock.Set(currentTime) 386 vm.state.SetTimestamp(currentTime) 387 388 blk, err := vm.Builder.BuildBlock(context.Background()) 389 if err != nil { 390 return fmt.Errorf("failed building block: %w", err) 391 } 392 if err := blk.Verify(context.Background()); err != nil { 393 return fmt.Errorf("failed verifying block: %w", err) 394 } 395 396 proposalBlk := blk.(snowman.OracleBlock) 397 options, err := proposalBlk.Options(context.Background()) 398 if err != nil { 399 return fmt.Errorf("failed retrieving options: %w", err) 400 } 401 402 commit := options[0].(*blockexecutor.Block) 403 _, ok := commit.Block.(*block.BanffCommitBlock) 404 if !ok { 405 return fmt.Errorf("failed retrieving commit option: %w", err) 406 } 407 if err := blk.Accept(context.Background()); err != nil { 408 return fmt.Errorf("failed accepting block: %w", err) 409 } 410 411 if err := commit.Verify(context.Background()); err != nil { 412 return fmt.Errorf("failed verifying commit block: %w", err) 413 } 414 if err := commit.Accept(context.Background()); err != nil { 415 return fmt.Errorf("failed accepting commit block: %w", err) 416 } 417 418 if err := vm.SetPreference(context.Background(), vm.manager.LastAccepted()); err != nil { 419 return fmt.Errorf("failed setting preference: %w", err) 420 } 421 422 return nil 423 } 424 425 type validatorInputData struct { 426 eventType uint8 427 startTime time.Time 428 endTime time.Time 429 nodeID ids.NodeID 430 publicKey *bls.PublicKey 431 } 432 433 // buildTimestampsList creates validators start and end time, given the event list. 434 // output is returned as a list of validatorInputData 435 func buildTimestampsList(events []uint8, currentTime time.Time, nodeID ids.NodeID) ([]*validatorInputData, error) { 436 res := make([]*validatorInputData, 0, len(events)) 437 438 currentTime = currentTime.Add(txexecutor.SyncBound) 439 switch endTime := currentTime.Add(defaultMinStakingDuration); events[0] { 440 case startPrimaryWithBLS: 441 sk, err := bls.NewSecretKey() 442 if err != nil { 443 return nil, fmt.Errorf("could not make private key: %w", err) 444 } 445 446 res = append(res, &validatorInputData{ 447 eventType: startPrimaryWithBLS, 448 startTime: currentTime, 449 endTime: endTime, 450 nodeID: nodeID, 451 publicKey: bls.PublicFromSecretKey(sk), 452 }) 453 default: 454 return nil, fmt.Errorf("unexpected initial event %d", events[0]) 455 } 456 457 // track current primary validator to make sure its staking period 458 // covers all of its subnet validators 459 currentPrimaryVal := res[0] 460 for i := 1; i < len(events); i++ { 461 currentTime = currentTime.Add(txexecutor.SyncBound) 462 463 switch currentEvent := events[i]; currentEvent { 464 case startSubnetValidator: 465 endTime := currentTime.Add(defaultMinStakingDuration) 466 res = append(res, &validatorInputData{ 467 eventType: startSubnetValidator, 468 startTime: currentTime, 469 endTime: endTime, 470 nodeID: nodeID, 471 publicKey: nil, 472 }) 473 474 currentPrimaryVal.endTime = endTime.Add(time.Second) 475 currentTime = endTime.Add(time.Second) 476 477 case startPrimaryWithBLS: 478 currentTime = currentPrimaryVal.endTime.Add(txexecutor.SyncBound) 479 sk, err := bls.NewSecretKey() 480 if err != nil { 481 return nil, fmt.Errorf("could not make private key: %w", err) 482 } 483 484 endTime := currentTime.Add(defaultMinStakingDuration) 485 val := &validatorInputData{ 486 eventType: startPrimaryWithBLS, 487 startTime: currentTime, 488 endTime: endTime, 489 nodeID: nodeID, 490 publicKey: bls.PublicFromSecretKey(sk), 491 } 492 res = append(res, val) 493 currentPrimaryVal = val 494 } 495 } 496 return res, nil 497 } 498 499 func TestTimestampListGenerator(t *testing.T) { 500 properties := gopter.NewProperties(nil) 501 502 properties.Property("primary validators are returned in sequence", prop.ForAll( 503 func(events []uint8) string { 504 currentTime := time.Now() 505 nodeID := ids.GenerateTestNodeID() 506 validatorsTimes, err := buildTimestampsList(events, currentTime, nodeID) 507 if err != nil { 508 return failedBuildingEventSeqString + err.Error() 509 } 510 511 if len(validatorsTimes) == 0 { 512 return errEmptyEventsList.Error() 513 } 514 515 // nil out non subnet validators 516 subnetIndexes := make([]int, 0) 517 for idx, ev := range validatorsTimes { 518 if ev.eventType == startSubnetValidator { 519 subnetIndexes = append(subnetIndexes, idx) 520 } 521 } 522 for _, idx := range subnetIndexes { 523 validatorsTimes[idx] = nil 524 } 525 526 currentEventTime := currentTime 527 for i, ev := range validatorsTimes { 528 if ev == nil { 529 continue // a subnet validator 530 } 531 if currentEventTime.After(ev.startTime) { 532 return fmt.Sprintf("validator %d start time larger than current event time", i) 533 } 534 535 if ev.startTime.After(ev.endTime) { 536 return fmt.Sprintf("validator %d start time larger than its end time", i) 537 } 538 539 currentEventTime = ev.endTime 540 } 541 542 return "" 543 }, 544 gen.SliceOf(gen.OneConstOf( 545 startPrimaryWithBLS, 546 startSubnetValidator, 547 )).SuchThat(func(v interface{}) bool { 548 list := v.([]uint8) 549 return len(list) > 0 && list[0] == startPrimaryWithBLS 550 }), 551 )) 552 553 properties.Property("subnet validators are returned in sequence", prop.ForAll( 554 func(events []uint8) string { 555 currentTime := time.Now() 556 nodeID := ids.GenerateTestNodeID() 557 validatorsTimes, err := buildTimestampsList(events, currentTime, nodeID) 558 if err != nil { 559 return failedBuildingEventSeqString + err.Error() 560 } 561 562 if len(validatorsTimes) == 0 { 563 return errEmptyEventsList.Error() 564 } 565 566 // nil out non subnet validators 567 nonSubnetIndexes := make([]int, 0) 568 for idx, ev := range validatorsTimes { 569 if ev.eventType != startSubnetValidator { 570 nonSubnetIndexes = append(nonSubnetIndexes, idx) 571 } 572 } 573 for _, idx := range nonSubnetIndexes { 574 validatorsTimes[idx] = nil 575 } 576 577 currentEventTime := currentTime 578 for i, ev := range validatorsTimes { 579 if ev == nil { 580 continue // a non-subnet validator 581 } 582 if currentEventTime.After(ev.startTime) { 583 return fmt.Sprintf("validator %d start time larger than current event time", i) 584 } 585 586 if ev.startTime.After(ev.endTime) { 587 return fmt.Sprintf("validator %d start time larger than its end time", i) 588 } 589 590 currentEventTime = ev.endTime 591 } 592 593 return "" 594 }, 595 gen.SliceOf(gen.OneConstOf( 596 startPrimaryWithBLS, 597 startSubnetValidator, 598 )).SuchThat(func(v interface{}) bool { 599 list := v.([]uint8) 600 return len(list) > 0 && list[0] == startPrimaryWithBLS 601 }), 602 )) 603 604 properties.Property("subnet validators' times are bound by a primary validator's times", prop.ForAll( 605 func(events []uint8) string { 606 currentTime := time.Now() 607 nodeID := ids.GenerateTestNodeID() 608 validatorsTimes, err := buildTimestampsList(events, currentTime, nodeID) 609 if err != nil { 610 return failedBuildingEventSeqString + err.Error() 611 } 612 613 if len(validatorsTimes) == 0 { 614 return errEmptyEventsList.Error() 615 } 616 617 currentPrimaryValidator := validatorsTimes[0] 618 for i := 1; i < len(validatorsTimes); i++ { 619 if validatorsTimes[i].eventType != startSubnetValidator { 620 currentPrimaryValidator = validatorsTimes[i] 621 continue 622 } 623 624 subnetVal := validatorsTimes[i] 625 if currentPrimaryValidator.startTime.After(subnetVal.startTime) || 626 subnetVal.endTime.After(currentPrimaryValidator.endTime) { 627 return "subnet validator not bounded by primary network ones" 628 } 629 } 630 return "" 631 }, 632 gen.SliceOf(gen.OneConstOf( 633 startPrimaryWithBLS, 634 startSubnetValidator, 635 )).SuchThat(func(v interface{}) bool { 636 list := v.([]uint8) 637 return len(list) > 0 && list[0] == startPrimaryWithBLS 638 }), 639 )) 640 641 properties.TestingRun(t) 642 } 643 644 // add a single validator at the end of times, 645 // to make sure it won't pollute our tests 646 func buildVM(t *testing.T) (*VM, ids.ID, error) { 647 forkTime := defaultGenesisTime 648 vm := &VM{Config: config.Config{ 649 Chains: chains.TestManager, 650 UptimeLockedCalculator: uptime.NewLockedCalculator(), 651 SybilProtectionEnabled: true, 652 Validators: validators.NewManager(), 653 StaticFeeConfig: fee.StaticConfig{ 654 TxFee: defaultTxFee, 655 CreateSubnetTxFee: 100 * defaultTxFee, 656 TransformSubnetTxFee: 100 * defaultTxFee, 657 CreateBlockchainTxFee: 100 * defaultTxFee, 658 }, 659 MinValidatorStake: defaultMinValidatorStake, 660 MaxValidatorStake: defaultMaxValidatorStake, 661 MinDelegatorStake: defaultMinDelegatorStake, 662 MinStakeDuration: defaultMinStakingDuration, 663 MaxStakeDuration: defaultMaxStakingDuration, 664 RewardConfig: defaultRewardConfig, 665 UpgradeConfig: upgrade.Config{ 666 ApricotPhase3Time: forkTime, 667 ApricotPhase5Time: forkTime, 668 BanffTime: forkTime, 669 CortinaTime: forkTime, 670 EUpgradeTime: mockable.MaxTime, 671 }, 672 }} 673 vm.clock.Set(forkTime.Add(time.Second)) 674 675 baseDB := memdb.New() 676 chainDB := prefixdb.New([]byte{0}, baseDB) 677 atomicDB := prefixdb.New([]byte{1}, baseDB) 678 679 msgChan := make(chan common.Message, 1) 680 ctx := snowtest.Context(t, snowtest.PChainID) 681 682 m := atomic.NewMemory(atomicDB) 683 ctx.SharedMemory = m.NewSharedMemory(ctx.ChainID) 684 685 ctx.Lock.Lock() 686 defer ctx.Lock.Unlock() 687 appSender := &common.SenderTest{} 688 appSender.CantSendAppGossip = true 689 appSender.SendAppGossipF = func(context.Context, common.SendConfig, []byte) error { 690 return nil 691 } 692 693 genesisBytes, err := buildCustomGenesis(ctx.AVAXAssetID) 694 if err != nil { 695 return nil, ids.Empty, err 696 } 697 698 err = vm.Initialize( 699 context.Background(), 700 ctx, 701 chainDB, 702 genesisBytes, 703 nil, 704 nil, 705 msgChan, 706 nil, 707 appSender, 708 ) 709 if err != nil { 710 return nil, ids.Empty, err 711 } 712 713 err = vm.SetState(context.Background(), snow.NormalOp) 714 if err != nil { 715 return nil, ids.Empty, err 716 } 717 718 // Create a subnet and store it in testSubnet1 719 // Note: following Banff activation, block acceptance will move 720 // chain time ahead 721 factory := txstest.NewWalletFactory(vm.ctx, &vm.Config, vm.state) 722 builder, signer := factory.NewWallet(keys[len(keys)-1]) 723 utx, err := builder.NewCreateSubnetTx( 724 &secp256k1fx.OutputOwners{ 725 Threshold: 1, 726 Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, 727 }, 728 walletcommon.WithChangeOwner(&secp256k1fx.OutputOwners{ 729 Threshold: 1, 730 Addrs: []ids.ShortID{keys[0].PublicKey().Address()}, 731 }), 732 ) 733 if err != nil { 734 return nil, ids.Empty, err 735 } 736 testSubnet1, err = walletsigner.SignUnsigned(context.Background(), signer, utx) 737 if err != nil { 738 return nil, ids.Empty, err 739 } 740 vm.ctx.Lock.Unlock() 741 err = vm.issueTxFromRPC(testSubnet1) 742 vm.ctx.Lock.Lock() 743 if err != nil { 744 return nil, ids.Empty, err 745 } 746 747 blk, err := vm.Builder.BuildBlock(context.Background()) 748 if err != nil { 749 return nil, ids.Empty, err 750 } 751 if err := blk.Verify(context.Background()); err != nil { 752 return nil, ids.Empty, err 753 } 754 if err := blk.Accept(context.Background()); err != nil { 755 return nil, ids.Empty, err 756 } 757 if err := vm.SetPreference(context.Background(), vm.manager.LastAccepted()); err != nil { 758 return nil, ids.Empty, err 759 } 760 761 return vm, testSubnet1.ID(), nil 762 } 763 764 func buildCustomGenesis(avaxAssetID ids.ID) ([]byte, error) { 765 genesisUTXOs := make([]api.UTXO, len(keys)) 766 for i, key := range keys { 767 id := key.PublicKey().Address() 768 addr, err := address.FormatBech32(constants.UnitTestHRP, id.Bytes()) 769 if err != nil { 770 return nil, err 771 } 772 genesisUTXOs[i] = api.UTXO{ 773 Amount: json.Uint64(defaultBalance), 774 Address: addr, 775 } 776 } 777 778 // we need at least a validator, otherwise BuildBlock would fail, since it 779 // won't find next staker to promote/evict from stakers set. Contrary to 780 // what happens with production code we push such validator at the end of 781 // times, so to avoid interference with our tests 782 nodeID := genesisNodeIDs[len(genesisNodeIDs)-1] 783 addr, err := address.FormatBech32(constants.UnitTestHRP, nodeID.Bytes()) 784 if err != nil { 785 return nil, err 786 } 787 788 starTime := mockable.MaxTime.Add(-1 * defaultMinStakingDuration) 789 endTime := mockable.MaxTime 790 genesisValidator := api.GenesisPermissionlessValidator{ 791 GenesisValidator: api.GenesisValidator{ 792 StartTime: json.Uint64(starTime.Unix()), 793 EndTime: json.Uint64(endTime.Unix()), 794 NodeID: nodeID, 795 }, 796 RewardOwner: &api.Owner{ 797 Threshold: 1, 798 Addresses: []string{addr}, 799 }, 800 Staked: []api.UTXO{{ 801 Amount: json.Uint64(defaultWeight), 802 Address: addr, 803 }}, 804 DelegationFee: reward.PercentDenominator, 805 } 806 807 buildGenesisArgs := api.BuildGenesisArgs{ 808 Encoding: formatting.Hex, 809 NetworkID: json.Uint32(constants.UnitTestID), 810 AvaxAssetID: avaxAssetID, 811 UTXOs: genesisUTXOs, 812 Validators: []api.GenesisPermissionlessValidator{genesisValidator}, 813 Chains: nil, 814 Time: json.Uint64(defaultGenesisTime.Unix()), 815 InitialSupply: json.Uint64(360 * units.MegaAvax), 816 } 817 818 buildGenesisResponse := api.BuildGenesisReply{} 819 platformvmSS := api.StaticService{} 820 if err := platformvmSS.BuildGenesis(nil, &buildGenesisArgs, &buildGenesisResponse); err != nil { 821 return nil, err 822 } 823 824 genesisBytes, err := formatting.Decode(buildGenesisResponse.Encoding, buildGenesisResponse.Bytes) 825 if err != nil { 826 return nil, err 827 } 828 829 return genesisBytes, nil 830 }