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  }