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