From 58d5b8ddf85fe3a4a9a9b0739e6d3844e6d1ea9f Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 21 Oct 2024 18:42:17 -0400 Subject: [PATCH 01/11] Populate BLS key diffs for subnet validators --- vms/platformvm/state/state.go | 317 ++++++++++++++++++++-------------- 1 file changed, 186 insertions(+), 131 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 58c2056570bd..a34b587a6732 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -59,8 +59,9 @@ const ( var ( _ State = (*state)(nil) - errValidatorSetAlreadyPopulated = errors.New("validator set already populated") - errIsNotSubnet = errors.New("is not a subnet") + errValidatorSetAlreadyPopulated = errors.New("validator set already populated") + errIsNotSubnet = errors.New("is not a subnet") + errMissingPrimaryNetworkValidator = errors.New("missing primary network validator") BlockIDPrefix = []byte("blockID") BlockPrefix = []byte("block") @@ -2007,164 +2008,218 @@ func (s *state) writeExpiry() error { func (s *state) writeCurrentStakers(updateValidators bool, height uint64, codecVersion uint16) error { for subnetID, validatorDiffs := range s.currentStakers.validatorDiffs { + // We must write the primary network stakers last because writing subnet + // validator diffs may depend on the primary network validator diffs to + // inherit the public keys. + if subnetID == constants.PrimaryNetworkID { + continue + } + delete(s.currentStakers.validatorDiffs, subnetID) - // Select db to write to - validatorDB := s.currentSubnetValidatorList - delegatorDB := s.currentSubnetDelegatorList - if subnetID == constants.PrimaryNetworkID { - validatorDB = s.currentValidatorList - delegatorDB = s.currentDelegatorList + err := s.writeCurrentStakersSubnetDiff( + subnetID, + validatorDiffs, + updateValidators, + height, + codecVersion, + ) + if err != nil { + return err } + } - // Record the change in weight and/or public key for each validator. - for nodeID, validatorDiff := range validatorDiffs { - // Copy [nodeID] so it doesn't get overwritten next iteration. - nodeID := nodeID + if validatorDiffs, ok := s.currentStakers.validatorDiffs[constants.PrimaryNetworkID]; ok { + delete(s.currentStakers.validatorDiffs, constants.PrimaryNetworkID) - weightDiff := &ValidatorWeightDiff{ - Decrease: validatorDiff.validatorStatus == deleted, - } - switch validatorDiff.validatorStatus { - case added: - staker := validatorDiff.validator - weightDiff.Amount = staker.Weight - - // Invariant: Only the Primary Network contains non-nil public - // keys. - if staker.PublicKey != nil { - // Record that the public key for the validator is being - // added. This means the prior value for the public key was - // nil. - err := s.validatorPublicKeyDiffsDB.Put( - marshalDiffKey(constants.PrimaryNetworkID, height, nodeID), - nil, - ) - if err != nil { - return err - } - } + err := s.writeCurrentStakersSubnetDiff( + constants.PrimaryNetworkID, + validatorDiffs, + updateValidators, + height, + codecVersion, + ) + if err != nil { + return err + } + } - // The validator is being added. - // - // Invariant: It's impossible for a delegator to have been - // rewarded in the same block that the validator was added. - startTime := uint64(staker.StartTime.Unix()) - metadata := &validatorMetadata{ - txID: staker.TxID, - lastUpdated: staker.StartTime, - - UpDuration: 0, - LastUpdated: startTime, - StakerStartTime: startTime, - PotentialReward: staker.PotentialReward, - PotentialDelegateeReward: 0, - } + // TODO: Move validator set management out of the state package + // + // Attempt to update the stake metrics + if !updateValidators { + return nil + } - metadataBytes, err := MetadataCodec.Marshal(codecVersion, metadata) - if err != nil { - return fmt.Errorf("failed to serialize current validator: %w", err) - } + totalWeight, err := s.validators.TotalWeight(constants.PrimaryNetworkID) + if err != nil { + return fmt.Errorf("failed to get total weight of primary network: %w", err) + } - if err = validatorDB.Put(staker.TxID[:], metadataBytes); err != nil { - return fmt.Errorf("failed to write current validator to list: %w", err) - } + s.metrics.SetLocalStake(s.validators.GetWeight(constants.PrimaryNetworkID, s.ctx.NodeID)) + s.metrics.SetTotalStake(totalWeight) + return nil +} - s.validatorState.LoadValidatorMetadata(nodeID, subnetID, metadata) - case deleted: - staker := validatorDiff.validator - weightDiff.Amount = staker.Weight - - // Invariant: Only the Primary Network contains non-nil public - // keys. - if staker.PublicKey != nil { - // Record that the public key for the validator is being - // removed. This means we must record the prior value of the - // public key. - // - // Note: We store the uncompressed public key here as it is - // significantly more efficient to parse when applying - // diffs. - err := s.validatorPublicKeyDiffsDB.Put( - marshalDiffKey(constants.PrimaryNetworkID, height, nodeID), - bls.PublicKeyToUncompressedBytes(staker.PublicKey), - ) - if err != nil { - return err - } - } +func (s *state) writeCurrentStakersSubnetDiff( + subnetID ids.ID, + validatorDiffs map[ids.NodeID]*diffValidator, + updateValidators bool, + height uint64, + codecVersion uint16, +) error { + // Select db to write to + validatorDB := s.currentSubnetValidatorList + delegatorDB := s.currentSubnetDelegatorList + if subnetID == constants.PrimaryNetworkID { + validatorDB = s.currentValidatorList + delegatorDB = s.currentDelegatorList + } - if err := validatorDB.Delete(staker.TxID[:]); err != nil { - return fmt.Errorf("failed to delete current staker: %w", err) + // Record the change in weight and/or public key for each validator. + for nodeID, validatorDiff := range validatorDiffs { + var ( + staker *Staker + pk *bls.PublicKey + weightDiff = &ValidatorWeightDiff{ + Decrease: validatorDiff.validatorStatus == deleted, + } + ) + if validatorDiff.validatorStatus != unmodified { + staker = validatorDiff.validator + + pk = staker.PublicKey + // For non-primary network validators, the public key is inherited + // from the primary network. + if subnetID != constants.PrimaryNetworkID { + if vdr, ok := s.currentStakers.validators[constants.PrimaryNetworkID][nodeID]; ok && vdr.validator != nil { + // The primary network validator is still present after + // writing. + pk = vdr.validator.PublicKey + } else if vdr, ok := s.currentStakers.validatorDiffs[constants.PrimaryNetworkID][nodeID]; ok && vdr.validator != nil { + // The primary network validator is being removed during + // writing. + pk = vdr.validator.PublicKey + } else { + // This should never happen. + return ErrMissingPrimaryNetworkValidator } - - s.validatorState.DeleteValidatorMetadata(nodeID, subnetID) } - err := writeCurrentDelegatorDiff( - delegatorDB, - weightDiff, - validatorDiff, - codecVersion, - ) - if err != nil { - return err + weightDiff.Amount = staker.Weight + } + + switch validatorDiff.validatorStatus { + case added: + if pk != nil { + // Record that the public key for the validator is being added. + // This means the prior value for the public key was nil. + err := s.validatorPublicKeyDiffsDB.Put( + marshalDiffKey(subnetID, height, nodeID), + nil, + ) + if err != nil { + return err + } } - if weightDiff.Amount == 0 { - // No weight change to record; go to next validator. - continue + // The validator is being added. + // + // Invariant: It's impossible for a delegator to have been rewarded + // in the same block that the validator was added. + startTime := uint64(staker.StartTime.Unix()) + metadata := &validatorMetadata{ + txID: staker.TxID, + lastUpdated: staker.StartTime, + + UpDuration: 0, + LastUpdated: startTime, + StakerStartTime: startTime, + PotentialReward: staker.PotentialReward, + PotentialDelegateeReward: 0, } - err = s.validatorWeightDiffsDB.Put( - marshalDiffKey(subnetID, height, nodeID), - marshalWeightDiff(weightDiff), - ) + metadataBytes, err := MetadataCodec.Marshal(codecVersion, metadata) if err != nil { - return err + return fmt.Errorf("failed to serialize current validator: %w", err) } - // TODO: Move the validator set management out of the state package - if !updateValidators { - continue + if err = validatorDB.Put(staker.TxID[:], metadataBytes); err != nil { + return fmt.Errorf("failed to write current validator to list: %w", err) } - if weightDiff.Decrease { - err = s.validators.RemoveWeight(subnetID, nodeID, weightDiff.Amount) - } else { - if validatorDiff.validatorStatus == added { - staker := validatorDiff.validator - err = s.validators.AddStaker( - subnetID, - nodeID, - staker.PublicKey, - staker.TxID, - weightDiff.Amount, - ) - } else { - err = s.validators.AddWeight(subnetID, nodeID, weightDiff.Amount) + s.validatorState.LoadValidatorMetadata(nodeID, subnetID, metadata) + case deleted: + if pk != nil { + // Record that the public key for the validator is being + // removed. This means we must record the prior value of the + // public key. + // + // Note: We store the uncompressed public key here as it is + // significantly more efficient to parse when applying diffs. + err := s.validatorPublicKeyDiffsDB.Put( + marshalDiffKey(subnetID, height, nodeID), + bls.PublicKeyToUncompressedBytes(pk), + ) + if err != nil { + return err } } - if err != nil { - return fmt.Errorf("failed to update validator weight: %w", err) + + if err := validatorDB.Delete(staker.TxID[:]); err != nil { + return fmt.Errorf("failed to delete current staker: %w", err) } + + s.validatorState.DeleteValidatorMetadata(nodeID, subnetID) } - } - // TODO: Move validator set management out of the state package - // - // Attempt to update the stake metrics - if !updateValidators { - return nil - } + err := writeCurrentDelegatorDiff( + delegatorDB, + weightDiff, + validatorDiff, + codecVersion, + ) + if err != nil { + return err + } - totalWeight, err := s.validators.TotalWeight(constants.PrimaryNetworkID) - if err != nil { - return fmt.Errorf("failed to get total weight of primary network: %w", err) - } + if weightDiff.Amount == 0 { + // No weight change to record; go to next validator. + continue + } - s.metrics.SetLocalStake(s.validators.GetWeight(constants.PrimaryNetworkID, s.ctx.NodeID)) - s.metrics.SetTotalStake(totalWeight) + err = s.validatorWeightDiffsDB.Put( + marshalDiffKey(subnetID, height, nodeID), + marshalWeightDiff(weightDiff), + ) + if err != nil { + return err + } + + // TODO: Move the validator set management out of the state package + if !updateValidators { + continue + } + + if weightDiff.Decrease { + err = s.validators.RemoveWeight(subnetID, nodeID, weightDiff.Amount) + } else { + if validatorDiff.validatorStatus == added { + err = s.validators.AddStaker( + subnetID, + nodeID, + pk, + staker.TxID, + weightDiff.Amount, + ) + } else { + err = s.validators.AddWeight(subnetID, nodeID, weightDiff.Amount) + } + } + if err != nil { + return fmt.Errorf("failed to update validator weight: %w", err) + } + } return nil } From 273fbbecd3da929db6d00bab40511884efe5ee0c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 21 Oct 2024 18:50:47 -0400 Subject: [PATCH 02/11] Populate BLS key diffs for subnet validators --- vms/platformvm/state/state.go | 12 +- vms/platformvm/state/state_test.go | 351 +++++++++++++++++---------- vms/platformvm/validators/manager.go | 8 +- 3 files changed, 241 insertions(+), 130 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index a34b587a6732..bed7b4746fbe 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -190,6 +190,7 @@ type State interface { validators map[ids.NodeID]*validators.GetValidatorOutput, startHeight uint64, endHeight uint64, + subnetID ids.ID, ) error SetHeight(height uint64) @@ -1244,10 +1245,11 @@ func (s *state) ApplyValidatorPublicKeyDiffs( validators map[ids.NodeID]*validators.GetValidatorOutput, startHeight uint64, endHeight uint64, + subnetID ids.ID, ) error { diffIter := s.validatorPublicKeyDiffsDB.NewIteratorWithStartAndPrefix( - marshalStartDiffKey(constants.PrimaryNetworkID, startHeight), - constants.PrimaryNetworkID[:], + marshalStartDiffKey(subnetID, startHeight), + subnetID[:], ) defer diffIter.Release() @@ -2101,8 +2103,10 @@ func (s *state) writeCurrentStakersSubnetDiff( // writing. pk = vdr.validator.PublicKey } else { - // This should never happen. - return ErrMissingPrimaryNetworkValidator + // This should never happen as the primary network diffs are + // written last and subnet validator times must be a subset + // of the primary network validator times. + return errMissingPrimaryNetworkValidator } } diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index d29129500435..ba8dbd65481f 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -28,6 +28,7 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/iterator" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/avalanchego/vms/components/avax" @@ -996,35 +997,43 @@ func TestStateAddRemoveValidator(t *testing.T) { state := newTestState(t, memdb.New()) var ( - numNodes = 3 - subnetID = ids.GenerateTestID() - startTime = time.Now() - endTime = startTime.Add(24 * time.Hour) - stakers = make([]Staker, numNodes) + numNodes = 5 + subnetID = ids.GenerateTestID() + startTime = time.Now() + endTime = startTime.Add(24 * time.Hour) + primaryStakers = make([]Staker, numNodes) + subnetStakers = make([]Staker, numNodes) ) - for i := 0; i < numNodes; i++ { - stakers[i] = Staker{ + for i := range primaryStakers { + sk, err := bls.NewSecretKey() + require.NoError(err) + + primaryStakers[i] = Staker{ TxID: ids.GenerateTestID(), NodeID: ids.GenerateTestNodeID(), + PublicKey: bls.PublicFromSecretKey(sk), + SubnetID: constants.PrimaryNetworkID, Weight: uint64(i + 1), StartTime: startTime.Add(time.Duration(i) * time.Second), EndTime: endTime.Add(time.Duration(i) * time.Second), PotentialReward: uint64(i + 1), } - if i%2 == 0 { - stakers[i].SubnetID = subnetID - } else { - sk, err := bls.NewSecretKey() - require.NoError(err) - stakers[i].PublicKey = bls.PublicFromSecretKey(sk) - stakers[i].SubnetID = constants.PrimaryNetworkID + } + for i, primaryStaker := range primaryStakers { + subnetStakers[i] = Staker{ + TxID: ids.GenerateTestID(), + NodeID: primaryStaker.NodeID, + PublicKey: nil, // Key is inherited from the primary network + SubnetID: subnetID, + Weight: uint64(i + 1), + StartTime: primaryStaker.StartTime, + EndTime: primaryStaker.EndTime, + PotentialReward: uint64(i + 1), } } type diff struct { addedValidators []Staker - addedDelegators []Staker - removedDelegators []Staker removedValidators []Staker expectedPrimaryValidatorSet map[ids.NodeID]*validators.GetValidatorOutput @@ -1037,101 +1046,176 @@ func TestStateAddRemoveValidator(t *testing.T) { expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, }, { - // Add a subnet validator - addedValidators: []Staker{stakers[0]}, - expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, - expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ - stakers[0].NodeID: { - NodeID: stakers[0].NodeID, - Weight: stakers[0].Weight, + // Add primary validator 0 + addedValidators: []Staker{primaryStakers[0]}, + expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ + primaryStakers[0].NodeID: { + NodeID: primaryStakers[0].NodeID, + PublicKey: primaryStakers[0].PublicKey, + Weight: primaryStakers[0].Weight, }, }, + expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, }, { - // Remove a subnet validator - removedValidators: []Staker{stakers[0]}, - expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, - expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, + // Add subnet validator 0 + addedValidators: []Staker{subnetStakers[0]}, + expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ + primaryStakers[0].NodeID: { + NodeID: primaryStakers[0].NodeID, + PublicKey: primaryStakers[0].PublicKey, + Weight: primaryStakers[0].Weight, + }, + }, + expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ + subnetStakers[0].NodeID: { + NodeID: subnetStakers[0].NodeID, + PublicKey: primaryStakers[0].PublicKey, + Weight: subnetStakers[0].Weight, + }, + }, }, - { // Add a primary network validator - addedValidators: []Staker{stakers[1]}, + { + // Remove subnet validator 0 + removedValidators: []Staker{subnetStakers[0]}, expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ - stakers[1].NodeID: { - NodeID: stakers[1].NodeID, - PublicKey: stakers[1].PublicKey, - Weight: stakers[1].Weight, + primaryStakers[0].NodeID: { + NodeID: primaryStakers[0].NodeID, + PublicKey: primaryStakers[0].PublicKey, + Weight: primaryStakers[0].Weight, }, }, expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, }, { - // Do nothing + // Add primary network validator 1, and subnet validator 1 + addedValidators: []Staker{primaryStakers[1], subnetStakers[1]}, + // Remove primary network validator 0, and subnet validator 1 + removedValidators: []Staker{primaryStakers[0], subnetStakers[1]}, expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ - stakers[1].NodeID: { - NodeID: stakers[1].NodeID, - PublicKey: stakers[1].PublicKey, - Weight: stakers[1].Weight, + primaryStakers[1].NodeID: { + NodeID: primaryStakers[1].NodeID, + PublicKey: primaryStakers[1].PublicKey, + Weight: primaryStakers[1].Weight, }, }, expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, }, - { // Remove a primary network validator - removedValidators: []Staker{stakers[1]}, - expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, - expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, + { + // Add primary network validator 2, and subnet validator 2 + addedValidators: []Staker{primaryStakers[2], subnetStakers[2]}, + // Remove primary network validator 1 + removedValidators: []Staker{primaryStakers[1]}, + expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ + primaryStakers[2].NodeID: { + NodeID: primaryStakers[2].NodeID, + PublicKey: primaryStakers[2].PublicKey, + Weight: primaryStakers[2].Weight, + }, + }, + expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ + subnetStakers[2].NodeID: { + NodeID: subnetStakers[2].NodeID, + PublicKey: primaryStakers[2].PublicKey, + Weight: subnetStakers[2].Weight, + }, + }, }, { - // Add 2 subnet validators and a primary network validator - addedValidators: []Staker{stakers[0], stakers[1], stakers[2]}, + // Add primary network and subnet validators 3 & 4 + addedValidators: []Staker{primaryStakers[3], primaryStakers[4], subnetStakers[3], subnetStakers[4]}, expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ - stakers[1].NodeID: { - NodeID: stakers[1].NodeID, - PublicKey: stakers[1].PublicKey, - Weight: stakers[1].Weight, + primaryStakers[2].NodeID: { + NodeID: primaryStakers[2].NodeID, + PublicKey: primaryStakers[2].PublicKey, + Weight: primaryStakers[2].Weight, + }, + primaryStakers[3].NodeID: { + NodeID: primaryStakers[3].NodeID, + PublicKey: primaryStakers[3].PublicKey, + Weight: primaryStakers[3].Weight, + }, + primaryStakers[4].NodeID: { + NodeID: primaryStakers[4].NodeID, + PublicKey: primaryStakers[4].PublicKey, + Weight: primaryStakers[4].Weight, }, }, expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ - stakers[0].NodeID: { - NodeID: stakers[0].NodeID, - Weight: stakers[0].Weight, + subnetStakers[2].NodeID: { + NodeID: subnetStakers[2].NodeID, + PublicKey: primaryStakers[2].PublicKey, + Weight: subnetStakers[2].Weight, + }, + subnetStakers[3].NodeID: { + NodeID: subnetStakers[3].NodeID, + PublicKey: primaryStakers[3].PublicKey, + Weight: subnetStakers[3].Weight, }, - stakers[2].NodeID: { - NodeID: stakers[2].NodeID, - Weight: stakers[2].Weight, + subnetStakers[4].NodeID: { + NodeID: subnetStakers[4].NodeID, + PublicKey: primaryStakers[4].PublicKey, + Weight: subnetStakers[4].Weight, }, }, }, { - // Remove 2 subnet validators and a primary network validator. - removedValidators: []Staker{stakers[0], stakers[1], stakers[2]}, + // Remove primary network and subnet validators 2 & 3 & 4 + removedValidators: []Staker{ + primaryStakers[2], primaryStakers[3], primaryStakers[4], + subnetStakers[2], subnetStakers[3], subnetStakers[4], + }, + expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, + expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, + }, + { + // Do nothing expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, }, } for currentIndex, diff := range diffs { - for _, added := range diff.addedValidators { - added := added - require.NoError(state.PutCurrentValidator(&added)) - } - for _, added := range diff.addedDelegators { - added := added - state.PutCurrentDelegator(&added) + d, err := NewDiffOn(state) + require.NoError(err) + + type subnetIDNodeID struct { + subnetID ids.ID + nodeID ids.NodeID } - for _, removed := range diff.removedDelegators { - removed := removed - state.DeleteCurrentDelegator(&removed) + var expectedValidators set.Set[subnetIDNodeID] + for _, added := range diff.addedValidators { + require.NoError(d.PutCurrentValidator(&added)) + + expectedValidators.Add(subnetIDNodeID{ + subnetID: added.SubnetID, + nodeID: added.NodeID, + }) } for _, removed := range diff.removedValidators { - removed := removed - state.DeleteCurrentValidator(&removed) + d.DeleteCurrentValidator(&removed) + + expectedValidators.Remove(subnetIDNodeID{ + subnetID: removed.SubnetID, + nodeID: removed.NodeID, + }) } + require.NoError(d.Apply(state)) + currentHeight := uint64(currentIndex + 1) state.SetHeight(currentHeight) require.NoError(state.Commit()) for _, added := range diff.addedValidators { + subnetNodeID := subnetIDNodeID{ + subnetID: added.SubnetID, + nodeID: added.NodeID, + } + if !expectedValidators.Contains(subnetNodeID) { + continue + } + gotValidator, err := state.GetCurrentValidator(added.SubnetID, added.NodeID) require.NoError(err) require.Equal(added, *gotValidator) @@ -1142,37 +1226,84 @@ func TestStateAddRemoveValidator(t *testing.T) { require.ErrorIs(err, database.ErrNotFound) } + primaryValidatorSet := state.validators.GetMap(constants.PrimaryNetworkID) + delete(primaryValidatorSet, defaultValidatorNodeID) // Ignore the genesis validator + require.Equal(diff.expectedPrimaryValidatorSet, primaryValidatorSet) + + require.Equal(diff.expectedSubnetValidatorSet, state.validators.GetMap(subnetID)) + for i := 0; i < currentIndex; i++ { prevDiff := diffs[i] prevHeight := uint64(i + 1) - primaryValidatorSet := copyValidatorSet(diff.expectedPrimaryValidatorSet) - require.NoError(state.ApplyValidatorWeightDiffs( - context.Background(), - primaryValidatorSet, - currentHeight, - prevHeight+1, - constants.PrimaryNetworkID, - )) - requireEqualWeightsValidatorSet(require, prevDiff.expectedPrimaryValidatorSet, primaryValidatorSet) - - require.NoError(state.ApplyValidatorPublicKeyDiffs( - context.Background(), - primaryValidatorSet, - currentHeight, - prevHeight+1, - )) - requireEqualPublicKeysValidatorSet(require, prevDiff.expectedPrimaryValidatorSet, primaryValidatorSet) - - subnetValidatorSet := copyValidatorSet(diff.expectedSubnetValidatorSet) - require.NoError(state.ApplyValidatorWeightDiffs( - context.Background(), - subnetValidatorSet, - currentHeight, - prevHeight+1, - subnetID, - )) - requireEqualWeightsValidatorSet(require, prevDiff.expectedSubnetValidatorSet, subnetValidatorSet) + { + primaryValidatorSet := copyValidatorSet(diff.expectedPrimaryValidatorSet) + require.NoError(state.ApplyValidatorWeightDiffs( + context.Background(), + primaryValidatorSet, + currentHeight, + prevHeight+1, + constants.PrimaryNetworkID, + )) + require.NoError(state.ApplyValidatorPublicKeyDiffs( + context.Background(), + primaryValidatorSet, + currentHeight, + prevHeight+1, + constants.PrimaryNetworkID, + )) + require.Equal(prevDiff.expectedPrimaryValidatorSet, primaryValidatorSet) + } + + { + legacySubnetValidatorSet := copyValidatorSet(diff.expectedSubnetValidatorSet) + require.NoError(state.ApplyValidatorWeightDiffs( + context.Background(), + legacySubnetValidatorSet, + currentHeight, + prevHeight+1, + subnetID, + )) + + // Update the public keys of the subnet validators with the current + // primary network validator public keys + for nodeID, vdr := range legacySubnetValidatorSet { + if primaryVdr, ok := diff.expectedPrimaryValidatorSet[nodeID]; ok { + vdr.PublicKey = primaryVdr.PublicKey + } else { + vdr.PublicKey = nil + } + } + + require.NoError(state.ApplyValidatorPublicKeyDiffs( + context.Background(), + legacySubnetValidatorSet, + currentHeight, + prevHeight+1, + constants.PrimaryNetworkID, + )) + require.Equal(prevDiff.expectedSubnetValidatorSet, legacySubnetValidatorSet) + } + + { + subnetValidatorSet := copyValidatorSet(diff.expectedSubnetValidatorSet) + require.NoError(state.ApplyValidatorWeightDiffs( + context.Background(), + subnetValidatorSet, + currentHeight, + prevHeight+1, + subnetID, + )) + + require.NoError(state.ApplyValidatorPublicKeyDiffs( + context.Background(), + subnetValidatorSet, + currentHeight, + prevHeight+1, + subnetID, + )) + require.Equal(prevDiff.expectedSubnetValidatorSet, subnetValidatorSet) + } } } } @@ -1188,36 +1319,6 @@ func copyValidatorSet( return result } -func requireEqualWeightsValidatorSet( - require *require.Assertions, - expected map[ids.NodeID]*validators.GetValidatorOutput, - actual map[ids.NodeID]*validators.GetValidatorOutput, -) { - require.Len(actual, len(expected)) - for nodeID, expectedVdr := range expected { - require.Contains(actual, nodeID) - - actualVdr := actual[nodeID] - require.Equal(expectedVdr.NodeID, actualVdr.NodeID) - require.Equal(expectedVdr.Weight, actualVdr.Weight) - } -} - -func requireEqualPublicKeysValidatorSet( - require *require.Assertions, - expected map[ids.NodeID]*validators.GetValidatorOutput, - actual map[ids.NodeID]*validators.GetValidatorOutput, -) { - require.Len(actual, len(expected)) - for nodeID, expectedVdr := range expected { - require.Contains(actual, nodeID) - - actualVdr := actual[nodeID] - require.Equal(expectedVdr.NodeID, actualVdr.NodeID) - require.Equal(expectedVdr.PublicKey, actualVdr.PublicKey) - } -} - func TestParsedStateBlock(t *testing.T) { var ( require = require.New(t) diff --git a/vms/platformvm/validators/manager.go b/vms/platformvm/validators/manager.go index 7f1ea5ea6407..142db3e7635c 100644 --- a/vms/platformvm/validators/manager.go +++ b/vms/platformvm/validators/manager.go @@ -85,6 +85,7 @@ type State interface { validators map[ids.NodeID]*validators.GetValidatorOutput, startHeight uint64, endHeight uint64, + subnetID ids.ID, ) error } @@ -271,7 +272,7 @@ func (m *manager) makePrimaryNetworkValidatorSet( validatorSet, currentHeight, lastDiffHeight, - constants.PlatformChainID, + constants.PrimaryNetworkID, ) if err != nil { return nil, 0, err @@ -282,6 +283,7 @@ func (m *manager) makePrimaryNetworkValidatorSet( validatorSet, currentHeight, lastDiffHeight, + constants.PrimaryNetworkID, ) return validatorSet, currentHeight, err } @@ -348,6 +350,10 @@ func (m *manager) makeSubnetValidatorSet( subnetValidatorSet, currentHeight, lastDiffHeight, + // TODO: Etna introduces L1s whose validators specify their own public + // keys, rather than inheriting them from the primary network. + // Therefore, this will need to use the subnetID after Etna. + constants.PrimaryNetworkID, ) return subnetValidatorSet, currentHeight, err } From 290ef974191e6ebef1660b69c391a494e2ac9455 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 21 Oct 2024 20:31:16 -0400 Subject: [PATCH 03/11] Update mocks --- vms/platformvm/state/mock_state.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go index a17593982572..9d2568422517 100644 --- a/vms/platformvm/state/mock_state.go +++ b/vms/platformvm/state/mock_state.go @@ -149,17 +149,17 @@ func (mr *MockStateMockRecorder) AddUTXO(utxo any) *gomock.Call { } // ApplyValidatorPublicKeyDiffs mocks base method. -func (m *MockState) ApplyValidatorPublicKeyDiffs(ctx context.Context, validators map[ids.NodeID]*validators.GetValidatorOutput, startHeight, endHeight uint64) error { +func (m *MockState) ApplyValidatorPublicKeyDiffs(ctx context.Context, validators map[ids.NodeID]*validators.GetValidatorOutput, startHeight, endHeight uint64, subnetID ids.ID) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ApplyValidatorPublicKeyDiffs", ctx, validators, startHeight, endHeight) + ret := m.ctrl.Call(m, "ApplyValidatorPublicKeyDiffs", ctx, validators, startHeight, endHeight, subnetID) ret0, _ := ret[0].(error) return ret0 } // ApplyValidatorPublicKeyDiffs indicates an expected call of ApplyValidatorPublicKeyDiffs. -func (mr *MockStateMockRecorder) ApplyValidatorPublicKeyDiffs(ctx, validators, startHeight, endHeight any) *gomock.Call { +func (mr *MockStateMockRecorder) ApplyValidatorPublicKeyDiffs(ctx, validators, startHeight, endHeight, subnetID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyValidatorPublicKeyDiffs", reflect.TypeOf((*MockState)(nil).ApplyValidatorPublicKeyDiffs), ctx, validators, startHeight, endHeight) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyValidatorPublicKeyDiffs", reflect.TypeOf((*MockState)(nil).ApplyValidatorPublicKeyDiffs), ctx, validators, startHeight, endHeight, subnetID) } // ApplyValidatorWeightDiffs mocks base method. From 9155e1fd37f436f772568640f35cc813c4405c21 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Mon, 21 Oct 2024 20:34:50 -0400 Subject: [PATCH 04/11] Fix tests --- vms/platformvm/state/state_test.go | 850 ++++++++++------------------- 1 file changed, 283 insertions(+), 567 deletions(-) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index ba8dbd65481f..419586f7871c 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -5,7 +5,6 @@ package state import ( "context" - "fmt" "math" "math/rand" "sync" @@ -28,6 +27,7 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/iterator" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/maybe" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/utils/wrappers" @@ -110,620 +110,336 @@ func TestStateSyncGenesis(t *testing.T) { ) } -// Whenever we store a staker, a whole bunch a data structures are updated +// Whenever we store a staker, a whole bunch of data structures are updated // This test is meant to capture which updates are carried out func TestPersistStakers(t *testing.T) { - tests := map[string]struct { - // Insert or delete a staker to state and store it - storeStaker func(*require.Assertions, ids.ID /*=subnetID*/, *state) *Staker - - // Check that the staker is duly stored/removed in P-chain state - checkStakerInState func(*require.Assertions, *state, *Staker) - - // Check whether validators are duly reported in the validator set, - // with the right weight and showing the BLS key - checkValidatorsSet func(*require.Assertions, *state, *Staker) + const ( + primaryValidatorDuration = 28 * 24 * time.Hour + primaryDelegatorDuration = 14 * 24 * time.Hour + subnetValidatorDuration = 21 * 24 * time.Hour + subnetDelegatorDuration = 14 * 24 * time.Hour + + primaryValidatorReward = iota + primaryDelegatorReward + ) + var ( + primaryValidatorStartTime = time.Now().Truncate(time.Second) + primaryValidatorEndTime = primaryValidatorStartTime.Add(primaryValidatorDuration) + primaryValidatorEndTimeUnix = uint64(primaryValidatorEndTime.Unix()) + + primaryDelegatorStartTime = primaryValidatorStartTime + primaryDelegatorEndTime = primaryDelegatorStartTime.Add(primaryDelegatorDuration) + primaryDelegatorEndTimeUnix = uint64(primaryDelegatorEndTime.Unix()) + + primaryValidatorData = txs.Validator{ + NodeID: ids.GenerateTestNodeID(), + End: primaryValidatorEndTimeUnix, + Wght: 1234, + } + primaryDelegatorData = txs.Validator{ + NodeID: primaryValidatorData.NodeID, + End: primaryDelegatorEndTimeUnix, + Wght: 6789, + } + ) - // Check that node duly track stakers uptimes - checkValidatorUptimes func(*require.Assertions, *state, *Staker) + unsignedAddPrimaryNetworkValidator := createPermissionlessValidatorTx(t, constants.PrimaryNetworkID, primaryValidatorData) + addPrimaryNetworkValidator := &txs.Tx{Unsigned: unsignedAddPrimaryNetworkValidator} + require.NoError(t, addPrimaryNetworkValidator.Initialize(txs.Codec)) - // Check whether weight/bls keys diffs are duly stored - checkDiffs func(*require.Assertions, *state, *Staker, uint64) - }{ - "add current validator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - var ( - startTime = time.Now().Unix() - endTime = time.Now().Add(14 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - End: uint64(endTime), - Wght: 1234, - } - validatorReward uint64 = 5678 - ) + primaryNetworkPendingValidatorStaker, err := NewPendingStaker( + addPrimaryNetworkValidator.ID(), + unsignedAddPrimaryNetworkValidator, + ) + require.NoError(t, err) - utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utx} - r.NoError(addPermValTx.Initialize(txs.Codec)) + primaryNetworkCurrentValidatorStaker, err := NewCurrentStaker( + addPrimaryNetworkValidator.ID(), + unsignedAddPrimaryNetworkValidator, + primaryValidatorStartTime, + primaryValidatorReward, + ) + require.NoError(t, err) - staker, err := NewCurrentStaker( - addPermValTx.ID(), - utx, - time.Unix(startTime, 0), - validatorReward, - ) - r.NoError(err) + unsignedAddPrimaryNetworkDelegator := createPermissionlessDelegatorTx(constants.PrimaryNetworkID, primaryDelegatorData) + addPrimaryNetworkDelegator := &txs.Tx{Unsigned: unsignedAddPrimaryNetworkDelegator} + require.NoError(t, addPrimaryNetworkDelegator.Initialize(txs.Codec)) - r.NoError(s.PutCurrentValidator(staker)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - return staker - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - retrievedStaker, err := s.GetCurrentValidator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.Equal(staker, retrievedStaker) - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - valsMap := s.validators.GetMap(staker.SubnetID) - r.Contains(valsMap, staker.NodeID) - r.Equal( - &validators.GetValidatorOutput{ - NodeID: staker.NodeID, - PublicKey: staker.PublicKey, - Weight: staker.Weight, - }, - valsMap[staker.NodeID], - ) - }, - checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { - upDuration, lastUpdated, err := s.GetUptime(staker.NodeID) - if staker.SubnetID != constants.PrimaryNetworkID { - // only primary network validators have uptimes - r.ErrorIs(err, database.ErrNotFound) - } else { - r.NoError(err) - r.Equal(upDuration, time.Duration(0)) - r.Equal(lastUpdated, staker.StartTime) - } - }, - checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { - weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.NoError(err) - weightDiff, err := unmarshalWeightDiff(weightDiffBytes) - r.NoError(err) - r.Equal(&ValidatorWeightDiff{ - Decrease: false, - Amount: staker.Weight, - }, weightDiff) - - blsDiffBytes, err := s.validatorPublicKeyDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - if staker.SubnetID == constants.PrimaryNetworkID { - r.NoError(err) - r.Nil(blsDiffBytes) - } else { - r.ErrorIs(err, database.ErrNotFound) - } - }, - }, - "add current delegator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - // insert the delegator and its validator - var ( - valStartTime = time.Now().Truncate(time.Second).Unix() - delStartTime = time.Unix(valStartTime, 0).Add(time.Hour).Unix() - delEndTime = time.Unix(delStartTime, 0).Add(30 * 24 * time.Hour).Unix() - valEndTime = time.Unix(valStartTime, 0).Add(365 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - End: uint64(valEndTime), - Wght: 1234, - } - validatorReward uint64 = 5678 - - delegatorData = txs.Validator{ - NodeID: validatorsData.NodeID, - End: uint64(delEndTime), - Wght: validatorsData.Wght / 2, - } - delegatorReward uint64 = 5432 - ) + primaryNetworkPendingDelegatorStaker, err := NewPendingStaker( + addPrimaryNetworkDelegator.ID(), + unsignedAddPrimaryNetworkDelegator, + ) + require.NoError(t, err) - utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utxVal} - r.NoError(addPermValTx.Initialize(txs.Codec)) + primaryNetworkCurrentDelegatorStaker, err := NewCurrentStaker( + addPrimaryNetworkDelegator.ID(), + unsignedAddPrimaryNetworkDelegator, + primaryDelegatorStartTime, + primaryDelegatorReward, + ) + require.NoError(t, err) - val, err := NewCurrentStaker( - addPermValTx.ID(), - utxVal, - time.Unix(valStartTime, 0), - validatorReward, - ) - r.NoError(err) + tests := map[string]struct { + initialStakers []*Staker + initialTxs []*txs.Tx - utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) - addPermDelTx := &txs.Tx{Unsigned: utxDel} - r.NoError(addPermDelTx.Initialize(txs.Codec)) + // Staker to insert or remove + staker *Staker + tx *txs.Tx // If tx is nil, the staker is being removed - del, err := NewCurrentStaker( - addPermDelTx.ID(), - utxDel, - time.Unix(delStartTime, 0), - delegatorReward, - ) - r.NoError(err) + // Check that the staker is duly stored/removed in P-chain state + expectedCurrentValidator *Staker + expectedPendingValidator *Staker + expectedCurrentDelegators []*Staker + expectedPendingDelegators []*Staker - r.NoError(s.PutCurrentValidator(val)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) + // Check that the validator entry has been set correctly in the + // in-memory validator set. + expectedValidatorSetOutput *validators.GetValidatorOutput - s.PutCurrentDelegator(del) - s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - return del - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - delIt, err := s.GetCurrentDelegatorIterator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.True(delIt.Next()) - retrievedDelegator := delIt.Value() - r.False(delIt.Next()) - delIt.Release() - r.Equal(staker, retrievedDelegator) - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - val, err := s.GetCurrentValidator(staker.SubnetID, staker.NodeID) - r.NoError(err) - - valsMap := s.validators.GetMap(staker.SubnetID) - r.Contains(valsMap, staker.NodeID) - valOut := valsMap[staker.NodeID] - r.Equal(valOut.NodeID, staker.NodeID) - r.Equal(valOut.Weight, val.Weight+staker.Weight) - }, - checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, - checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { - // validator's weight must increase of delegator's weight amount - weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.NoError(err) - weightDiff, err := unmarshalWeightDiff(weightDiffBytes) - r.NoError(err) - r.Equal(&ValidatorWeightDiff{ - Decrease: false, - Amount: staker.Weight, - }, weightDiff) + // Check whether weight/bls keys diffs are duly stored + expectedWeightDiff *ValidatorWeightDiff + expectedPublicKeyDiff maybe.Maybe[*bls.PublicKey] + }{ + "add current primary network validator": { + staker: primaryNetworkCurrentValidatorStaker, + tx: addPrimaryNetworkValidator, + expectedCurrentValidator: primaryNetworkCurrentValidatorStaker, + expectedValidatorSetOutput: &validators.GetValidatorOutput{ + NodeID: primaryNetworkCurrentValidatorStaker.NodeID, + PublicKey: primaryNetworkCurrentValidatorStaker.PublicKey, + Weight: primaryNetworkCurrentValidatorStaker.Weight, + }, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: false, + Amount: primaryNetworkCurrentValidatorStaker.Weight, }, + expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](nil), }, - "add pending validator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - var ( - startTime = time.Now().Unix() - endTime = time.Now().Add(14 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - Start: uint64(startTime), - End: uint64(endTime), - Wght: 1234, - } - ) - - utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utx} - r.NoError(addPermValTx.Initialize(txs.Codec)) - - staker, err := NewPendingStaker( - addPermValTx.ID(), - utx, - ) - r.NoError(err) - - r.NoError(s.PutPendingValidator(staker)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - return staker - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - retrievedStaker, err := s.GetPendingValidator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.Equal(staker, retrievedStaker) - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - // pending validators are not showed in validators set - valsMap := s.validators.GetMap(staker.SubnetID) - r.NotContains(valsMap, staker.NodeID) - }, - checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { - // pending validators uptime is not tracked - _, _, err := s.GetUptime(staker.NodeID) - r.ErrorIs(err, database.ErrNotFound) + "add current primary network delegator": { + initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: primaryNetworkCurrentDelegatorStaker, + tx: addPrimaryNetworkDelegator, + expectedCurrentValidator: primaryNetworkCurrentValidatorStaker, + expectedCurrentDelegators: []*Staker{primaryNetworkCurrentDelegatorStaker}, + expectedValidatorSetOutput: &validators.GetValidatorOutput{ + NodeID: primaryNetworkCurrentDelegatorStaker.NodeID, + PublicKey: primaryNetworkCurrentValidatorStaker.PublicKey, + Weight: primaryNetworkCurrentDelegatorStaker.Weight + primaryNetworkCurrentValidatorStaker.Weight, + }, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: false, + Amount: primaryNetworkCurrentDelegatorStaker.Weight, }, - checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { - // pending validators weight diff and bls diffs are not stored - _, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.ErrorIs(err, database.ErrNotFound) - - _, err = s.validatorPublicKeyDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.ErrorIs(err, database.ErrNotFound) + }, + "add pending primary network validator": { + staker: primaryNetworkPendingValidatorStaker, + tx: addPrimaryNetworkValidator, + expectedPendingValidator: primaryNetworkPendingValidatorStaker, + }, + "add pending primary network delegator": { + initialStakers: []*Staker{primaryNetworkPendingValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: primaryNetworkPendingDelegatorStaker, + tx: addPrimaryNetworkDelegator, + expectedPendingValidator: primaryNetworkPendingValidatorStaker, + expectedPendingDelegators: []*Staker{primaryNetworkPendingDelegatorStaker}, + }, + "delete current primary network validator": { + initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: primaryNetworkCurrentValidatorStaker, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: true, + Amount: primaryNetworkCurrentValidatorStaker.Weight, }, + expectedPublicKeyDiff: maybe.Some(primaryNetworkCurrentValidatorStaker.PublicKey), }, - "add pending delegator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - // insert the delegator and its validator - var ( - valStartTime = time.Now().Truncate(time.Second).Unix() - delStartTime = time.Unix(valStartTime, 0).Add(time.Hour).Unix() - delEndTime = time.Unix(delStartTime, 0).Add(30 * 24 * time.Hour).Unix() - valEndTime = time.Unix(valStartTime, 0).Add(365 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - Start: uint64(valStartTime), - End: uint64(valEndTime), - Wght: 1234, - } - - delegatorData = txs.Validator{ - NodeID: validatorsData.NodeID, - Start: uint64(delStartTime), - End: uint64(delEndTime), - Wght: validatorsData.Wght / 2, - } - ) - - utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utxVal} - r.NoError(addPermValTx.Initialize(txs.Codec)) - - val, err := NewPendingStaker(addPermValTx.ID(), utxVal) - r.NoError(err) - - utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) - addPermDelTx := &txs.Tx{Unsigned: utxDel} - r.NoError(addPermDelTx.Initialize(txs.Codec)) - - del, err := NewPendingStaker(addPermDelTx.ID(), utxDel) - r.NoError(err) - - r.NoError(s.PutPendingValidator(val)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - - s.PutPendingDelegator(del) - s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - - return del + "delete current primary network delegator": { + initialStakers: []*Staker{ + primaryNetworkCurrentValidatorStaker, + primaryNetworkCurrentDelegatorStaker, + }, + initialTxs: []*txs.Tx{ + addPrimaryNetworkValidator, + addPrimaryNetworkDelegator, + }, + staker: primaryNetworkCurrentDelegatorStaker, + expectedCurrentValidator: primaryNetworkCurrentValidatorStaker, + expectedValidatorSetOutput: &validators.GetValidatorOutput{ + NodeID: primaryNetworkCurrentValidatorStaker.NodeID, + PublicKey: primaryNetworkCurrentValidatorStaker.PublicKey, + Weight: primaryNetworkCurrentValidatorStaker.Weight, + }, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: true, + Amount: primaryNetworkCurrentDelegatorStaker.Weight, }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - delIt, err := s.GetPendingDelegatorIterator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.True(delIt.Next()) - retrievedDelegator := delIt.Value() - r.False(delIt.Next()) - delIt.Release() - r.Equal(staker, retrievedDelegator) + }, + "delete pending primary network validator": { + initialStakers: []*Staker{primaryNetworkPendingValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: primaryNetworkPendingValidatorStaker, + }, + "delete pending primary network delegator": { + initialStakers: []*Staker{ + primaryNetworkPendingValidatorStaker, + primaryNetworkPendingDelegatorStaker, }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - valsMap := s.validators.GetMap(staker.SubnetID) - r.NotContains(valsMap, staker.NodeID) + initialTxs: []*txs.Tx{ + addPrimaryNetworkValidator, + addPrimaryNetworkDelegator, }, - checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, - checkDiffs: func(*require.Assertions, *state, *Staker, uint64) {}, + staker: primaryNetworkPendingDelegatorStaker, + expectedPendingValidator: primaryNetworkPendingValidatorStaker, }, - "delete current validator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - // add them remove the validator - var ( - startTime = time.Now().Unix() - endTime = time.Now().Add(14 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - End: uint64(endTime), - Wght: 1234, - } - validatorReward uint64 = 5678 - ) + } - utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utx} - r.NoError(addPermValTx.Initialize(txs.Codec)) + for name, test := range tests { + t.Run(name, func(t *testing.T) { + require := require.New(t) - staker, err := NewCurrentStaker( - addPermValTx.ID(), - utx, - time.Unix(startTime, 0), - validatorReward, - ) - r.NoError(err) + db := memdb.New() + state := newTestState(t, db) + + // create and store the initial stakers + for _, staker := range test.initialStakers { + switch { + case staker.Priority.IsCurrentValidator(): + require.NoError(state.PutCurrentValidator(staker)) + case staker.Priority.IsPendingValidator(): + require.NoError(state.PutPendingValidator(staker)) + case staker.Priority.IsCurrentDelegator(): + state.PutCurrentDelegator(staker) + case staker.Priority.IsPendingDelegator(): + state.PutPendingDelegator(staker) + } + } + for _, tx := range test.initialTxs { + state.AddTx(tx, status.Committed) + } - r.NoError(s.PutCurrentValidator(staker)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) + state.SetHeight(0) + require.NoError(state.Commit()) - s.DeleteCurrentValidator(staker) - r.NoError(s.Commit()) - return staker - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - _, err := s.GetCurrentValidator(staker.SubnetID, staker.NodeID) - r.ErrorIs(err, database.ErrNotFound) - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - // deleted validators are not showed in the validators set anymore - valsMap := s.validators.GetMap(staker.SubnetID) - r.NotContains(valsMap, staker.NodeID) - }, - checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { - // uptimes of delete validators are dropped - _, _, err := s.GetUptime(staker.NodeID) - r.ErrorIs(err, database.ErrNotFound) - }, - checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { - weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.NoError(err) - weightDiff, err := unmarshalWeightDiff(weightDiffBytes) - r.NoError(err) - r.Equal(&ValidatorWeightDiff{ - Decrease: true, - Amount: staker.Weight, - }, weightDiff) - - blsDiffBytes, err := s.validatorPublicKeyDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - if staker.SubnetID == constants.PrimaryNetworkID { - r.NoError(err) - r.Equal(bls.PublicKeyFromValidUncompressedBytes(blsDiffBytes), staker.PublicKey) + // create and store the staker under test + switch { + case test.staker.Priority.IsCurrentValidator(): + if test.tx != nil { + require.NoError(state.PutCurrentValidator(test.staker)) } else { - r.ErrorIs(err, database.ErrNotFound) + state.DeleteCurrentValidator(test.staker) } - }, - }, - "delete current delegator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - // insert validator and delegator, then remove the delegator - var ( - valStartTime = time.Now().Truncate(time.Second).Unix() - delStartTime = time.Unix(valStartTime, 0).Add(time.Hour).Unix() - delEndTime = time.Unix(delStartTime, 0).Add(30 * 24 * time.Hour).Unix() - valEndTime = time.Unix(valStartTime, 0).Add(365 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - End: uint64(valEndTime), - Wght: 1234, - } - validatorReward uint64 = 5678 - - delegatorData = txs.Validator{ - NodeID: validatorsData.NodeID, - End: uint64(delEndTime), - Wght: validatorsData.Wght / 2, - } - delegatorReward uint64 = 5432 - ) - - utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utxVal} - r.NoError(addPermValTx.Initialize(txs.Codec)) - - val, err := NewCurrentStaker( - addPermValTx.ID(), - utxVal, - time.Unix(valStartTime, 0), - validatorReward, - ) - r.NoError(err) - - utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) - addPermDelTx := &txs.Tx{Unsigned: utxDel} - r.NoError(addPermDelTx.Initialize(txs.Codec)) + case test.staker.Priority.IsPendingValidator(): + if test.tx != nil { + require.NoError(state.PutPendingValidator(test.staker)) + } else { + state.DeletePendingValidator(test.staker) + } + case test.staker.Priority.IsCurrentDelegator(): + if test.tx != nil { + state.PutCurrentDelegator(test.staker) + } else { + state.DeleteCurrentDelegator(test.staker) + } + case test.staker.Priority.IsPendingDelegator(): + if test.tx != nil { + state.PutPendingDelegator(test.staker) + } else { + state.DeletePendingDelegator(test.staker) + } + } + if test.tx != nil { + state.AddTx(test.tx, status.Committed) + } - del, err := NewCurrentStaker( - addPermDelTx.ID(), - utxDel, - time.Unix(delStartTime, 0), - delegatorReward, - ) - r.NoError(err) + state.SetHeight(1) + require.NoError(state.Commit()) - r.NoError(s.PutCurrentValidator(val)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker + // Perform the checks once immediately after committing to the + // state, and once after re-loading the state from disk. + for i := 0; i < 2; i++ { + currentValidator, err := state.GetCurrentValidator(test.staker.SubnetID, test.staker.NodeID) + if test.expectedCurrentValidator == nil { + require.ErrorIs(err, database.ErrNotFound) - s.PutCurrentDelegator(del) - s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) + // Only current validators should have uptimes + _, _, err := state.GetUptime(test.staker.NodeID) + require.ErrorIs(err, database.ErrNotFound) + } else { + require.NoError(err) + require.Equal(test.expectedCurrentValidator, currentValidator) + + // Current validators should also have uptimes + upDuration, lastUpdated, err := state.GetUptime(currentValidator.NodeID) + require.NoError(err) + require.Zero(upDuration) + require.Equal(currentValidator.StartTime, lastUpdated) + } - s.DeleteCurrentDelegator(del) - r.NoError(s.Commit()) + pendingValidator, err := state.GetPendingValidator(test.staker.SubnetID, test.staker.NodeID) + if test.expectedPendingValidator == nil { + require.ErrorIs(err, database.ErrNotFound) + } else { + require.NoError(err) + require.Equal(test.expectedPendingValidator, pendingValidator) + } - return del - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - delIt, err := s.GetCurrentDelegatorIterator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.False(delIt.Next()) - delIt.Release() - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - val, err := s.GetCurrentValidator(staker.SubnetID, staker.NodeID) - r.NoError(err) - - valsMap := s.validators.GetMap(staker.SubnetID) - r.Contains(valsMap, staker.NodeID) - valOut := valsMap[staker.NodeID] - r.Equal(valOut.NodeID, staker.NodeID) - r.Equal(valOut.Weight, val.Weight) - }, - checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, - checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { - // validator's weight must decrease of delegator's weight amount - weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.NoError(err) - weightDiff, err := unmarshalWeightDiff(weightDiffBytes) - r.NoError(err) - r.Equal(&ValidatorWeightDiff{ - Decrease: true, - Amount: staker.Weight, - }, weightDiff) - }, - }, - "delete pending validator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - var ( - startTime = time.Now().Unix() - endTime = time.Now().Add(14 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - Start: uint64(startTime), - End: uint64(endTime), - Wght: 1234, - } + it, err := state.GetCurrentDelegatorIterator(test.staker.SubnetID, test.staker.NodeID) + require.NoError(err) + require.Equal( + test.expectedCurrentDelegators, + iterator.ToSlice(it), ) - utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utx} - r.NoError(addPermValTx.Initialize(txs.Codec)) - - staker, err := NewPendingStaker( - addPermValTx.ID(), - utx, + it, err = state.GetPendingDelegatorIterator(test.staker.SubnetID, test.staker.NodeID) + require.NoError(err) + require.Equal( + test.expectedPendingDelegators, + iterator.ToSlice(it), ) - r.NoError(err) - - r.NoError(s.PutPendingValidator(staker)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - s.DeletePendingValidator(staker) - r.NoError(s.Commit()) - - return staker - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - _, err := s.GetPendingValidator(staker.SubnetID, staker.NodeID) - r.ErrorIs(err, database.ErrNotFound) - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - valsMap := s.validators.GetMap(staker.SubnetID) - r.NotContains(valsMap, staker.NodeID) - }, - checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { - _, _, err := s.GetUptime(staker.NodeID) - r.ErrorIs(err, database.ErrNotFound) - }, - checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { - _, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.ErrorIs(err, database.ErrNotFound) - - _, err = s.validatorPublicKeyDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.ErrorIs(err, database.ErrNotFound) - }, - }, - "delete pending delegator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - // insert validator and delegator the remove the validator - var ( - valStartTime = time.Now().Truncate(time.Second).Unix() - delStartTime = time.Unix(valStartTime, 0).Add(time.Hour).Unix() - delEndTime = time.Unix(delStartTime, 0).Add(30 * 24 * time.Hour).Unix() - valEndTime = time.Unix(valStartTime, 0).Add(365 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - Start: uint64(valStartTime), - End: uint64(valEndTime), - Wght: 1234, - } - - delegatorData = txs.Validator{ - NodeID: validatorsData.NodeID, - Start: uint64(delStartTime), - End: uint64(delEndTime), - Wght: validatorsData.Wght / 2, - } + require.Equal( + test.expectedValidatorSetOutput, + state.validators.GetMap(test.staker.SubnetID)[test.staker.NodeID], ) - utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utxVal} - r.NoError(addPermValTx.Initialize(txs.Codec)) - - val, err := NewPendingStaker(addPermValTx.ID(), utxVal) - r.NoError(err) - - utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) - addPermDelTx := &txs.Tx{Unsigned: utxDel} - r.NoError(addPermDelTx.Initialize(txs.Codec)) - - del, err := NewPendingStaker(addPermDelTx.ID(), utxDel) - r.NoError(err) - - r.NoError(s.PutPendingValidator(val)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - - s.PutPendingDelegator(del) - s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - - s.DeletePendingDelegator(del) - r.NoError(s.Commit()) - return del - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - delIt, err := s.GetPendingDelegatorIterator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.False(delIt.Next()) - delIt.Release() - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - valsMap := s.validators.GetMap(staker.SubnetID) - r.NotContains(valsMap, staker.NodeID) - }, - checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, - checkDiffs: func(*require.Assertions, *state, *Staker, uint64) {}, - }, - } - - subnetIDs := []ids.ID{constants.PrimaryNetworkID, ids.GenerateTestID()} - for _, subnetID := range subnetIDs { - for name, test := range tests { - t.Run(fmt.Sprintf("%s - subnetID %s", name, subnetID), func(t *testing.T) { - require := require.New(t) - - db := memdb.New() - state := newTestState(t, db) - - // create and store the staker - staker := test.storeStaker(require, subnetID, state) + diffKey := marshalDiffKey(test.staker.SubnetID, 1, test.staker.NodeID) + weightDiffBytes, err := state.validatorWeightDiffsDB.Get(diffKey) + if test.expectedWeightDiff == nil { + require.ErrorIs(err, database.ErrNotFound) + } else { + require.NoError(err) - // check all relevant data are stored - test.checkStakerInState(require, state, staker) - test.checkValidatorsSet(require, state, staker) - test.checkValidatorUptimes(require, state, staker) - test.checkDiffs(require, state, staker, 0 /*height*/) + weightDiff, err := unmarshalWeightDiff(weightDiffBytes) + require.NoError(err) + require.Equal(test.expectedWeightDiff, weightDiff) + } - // rebuild the state - rebuiltState := newTestState(t, db) + publicKeyDiffBytes, err := state.validatorPublicKeyDiffsDB.Get(diffKey) + if test.expectedPublicKeyDiff.IsNothing() { + require.ErrorIs(err, database.ErrNotFound) + } else if expectedPublicKeyDiff := test.expectedPublicKeyDiff.Value(); expectedPublicKeyDiff == nil { + require.NoError(err) + require.Empty(publicKeyDiffBytes) + } else { + require.NoError(err) + require.Equal(expectedPublicKeyDiff, bls.PublicKeyFromValidUncompressedBytes(publicKeyDiffBytes)) + } - // check again that all relevant data are still available in rebuilt state - test.checkStakerInState(require, rebuiltState, staker) - test.checkValidatorsSet(require, rebuiltState, staker) - test.checkValidatorUptimes(require, rebuiltState, staker) - test.checkDiffs(require, rebuiltState, staker, 0 /*height*/) - }) - } + // re-load the state from disk + state = newTestState(t, db) + } + }) } } -func createPermissionlessValidatorTx(r *require.Assertions, subnetID ids.ID, validatorsData txs.Validator) *txs.AddPermissionlessValidatorTx { +func createPermissionlessValidatorTx(t testing.TB, subnetID ids.ID, validatorsData txs.Validator) *txs.AddPermissionlessValidatorTx { var sig signer.Signer = &signer.Empty{} if subnetID == constants.PrimaryNetworkID { sk, err := bls.NewSecretKey() - r.NoError(err) + require.NoError(t, err) sig = signer.NewProofOfPossession(sk) } From 803d0c4d777aa587949df49f40b21b17cacafeed Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 22 Oct 2024 07:07:59 -0400 Subject: [PATCH 05/11] nit --- vms/platformvm/state/state_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 419586f7871c..29d37c15a341 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -428,7 +428,7 @@ func TestPersistStakers(t *testing.T) { require.Equal(expectedPublicKeyDiff, bls.PublicKeyFromValidUncompressedBytes(publicKeyDiffBytes)) } - // re-load the state from disk + // re-load the state from disk for the second iteration state = newTestState(t, db) } }) From a2c777337e26d7e63426644d61b00c8e0d21a81c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Tue, 22 Oct 2024 08:57:04 -0400 Subject: [PATCH 06/11] Update test and populate public keys during startup --- vms/platformvm/state/state.go | 21 ++++++--- vms/platformvm/state/state_test.go | 72 ++++++++++++++++++++++++++---- 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index bed7b4746fbe..8a81931f1111 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -1718,19 +1718,28 @@ func (s *state) loadPendingValidators() error { // Invariant: initValidatorSets requires loadCurrentValidators to have already // been called. func (s *state) initValidatorSets() error { - for subnetID, validators := range s.currentStakers.validators { + primaryNetworkValidators := s.currentStakers.validators[constants.PrimaryNetworkID] + for subnetID, subnetValidators := range s.currentStakers.validators { if s.validators.Count(subnetID) != 0 { // Enforce the invariant that the validator set is empty here. return fmt.Errorf("%w: %s", errValidatorSetAlreadyPopulated, subnetID) } - for nodeID, validator := range validators { - validatorStaker := validator.validator - if err := s.validators.AddStaker(subnetID, nodeID, validatorStaker.PublicKey, validatorStaker.TxID, validatorStaker.Weight); err != nil { + for nodeID, subnetValidator := range subnetValidators { + primaryValidator, ok := primaryNetworkValidators[nodeID] + if !ok { + return fmt.Errorf("%w: %s", errMissingPrimaryNetworkValidator, nodeID) + } + + var ( + primaryStaker = primaryValidator.validator + subnetStaker = subnetValidator.validator + ) + if err := s.validators.AddStaker(subnetID, nodeID, primaryStaker.PublicKey, subnetStaker.TxID, subnetStaker.Weight); err != nil { return err } - delegatorIterator := iterator.FromTree(validator.delegators) + delegatorIterator := iterator.FromTree(subnetValidator.delegators) for delegatorIterator.Next() { delegatorStaker := delegatorIterator.Value() if err := s.validators.AddWeight(subnetID, nodeID, delegatorStaker.Weight); err != nil { @@ -2106,7 +2115,7 @@ func (s *state) writeCurrentStakersSubnetDiff( // This should never happen as the primary network diffs are // written last and subnet validator times must be a subset // of the primary network validator times. - return errMissingPrimaryNetworkValidator + return fmt.Errorf("%w: %s", errMissingPrimaryNetworkValidator, nodeID) } } diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 29d37c15a341..b725761bab9a 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -117,10 +117,10 @@ func TestPersistStakers(t *testing.T) { primaryValidatorDuration = 28 * 24 * time.Hour primaryDelegatorDuration = 14 * 24 * time.Hour subnetValidatorDuration = 21 * 24 * time.Hour - subnetDelegatorDuration = 14 * 24 * time.Hour primaryValidatorReward = iota primaryDelegatorReward + subnetValidatorReward ) var ( primaryValidatorStartTime = time.Now().Truncate(time.Second) @@ -131,6 +131,10 @@ func TestPersistStakers(t *testing.T) { primaryDelegatorEndTime = primaryDelegatorStartTime.Add(primaryDelegatorDuration) primaryDelegatorEndTimeUnix = uint64(primaryDelegatorEndTime.Unix()) + subnetValidatorStartTime = primaryValidatorStartTime + subnetValidatorEndTime = subnetValidatorStartTime.Add(subnetValidatorDuration) + subnetValidatorEndTimeUnix = uint64(subnetValidatorEndTime.Unix()) + primaryValidatorData = txs.Validator{ NodeID: ids.GenerateTestNodeID(), End: primaryValidatorEndTimeUnix, @@ -141,6 +145,13 @@ func TestPersistStakers(t *testing.T) { End: primaryDelegatorEndTimeUnix, Wght: 6789, } + subnetValidatorData = txs.Validator{ + NodeID: primaryValidatorData.NodeID, + End: subnetValidatorEndTimeUnix, + Wght: 9876, + } + + subnetID = ids.GenerateTestID() ) unsignedAddPrimaryNetworkValidator := createPermissionlessValidatorTx(t, constants.PrimaryNetworkID, primaryValidatorData) @@ -179,6 +190,18 @@ func TestPersistStakers(t *testing.T) { ) require.NoError(t, err) + unsignedAddSubnetValidator := createPermissionlessValidatorTx(t, subnetID, subnetValidatorData) + addSubnetValidator := &txs.Tx{Unsigned: unsignedAddSubnetValidator} + require.NoError(t, addSubnetValidator.Initialize(txs.Codec)) + + subnetCurrentValidatorStaker, err := NewCurrentStaker( + addSubnetValidator.ID(), + unsignedAddSubnetValidator, + subnetValidatorStartTime, + subnetValidatorReward, + ) + require.NoError(t, err) + tests := map[string]struct { initialStakers []*Staker initialTxs []*txs.Tx @@ -246,6 +269,23 @@ func TestPersistStakers(t *testing.T) { expectedPendingValidator: primaryNetworkPendingValidatorStaker, expectedPendingDelegators: []*Staker{primaryNetworkPendingDelegatorStaker}, }, + "add current subnet validator": { + initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: subnetCurrentValidatorStaker, + tx: addSubnetValidator, + expectedCurrentValidator: subnetCurrentValidatorStaker, + expectedValidatorSetOutput: &validators.GetValidatorOutput{ + NodeID: subnetCurrentValidatorStaker.NodeID, + PublicKey: primaryNetworkCurrentValidatorStaker.PublicKey, + Weight: subnetCurrentValidatorStaker.Weight, + }, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: false, + Amount: subnetCurrentValidatorStaker.Weight, + }, + expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](nil), + }, "delete current primary network validator": { initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker}, initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, @@ -294,6 +334,16 @@ func TestPersistStakers(t *testing.T) { staker: primaryNetworkPendingDelegatorStaker, expectedPendingValidator: primaryNetworkPendingValidatorStaker, }, + "delete current subnet validator": { + initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker, subnetCurrentValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator, addSubnetValidator}, + staker: subnetCurrentValidatorStaker, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: true, + Amount: subnetCurrentValidatorStaker.Weight, + }, + expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](primaryNetworkCurrentValidatorStaker.PublicKey), + }, } for name, test := range tests { @@ -364,18 +414,22 @@ func TestPersistStakers(t *testing.T) { if test.expectedCurrentValidator == nil { require.ErrorIs(err, database.ErrNotFound) - // Only current validators should have uptimes - _, _, err := state.GetUptime(test.staker.NodeID) - require.ErrorIs(err, database.ErrNotFound) + if test.staker.SubnetID == constants.PrimaryNetworkID { + // Uptimes are only considered for primary network validators + _, _, err := state.GetUptime(test.staker.NodeID) + require.ErrorIs(err, database.ErrNotFound) + } } else { require.NoError(err) require.Equal(test.expectedCurrentValidator, currentValidator) - // Current validators should also have uptimes - upDuration, lastUpdated, err := state.GetUptime(currentValidator.NodeID) - require.NoError(err) - require.Zero(upDuration) - require.Equal(currentValidator.StartTime, lastUpdated) + if test.staker.SubnetID == constants.PrimaryNetworkID { + // Uptimes are only considered for primary network validators + upDuration, lastUpdated, err := state.GetUptime(currentValidator.NodeID) + require.NoError(err) + require.Zero(upDuration) + require.Equal(currentValidator.StartTime, lastUpdated) + } } pendingValidator, err := state.GetPendingValidator(test.staker.SubnetID, test.staker.NodeID) From 95c42a16942d6ba0a8a67a0ebd29ed7129310efa Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 23 Oct 2024 11:39:45 -0400 Subject: [PATCH 07/11] comment --- vms/platformvm/state/state.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 8a81931f1111..725b9e010c40 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -1726,6 +1726,8 @@ func (s *state) initValidatorSets() error { } for nodeID, subnetValidator := range subnetValidators { + // The subnet validator's Public Key is inherited from the + // corresponding primary network validator. primaryValidator, ok := primaryNetworkValidators[nodeID] if !ok { return fmt.Errorf("%w: %s", errMissingPrimaryNetworkValidator, nodeID) From 21cdc32e2b6508a09ff390fdee7f7b5bf90620fe Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 24 Oct 2024 10:28:07 -0400 Subject: [PATCH 08/11] Update P-chain state staker tests --- vms/platformvm/state/state_test.go | 1203 ++++++++++++---------------- 1 file changed, 514 insertions(+), 689 deletions(-) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 6204540bd615..e9096af13a1c 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -5,7 +5,6 @@ package state import ( "context" - "fmt" "math" "math/rand" "sync" @@ -28,6 +27,8 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/iterator" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/maybe" + "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/avalanchego/vms/components/avax" @@ -109,620 +110,387 @@ func TestStateSyncGenesis(t *testing.T) { ) } -// Whenever we store a staker, a whole bunch a data structures are updated -// This test is meant to capture which updates are carried out -func TestPersistStakers(t *testing.T) { - tests := map[string]struct { - // Insert or delete a staker to state and store it - storeStaker func(*require.Assertions, ids.ID /*=subnetID*/, *state) *Staker +// Whenever we add or remove a staker, a number of on-disk data structures +// should be updated. +// +// This test verifies that the on-disk data structures are updated as expected. +func TestState_writeStakers(t *testing.T) { + const ( + primaryValidatorDuration = 28 * 24 * time.Hour + primaryDelegatorDuration = 14 * 24 * time.Hour + subnetValidatorDuration = 21 * 24 * time.Hour + + primaryValidatorReward = iota + primaryDelegatorReward + subnetValidatorReward + ) + var ( + primaryValidatorStartTime = time.Now().Truncate(time.Second) + primaryValidatorEndTime = primaryValidatorStartTime.Add(primaryValidatorDuration) + primaryValidatorEndTimeUnix = uint64(primaryValidatorEndTime.Unix()) + + primaryDelegatorStartTime = primaryValidatorStartTime + primaryDelegatorEndTime = primaryDelegatorStartTime.Add(primaryDelegatorDuration) + primaryDelegatorEndTimeUnix = uint64(primaryDelegatorEndTime.Unix()) + + subnetValidatorStartTime = primaryValidatorStartTime + subnetValidatorEndTime = subnetValidatorStartTime.Add(subnetValidatorDuration) + subnetValidatorEndTimeUnix = uint64(subnetValidatorEndTime.Unix()) + + primaryValidatorData = txs.Validator{ + NodeID: ids.GenerateTestNodeID(), + End: primaryValidatorEndTimeUnix, + Wght: 1234, + } + primaryDelegatorData = txs.Validator{ + NodeID: primaryValidatorData.NodeID, + End: primaryDelegatorEndTimeUnix, + Wght: 6789, + } + subnetValidatorData = txs.Validator{ + NodeID: primaryValidatorData.NodeID, + End: subnetValidatorEndTimeUnix, + Wght: 9876, + } - // Check that the staker is duly stored/removed in P-chain state - checkStakerInState func(*require.Assertions, *state, *Staker) + subnetID = ids.GenerateTestID() + ) - // Check whether validators are duly reported in the validator set, - // with the right weight and showing the BLS key - checkValidatorsSet func(*require.Assertions, *state, *Staker) + unsignedAddPrimaryNetworkValidator := createPermissionlessValidatorTx(t, constants.PrimaryNetworkID, primaryValidatorData) + addPrimaryNetworkValidator := &txs.Tx{Unsigned: unsignedAddPrimaryNetworkValidator} + require.NoError(t, addPrimaryNetworkValidator.Initialize(txs.Codec)) - // Check that node duly track stakers uptimes - checkValidatorUptimes func(*require.Assertions, *state, *Staker) + primaryNetworkPendingValidatorStaker, err := NewPendingStaker( + addPrimaryNetworkValidator.ID(), + unsignedAddPrimaryNetworkValidator, + ) + require.NoError(t, err) - // Check whether weight/bls keys diffs are duly stored - checkDiffs func(*require.Assertions, *state, *Staker, uint64) - }{ - "add current validator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - var ( - startTime = time.Now().Unix() - endTime = time.Now().Add(14 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - End: uint64(endTime), - Wght: 1234, - } - validatorReward uint64 = 5678 - ) + primaryNetworkCurrentValidatorStaker, err := NewCurrentStaker( + addPrimaryNetworkValidator.ID(), + unsignedAddPrimaryNetworkValidator, + primaryValidatorStartTime, + primaryValidatorReward, + ) + require.NoError(t, err) - utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utx} - r.NoError(addPermValTx.Initialize(txs.Codec)) + unsignedAddPrimaryNetworkDelegator := createPermissionlessDelegatorTx(constants.PrimaryNetworkID, primaryDelegatorData) + addPrimaryNetworkDelegator := &txs.Tx{Unsigned: unsignedAddPrimaryNetworkDelegator} + require.NoError(t, addPrimaryNetworkDelegator.Initialize(txs.Codec)) - staker, err := NewCurrentStaker( - addPermValTx.ID(), - utx, - time.Unix(startTime, 0), - validatorReward, - ) - r.NoError(err) + primaryNetworkPendingDelegatorStaker, err := NewPendingStaker( + addPrimaryNetworkDelegator.ID(), + unsignedAddPrimaryNetworkDelegator, + ) + require.NoError(t, err) - r.NoError(s.PutCurrentValidator(staker)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - return staker - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - retrievedStaker, err := s.GetCurrentValidator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.Equal(staker, retrievedStaker) - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - valsMap := s.validators.GetMap(staker.SubnetID) - r.Contains(valsMap, staker.NodeID) - r.Equal( - &validators.GetValidatorOutput{ - NodeID: staker.NodeID, - PublicKey: staker.PublicKey, - Weight: staker.Weight, - }, - valsMap[staker.NodeID], - ) - }, - checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { - upDuration, lastUpdated, err := s.GetUptime(staker.NodeID) - if staker.SubnetID != constants.PrimaryNetworkID { - // only primary network validators have uptimes - r.ErrorIs(err, database.ErrNotFound) - } else { - r.NoError(err) - r.Equal(upDuration, time.Duration(0)) - r.Equal(lastUpdated, staker.StartTime) - } - }, - checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { - weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.NoError(err) - weightDiff, err := unmarshalWeightDiff(weightDiffBytes) - r.NoError(err) - r.Equal(&ValidatorWeightDiff{ - Decrease: false, - Amount: staker.Weight, - }, weightDiff) - - blsDiffBytes, err := s.validatorPublicKeyDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - if staker.SubnetID == constants.PrimaryNetworkID { - r.NoError(err) - r.Nil(blsDiffBytes) - } else { - r.ErrorIs(err, database.ErrNotFound) - } - }, - }, - "add current delegator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - // insert the delegator and its validator - var ( - valStartTime = time.Now().Truncate(time.Second).Unix() - delStartTime = time.Unix(valStartTime, 0).Add(time.Hour).Unix() - delEndTime = time.Unix(delStartTime, 0).Add(30 * 24 * time.Hour).Unix() - valEndTime = time.Unix(valStartTime, 0).Add(365 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - End: uint64(valEndTime), - Wght: 1234, - } - validatorReward uint64 = 5678 + primaryNetworkCurrentDelegatorStaker, err := NewCurrentStaker( + addPrimaryNetworkDelegator.ID(), + unsignedAddPrimaryNetworkDelegator, + primaryDelegatorStartTime, + primaryDelegatorReward, + ) + require.NoError(t, err) - delegatorData = txs.Validator{ - NodeID: validatorsData.NodeID, - End: uint64(delEndTime), - Wght: validatorsData.Wght / 2, - } - delegatorReward uint64 = 5432 - ) + unsignedAddSubnetValidator := createPermissionlessValidatorTx(t, subnetID, subnetValidatorData) + addSubnetValidator := &txs.Tx{Unsigned: unsignedAddSubnetValidator} + require.NoError(t, addSubnetValidator.Initialize(txs.Codec)) - utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utxVal} - r.NoError(addPermValTx.Initialize(txs.Codec)) + subnetCurrentValidatorStaker, err := NewCurrentStaker( + addSubnetValidator.ID(), + unsignedAddSubnetValidator, + subnetValidatorStartTime, + subnetValidatorReward, + ) + require.NoError(t, err) - val, err := NewCurrentStaker( - addPermValTx.ID(), - utxVal, - time.Unix(valStartTime, 0), - validatorReward, - ) - r.NoError(err) + tests := map[string]struct { + initialStakers []*Staker + initialTxs []*txs.Tx - utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) - addPermDelTx := &txs.Tx{Unsigned: utxDel} - r.NoError(addPermDelTx.Initialize(txs.Codec)) + // Staker to insert or remove + staker *Staker + addStakerTx *txs.Tx // If tx is nil, the staker is being removed - del, err := NewCurrentStaker( - addPermDelTx.ID(), - utxDel, - time.Unix(delStartTime, 0), - delegatorReward, - ) - r.NoError(err) + // Check that the staker is duly stored/removed in P-chain state + expectedCurrentValidator *Staker + expectedPendingValidator *Staker + expectedCurrentDelegators []*Staker + expectedPendingDelegators []*Staker - r.NoError(s.PutCurrentValidator(val)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) + // Check that the validator entry has been set correctly in the + // in-memory validator set. + expectedValidatorSetOutput *validators.GetValidatorOutput - s.PutCurrentDelegator(del) - s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - return del - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - delIt, err := s.GetCurrentDelegatorIterator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.True(delIt.Next()) - retrievedDelegator := delIt.Value() - r.False(delIt.Next()) - delIt.Release() - r.Equal(staker, retrievedDelegator) + // Check whether weight/bls keys diffs are duly stored + expectedWeightDiff *ValidatorWeightDiff + expectedPublicKeyDiff maybe.Maybe[*bls.PublicKey] + }{ + "add current primary network validator": { + staker: primaryNetworkCurrentValidatorStaker, + addStakerTx: addPrimaryNetworkValidator, + expectedCurrentValidator: primaryNetworkCurrentValidatorStaker, + expectedValidatorSetOutput: &validators.GetValidatorOutput{ + NodeID: primaryNetworkCurrentValidatorStaker.NodeID, + PublicKey: primaryNetworkCurrentValidatorStaker.PublicKey, + Weight: primaryNetworkCurrentValidatorStaker.Weight, + }, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: false, + Amount: primaryNetworkCurrentValidatorStaker.Weight, }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - val, err := s.GetCurrentValidator(staker.SubnetID, staker.NodeID) - r.NoError(err) - - valsMap := s.validators.GetMap(staker.SubnetID) - r.Contains(valsMap, staker.NodeID) - valOut := valsMap[staker.NodeID] - r.Equal(valOut.NodeID, staker.NodeID) - r.Equal(valOut.Weight, val.Weight+staker.Weight) + expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](nil), + }, + "add current primary network delegator": { + initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: primaryNetworkCurrentDelegatorStaker, + addStakerTx: addPrimaryNetworkDelegator, + expectedCurrentValidator: primaryNetworkCurrentValidatorStaker, + expectedCurrentDelegators: []*Staker{primaryNetworkCurrentDelegatorStaker}, + expectedValidatorSetOutput: &validators.GetValidatorOutput{ + NodeID: primaryNetworkCurrentDelegatorStaker.NodeID, + PublicKey: primaryNetworkCurrentValidatorStaker.PublicKey, + Weight: primaryNetworkCurrentDelegatorStaker.Weight + primaryNetworkCurrentValidatorStaker.Weight, + }, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: false, + Amount: primaryNetworkCurrentDelegatorStaker.Weight, }, - checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, - checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { - // validator's weight must increase of delegator's weight amount - weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.NoError(err) - weightDiff, err := unmarshalWeightDiff(weightDiffBytes) - r.NoError(err) - r.Equal(&ValidatorWeightDiff{ - Decrease: false, - Amount: staker.Weight, - }, weightDiff) + }, + "add pending primary network validator": { + staker: primaryNetworkPendingValidatorStaker, + addStakerTx: addPrimaryNetworkValidator, + expectedPendingValidator: primaryNetworkPendingValidatorStaker, + }, + "add pending primary network delegator": { + initialStakers: []*Staker{primaryNetworkPendingValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: primaryNetworkPendingDelegatorStaker, + addStakerTx: addPrimaryNetworkDelegator, + expectedPendingValidator: primaryNetworkPendingValidatorStaker, + expectedPendingDelegators: []*Staker{primaryNetworkPendingDelegatorStaker}, + }, + "add current subnet validator": { + initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: subnetCurrentValidatorStaker, + addStakerTx: addSubnetValidator, + expectedCurrentValidator: subnetCurrentValidatorStaker, + expectedValidatorSetOutput: &validators.GetValidatorOutput{ + NodeID: subnetCurrentValidatorStaker.NodeID, + PublicKey: primaryNetworkCurrentValidatorStaker.PublicKey, + Weight: subnetCurrentValidatorStaker.Weight, + }, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: false, + Amount: subnetCurrentValidatorStaker.Weight, }, + expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](nil), }, - "add pending validator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - var ( - startTime = time.Now().Unix() - endTime = time.Now().Add(14 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - Start: uint64(startTime), - End: uint64(endTime), - Wght: 1234, - } - ) - - utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utx} - r.NoError(addPermValTx.Initialize(txs.Codec)) - - staker, err := NewPendingStaker( - addPermValTx.ID(), - utx, - ) - r.NoError(err) - - r.NoError(s.PutPendingValidator(staker)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - return staker + "delete current primary network validator": { + initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: primaryNetworkCurrentValidatorStaker, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: true, + Amount: primaryNetworkCurrentValidatorStaker.Weight, }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - retrievedStaker, err := s.GetPendingValidator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.Equal(staker, retrievedStaker) + expectedPublicKeyDiff: maybe.Some(primaryNetworkCurrentValidatorStaker.PublicKey), + }, + "delete current primary network delegator": { + initialStakers: []*Staker{ + primaryNetworkCurrentValidatorStaker, + primaryNetworkCurrentDelegatorStaker, + }, + initialTxs: []*txs.Tx{ + addPrimaryNetworkValidator, + addPrimaryNetworkDelegator, + }, + staker: primaryNetworkCurrentDelegatorStaker, + expectedCurrentValidator: primaryNetworkCurrentValidatorStaker, + expectedValidatorSetOutput: &validators.GetValidatorOutput{ + NodeID: primaryNetworkCurrentValidatorStaker.NodeID, + PublicKey: primaryNetworkCurrentValidatorStaker.PublicKey, + Weight: primaryNetworkCurrentValidatorStaker.Weight, + }, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: true, + Amount: primaryNetworkCurrentDelegatorStaker.Weight, }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - // pending validators are not showed in validators set - valsMap := s.validators.GetMap(staker.SubnetID) - r.NotContains(valsMap, staker.NodeID) + }, + "delete pending primary network validator": { + initialStakers: []*Staker{primaryNetworkPendingValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator}, + staker: primaryNetworkPendingValidatorStaker, + }, + "delete pending primary network delegator": { + initialStakers: []*Staker{ + primaryNetworkPendingValidatorStaker, + primaryNetworkPendingDelegatorStaker, }, - checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { - // pending validators uptime is not tracked - _, _, err := s.GetUptime(staker.NodeID) - r.ErrorIs(err, database.ErrNotFound) + initialTxs: []*txs.Tx{ + addPrimaryNetworkValidator, + addPrimaryNetworkDelegator, }, - checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { - // pending validators weight diff and bls diffs are not stored - _, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.ErrorIs(err, database.ErrNotFound) - - _, err = s.validatorPublicKeyDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.ErrorIs(err, database.ErrNotFound) + staker: primaryNetworkPendingDelegatorStaker, + expectedPendingValidator: primaryNetworkPendingValidatorStaker, + }, + "delete current subnet validator": { + initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker, subnetCurrentValidatorStaker}, + initialTxs: []*txs.Tx{addPrimaryNetworkValidator, addSubnetValidator}, + staker: subnetCurrentValidatorStaker, + expectedWeightDiff: &ValidatorWeightDiff{ + Decrease: true, + Amount: subnetCurrentValidatorStaker.Weight, }, + expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](primaryNetworkCurrentValidatorStaker.PublicKey), }, - "add pending delegator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - // insert the delegator and its validator - var ( - valStartTime = time.Now().Truncate(time.Second).Unix() - delStartTime = time.Unix(valStartTime, 0).Add(time.Hour).Unix() - delEndTime = time.Unix(delStartTime, 0).Add(30 * 24 * time.Hour).Unix() - valEndTime = time.Unix(valStartTime, 0).Add(365 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - Start: uint64(valStartTime), - End: uint64(valEndTime), - Wght: 1234, - } + } - delegatorData = txs.Validator{ - NodeID: validatorsData.NodeID, - Start: uint64(delStartTime), - End: uint64(delEndTime), - Wght: validatorsData.Wght / 2, - } - ) + for name, test := range tests { + t.Run(name, func(t *testing.T) { + require := require.New(t) - utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utxVal} - r.NoError(addPermValTx.Initialize(txs.Codec)) + db := memdb.New() + state := newTestState(t, db) + + addOrDeleteStaker := func(staker *Staker, add bool) { + if add { + switch { + case staker.Priority.IsCurrentValidator(): + require.NoError(state.PutCurrentValidator(staker)) + case staker.Priority.IsPendingValidator(): + require.NoError(state.PutPendingValidator(staker)) + case staker.Priority.IsCurrentDelegator(): + state.PutCurrentDelegator(staker) + case staker.Priority.IsPendingDelegator(): + state.PutPendingDelegator(staker) + } + } else { + switch { + case staker.Priority.IsCurrentValidator(): + state.DeleteCurrentValidator(staker) + case staker.Priority.IsPendingValidator(): + state.DeletePendingValidator(staker) + case staker.Priority.IsCurrentDelegator(): + state.DeleteCurrentDelegator(staker) + case staker.Priority.IsPendingDelegator(): + state.DeletePendingDelegator(staker) + } + } + } - val, err := NewPendingStaker(addPermValTx.ID(), utxVal) - r.NoError(err) + // create and store the initial stakers + for _, staker := range test.initialStakers { + addOrDeleteStaker(staker, true) + } + for _, tx := range test.initialTxs { + state.AddTx(tx, status.Committed) + } - utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) - addPermDelTx := &txs.Tx{Unsigned: utxDel} - r.NoError(addPermDelTx.Initialize(txs.Codec)) + state.SetHeight(0) + require.NoError(state.Commit()) - del, err := NewPendingStaker(addPermDelTx.ID(), utxDel) - r.NoError(err) + // create and store the staker under test + addOrDeleteStaker(test.staker, test.addStakerTx != nil) + if test.addStakerTx != nil { + state.AddTx(test.addStakerTx, status.Committed) + } - r.NoError(s.PutPendingValidator(val)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) + state.SetHeight(1) + require.NoError(state.Commit()) - s.PutPendingDelegator(del) - s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) + // Perform the checks once immediately after committing to the + // state, and once after re-loading the state from disk. + for i := 0; i < 2; i++ { + currentValidator, err := state.GetCurrentValidator(test.staker.SubnetID, test.staker.NodeID) + if test.expectedCurrentValidator == nil { + require.ErrorIs(err, database.ErrNotFound) - return del - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - delIt, err := s.GetPendingDelegatorIterator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.True(delIt.Next()) - retrievedDelegator := delIt.Value() - r.False(delIt.Next()) - delIt.Release() - r.Equal(staker, retrievedDelegator) - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - valsMap := s.validators.GetMap(staker.SubnetID) - r.NotContains(valsMap, staker.NodeID) - }, - checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, - checkDiffs: func(*require.Assertions, *state, *Staker, uint64) {}, - }, - "delete current validator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - // add them remove the validator - var ( - startTime = time.Now().Unix() - endTime = time.Now().Add(14 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - End: uint64(endTime), - Wght: 1234, + if test.staker.SubnetID == constants.PrimaryNetworkID { + // Uptimes are only considered for primary network validators + _, _, err := state.GetUptime(test.staker.NodeID) + require.ErrorIs(err, database.ErrNotFound) } - validatorReward uint64 = 5678 - ) - - utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utx} - r.NoError(addPermValTx.Initialize(txs.Codec)) - - staker, err := NewCurrentStaker( - addPermValTx.ID(), - utx, - time.Unix(startTime, 0), - validatorReward, - ) - r.NoError(err) - - r.NoError(s.PutCurrentValidator(staker)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) + } else { + require.NoError(err) + require.Equal(test.expectedCurrentValidator, currentValidator) + + if test.staker.SubnetID == constants.PrimaryNetworkID { + // Uptimes are only considered for primary network validators + upDuration, lastUpdated, err := state.GetUptime(currentValidator.NodeID) + require.NoError(err) + require.Zero(upDuration) + require.Equal(currentValidator.StartTime, lastUpdated) + } + } - s.DeleteCurrentValidator(staker) - r.NoError(s.Commit()) - return staker - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - _, err := s.GetCurrentValidator(staker.SubnetID, staker.NodeID) - r.ErrorIs(err, database.ErrNotFound) - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - // deleted validators are not showed in the validators set anymore - valsMap := s.validators.GetMap(staker.SubnetID) - r.NotContains(valsMap, staker.NodeID) - }, - checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { - // uptimes of delete validators are dropped - _, _, err := s.GetUptime(staker.NodeID) - r.ErrorIs(err, database.ErrNotFound) - }, - checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { - weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.NoError(err) - weightDiff, err := unmarshalWeightDiff(weightDiffBytes) - r.NoError(err) - r.Equal(&ValidatorWeightDiff{ - Decrease: true, - Amount: staker.Weight, - }, weightDiff) - - blsDiffBytes, err := s.validatorPublicKeyDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - if staker.SubnetID == constants.PrimaryNetworkID { - r.NoError(err) - r.Equal(bls.PublicKeyFromValidUncompressedBytes(blsDiffBytes), staker.PublicKey) + pendingValidator, err := state.GetPendingValidator(test.staker.SubnetID, test.staker.NodeID) + if test.expectedPendingValidator == nil { + require.ErrorIs(err, database.ErrNotFound) } else { - r.ErrorIs(err, database.ErrNotFound) + require.NoError(err) + require.Equal(test.expectedPendingValidator, pendingValidator) } - }, - }, - "delete current delegator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - // insert validator and delegator, then remove the delegator - var ( - valStartTime = time.Now().Truncate(time.Second).Unix() - delStartTime = time.Unix(valStartTime, 0).Add(time.Hour).Unix() - delEndTime = time.Unix(delStartTime, 0).Add(30 * 24 * time.Hour).Unix() - valEndTime = time.Unix(valStartTime, 0).Add(365 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - End: uint64(valEndTime), - Wght: 1234, - } - validatorReward uint64 = 5678 - delegatorData = txs.Validator{ - NodeID: validatorsData.NodeID, - End: uint64(delEndTime), - Wght: validatorsData.Wght / 2, - } - delegatorReward uint64 = 5432 + it, err := state.GetCurrentDelegatorIterator(test.staker.SubnetID, test.staker.NodeID) + require.NoError(err) + require.Equal( + test.expectedCurrentDelegators, + iterator.ToSlice(it), ) - utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utxVal} - r.NoError(addPermValTx.Initialize(txs.Codec)) - - val, err := NewCurrentStaker( - addPermValTx.ID(), - utxVal, - time.Unix(valStartTime, 0), - validatorReward, + it, err = state.GetPendingDelegatorIterator(test.staker.SubnetID, test.staker.NodeID) + require.NoError(err) + require.Equal( + test.expectedPendingDelegators, + iterator.ToSlice(it), ) - r.NoError(err) - - utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) - addPermDelTx := &txs.Tx{Unsigned: utxDel} - r.NoError(addPermDelTx.Initialize(txs.Codec)) - del, err := NewCurrentStaker( - addPermDelTx.ID(), - utxDel, - time.Unix(delStartTime, 0), - delegatorReward, + require.Equal( + test.expectedValidatorSetOutput, + state.validators.GetMap(test.staker.SubnetID)[test.staker.NodeID], ) - r.NoError(err) - - r.NoError(s.PutCurrentValidator(val)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - s.PutCurrentDelegator(del) - s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - - s.DeleteCurrentDelegator(del) - r.NoError(s.Commit()) - - return del - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - delIt, err := s.GetCurrentDelegatorIterator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.False(delIt.Next()) - delIt.Release() - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - val, err := s.GetCurrentValidator(staker.SubnetID, staker.NodeID) - r.NoError(err) - - valsMap := s.validators.GetMap(staker.SubnetID) - r.Contains(valsMap, staker.NodeID) - valOut := valsMap[staker.NodeID] - r.Equal(valOut.NodeID, staker.NodeID) - r.Equal(valOut.Weight, val.Weight) - }, - checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, - checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { - // validator's weight must decrease of delegator's weight amount - weightDiffBytes, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.NoError(err) - weightDiff, err := unmarshalWeightDiff(weightDiffBytes) - r.NoError(err) - r.Equal(&ValidatorWeightDiff{ - Decrease: true, - Amount: staker.Weight, - }, weightDiff) - }, - }, - "delete pending validator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - var ( - startTime = time.Now().Unix() - endTime = time.Now().Add(14 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - Start: uint64(startTime), - End: uint64(endTime), - Wght: 1234, - } - ) - - utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utx} - r.NoError(addPermValTx.Initialize(txs.Codec)) - - staker, err := NewPendingStaker( - addPermValTx.ID(), - utx, - ) - r.NoError(err) - - r.NoError(s.PutPendingValidator(staker)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - - s.DeletePendingValidator(staker) - r.NoError(s.Commit()) + diffKey := marshalDiffKey(test.staker.SubnetID, 1, test.staker.NodeID) + weightDiffBytes, err := state.validatorWeightDiffsDB.Get(diffKey) + if test.expectedWeightDiff == nil { + require.ErrorIs(err, database.ErrNotFound) + } else { + require.NoError(err) - return staker - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - _, err := s.GetPendingValidator(staker.SubnetID, staker.NodeID) - r.ErrorIs(err, database.ErrNotFound) - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - valsMap := s.validators.GetMap(staker.SubnetID) - r.NotContains(valsMap, staker.NodeID) - }, - checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { - _, _, err := s.GetUptime(staker.NodeID) - r.ErrorIs(err, database.ErrNotFound) - }, - checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { - _, err := s.validatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.ErrorIs(err, database.ErrNotFound) + weightDiff, err := unmarshalWeightDiff(weightDiffBytes) + require.NoError(err) + require.Equal(test.expectedWeightDiff, weightDiff) + } - _, err = s.validatorPublicKeyDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) - r.ErrorIs(err, database.ErrNotFound) - }, - }, - "delete pending delegator": { - storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { - // insert validator and delegator the remove the validator - var ( - valStartTime = time.Now().Truncate(time.Second).Unix() - delStartTime = time.Unix(valStartTime, 0).Add(time.Hour).Unix() - delEndTime = time.Unix(delStartTime, 0).Add(30 * 24 * time.Hour).Unix() - valEndTime = time.Unix(valStartTime, 0).Add(365 * 24 * time.Hour).Unix() - - validatorsData = txs.Validator{ - NodeID: ids.GenerateTestNodeID(), - Start: uint64(valStartTime), - End: uint64(valEndTime), - Wght: 1234, - } + publicKeyDiffBytes, err := state.validatorPublicKeyDiffsDB.Get(diffKey) + if test.expectedPublicKeyDiff.IsNothing() { + require.ErrorIs(err, database.ErrNotFound) + } else { + require.NoError(err) - delegatorData = txs.Validator{ - NodeID: validatorsData.NodeID, - Start: uint64(delStartTime), - End: uint64(delEndTime), - Wght: validatorsData.Wght / 2, + expectedPublicKeyDiff := test.expectedPublicKeyDiff.Value() + if expectedPublicKeyDiff != nil { + require.Equal(expectedPublicKeyDiff, bls.PublicKeyFromValidUncompressedBytes(publicKeyDiffBytes)) + } else { + require.Empty(publicKeyDiffBytes) } - ) - - utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) - addPermValTx := &txs.Tx{Unsigned: utxVal} - r.NoError(addPermValTx.Initialize(txs.Codec)) - - val, err := NewPendingStaker(addPermValTx.ID(), utxVal) - r.NoError(err) - - utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) - addPermDelTx := &txs.Tx{Unsigned: utxDel} - r.NoError(addPermDelTx.Initialize(txs.Codec)) - - del, err := NewPendingStaker(addPermDelTx.ID(), utxDel) - r.NoError(err) - - r.NoError(s.PutPendingValidator(val)) - s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker - - s.PutPendingDelegator(del) - s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker - r.NoError(s.Commit()) - - s.DeletePendingDelegator(del) - r.NoError(s.Commit()) - return del - }, - checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { - delIt, err := s.GetPendingDelegatorIterator(staker.SubnetID, staker.NodeID) - r.NoError(err) - r.False(delIt.Next()) - delIt.Release() - }, - checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { - valsMap := s.validators.GetMap(staker.SubnetID) - r.NotContains(valsMap, staker.NodeID) - }, - checkValidatorUptimes: func(*require.Assertions, *state, *Staker) {}, - checkDiffs: func(*require.Assertions, *state, *Staker, uint64) {}, - }, - } - - subnetIDs := []ids.ID{constants.PrimaryNetworkID, ids.GenerateTestID()} - for _, subnetID := range subnetIDs { - for name, test := range tests { - t.Run(fmt.Sprintf("%s - subnetID %s", name, subnetID), func(t *testing.T) { - require := require.New(t) - - db := memdb.New() - state := newTestState(t, db) - - // create and store the staker - staker := test.storeStaker(require, subnetID, state) - - // check all relevant data are stored - test.checkStakerInState(require, state, staker) - test.checkValidatorsSet(require, state, staker) - test.checkValidatorUptimes(require, state, staker) - test.checkDiffs(require, state, staker, 0 /*height*/) - - // rebuild the state - rebuiltState := newTestState(t, db) + } - // check again that all relevant data are still available in rebuilt state - test.checkStakerInState(require, rebuiltState, staker) - test.checkValidatorsSet(require, rebuiltState, staker) - test.checkValidatorUptimes(require, rebuiltState, staker) - test.checkDiffs(require, rebuiltState, staker, 0 /*height*/) - }) - } + // re-load the state from disk for the second iteration + state = newTestState(t, db) + } + }) } } -func createPermissionlessValidatorTx(r *require.Assertions, subnetID ids.ID, validatorsData txs.Validator) *txs.AddPermissionlessValidatorTx { +func createPermissionlessValidatorTx(t testing.TB, subnetID ids.ID, validatorsData txs.Validator) *txs.AddPermissionlessValidatorTx { var sig signer.Signer = &signer.Empty{} if subnetID == constants.PrimaryNetworkID { sk, err := bls.NewSecretKey() - r.NoError(err) + require.NoError(t, err) sig = signer.NewProofOfPossession(sk) } @@ -988,43 +756,49 @@ func TestValidatorWeightDiff(t *testing.T) { } } -// Tests PutCurrentValidator, DeleteCurrentValidator, GetCurrentValidator, -// ApplyValidatorWeightDiffs, ApplyValidatorPublicKeyDiffs -func TestStateAddRemoveValidator(t *testing.T) { +func TestState_ApplyValidatorDiffs(t *testing.T) { require := require.New(t) state := newTestState(t, memdb.New()) var ( - numNodes = 3 - subnetID = ids.GenerateTestID() - startTime = time.Now() - endTime = startTime.Add(24 * time.Hour) - stakers = make([]Staker, numNodes) + numNodes = 5 + subnetID = ids.GenerateTestID() + startTime = time.Now() + endTime = startTime.Add(24 * time.Hour) + primaryStakers = make([]Staker, numNodes) + subnetStakers = make([]Staker, numNodes) ) - for i := 0; i < numNodes; i++ { - stakers[i] = Staker{ + for i := range primaryStakers { + sk, err := bls.NewSecretKey() + require.NoError(err) + + primaryStakers[i] = Staker{ TxID: ids.GenerateTestID(), NodeID: ids.GenerateTestNodeID(), + PublicKey: bls.PublicFromSecretKey(sk), + SubnetID: constants.PrimaryNetworkID, Weight: uint64(i + 1), StartTime: startTime.Add(time.Duration(i) * time.Second), EndTime: endTime.Add(time.Duration(i) * time.Second), PotentialReward: uint64(i + 1), } - if i%2 == 0 { - stakers[i].SubnetID = subnetID - } else { - sk, err := bls.NewSecretKey() - require.NoError(err) - stakers[i].PublicKey = bls.PublicFromSecretKey(sk) - stakers[i].SubnetID = constants.PrimaryNetworkID + } + for i, primaryStaker := range primaryStakers { + subnetStakers[i] = Staker{ + TxID: ids.GenerateTestID(), + NodeID: primaryStaker.NodeID, + PublicKey: nil, // Key is inherited from the primary network + SubnetID: subnetID, + Weight: uint64(i + 1), + StartTime: primaryStaker.StartTime, + EndTime: primaryStaker.EndTime, + PotentialReward: uint64(i + 1), } } type diff struct { addedValidators []Staker - addedDelegators []Staker - removedDelegators []Staker removedValidators []Staker expectedPrimaryValidatorSet map[ids.NodeID]*validators.GetValidatorOutput @@ -1037,101 +811,172 @@ func TestStateAddRemoveValidator(t *testing.T) { expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, }, { - // Add a subnet validator - addedValidators: []Staker{stakers[0]}, - expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, - expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ - stakers[0].NodeID: { - NodeID: stakers[0].NodeID, - Weight: stakers[0].Weight, + // Add primary validator 0 + addedValidators: []Staker{primaryStakers[0]}, + expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ + primaryStakers[0].NodeID: { + NodeID: primaryStakers[0].NodeID, + PublicKey: primaryStakers[0].PublicKey, + Weight: primaryStakers[0].Weight, }, }, + expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, }, { - // Remove a subnet validator - removedValidators: []Staker{stakers[0]}, - expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, - expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, + // Add subnet validator 0 + addedValidators: []Staker{subnetStakers[0]}, + expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ + primaryStakers[0].NodeID: { + NodeID: primaryStakers[0].NodeID, + PublicKey: primaryStakers[0].PublicKey, + Weight: primaryStakers[0].Weight, + }, + }, + expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ + subnetStakers[0].NodeID: { + NodeID: subnetStakers[0].NodeID, + Weight: subnetStakers[0].Weight, + }, + }, }, - { // Add a primary network validator - addedValidators: []Staker{stakers[1]}, + { + // Remove subnet validator 0 + removedValidators: []Staker{subnetStakers[0]}, expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ - stakers[1].NodeID: { - NodeID: stakers[1].NodeID, - PublicKey: stakers[1].PublicKey, - Weight: stakers[1].Weight, + primaryStakers[0].NodeID: { + NodeID: primaryStakers[0].NodeID, + PublicKey: primaryStakers[0].PublicKey, + Weight: primaryStakers[0].Weight, }, }, expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, }, { - // Do nothing + // Add primary network validator 1, and subnet validator 1 + addedValidators: []Staker{primaryStakers[1], subnetStakers[1]}, + // Remove primary network validator 0, and subnet validator 1 + removedValidators: []Staker{primaryStakers[0], subnetStakers[1]}, expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ - stakers[1].NodeID: { - NodeID: stakers[1].NodeID, - PublicKey: stakers[1].PublicKey, - Weight: stakers[1].Weight, + primaryStakers[1].NodeID: { + NodeID: primaryStakers[1].NodeID, + PublicKey: primaryStakers[1].PublicKey, + Weight: primaryStakers[1].Weight, }, }, expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, }, - { // Remove a primary network validator - removedValidators: []Staker{stakers[1]}, - expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, - expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, + { + // Add primary network validator 2, and subnet validator 2 + addedValidators: []Staker{primaryStakers[2], subnetStakers[2]}, + // Remove primary network validator 1 + removedValidators: []Staker{primaryStakers[1]}, + expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ + primaryStakers[2].NodeID: { + NodeID: primaryStakers[2].NodeID, + PublicKey: primaryStakers[2].PublicKey, + Weight: primaryStakers[2].Weight, + }, + }, + expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ + subnetStakers[2].NodeID: { + NodeID: subnetStakers[2].NodeID, + Weight: subnetStakers[2].Weight, + }, + }, }, { - // Add 2 subnet validators and a primary network validator - addedValidators: []Staker{stakers[0], stakers[1], stakers[2]}, + // Add primary network and subnet validators 3 & 4 + addedValidators: []Staker{primaryStakers[3], primaryStakers[4], subnetStakers[3], subnetStakers[4]}, expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ - stakers[1].NodeID: { - NodeID: stakers[1].NodeID, - PublicKey: stakers[1].PublicKey, - Weight: stakers[1].Weight, + primaryStakers[2].NodeID: { + NodeID: primaryStakers[2].NodeID, + PublicKey: primaryStakers[2].PublicKey, + Weight: primaryStakers[2].Weight, + }, + primaryStakers[3].NodeID: { + NodeID: primaryStakers[3].NodeID, + PublicKey: primaryStakers[3].PublicKey, + Weight: primaryStakers[3].Weight, + }, + primaryStakers[4].NodeID: { + NodeID: primaryStakers[4].NodeID, + PublicKey: primaryStakers[4].PublicKey, + Weight: primaryStakers[4].Weight, }, }, expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{ - stakers[0].NodeID: { - NodeID: stakers[0].NodeID, - Weight: stakers[0].Weight, + subnetStakers[2].NodeID: { + NodeID: subnetStakers[2].NodeID, + Weight: subnetStakers[2].Weight, + }, + subnetStakers[3].NodeID: { + NodeID: subnetStakers[3].NodeID, + Weight: subnetStakers[3].Weight, }, - stakers[2].NodeID: { - NodeID: stakers[2].NodeID, - Weight: stakers[2].Weight, + subnetStakers[4].NodeID: { + NodeID: subnetStakers[4].NodeID, + Weight: subnetStakers[4].Weight, }, }, }, { - // Remove 2 subnet validators and a primary network validator. - removedValidators: []Staker{stakers[0], stakers[1], stakers[2]}, + // Remove primary network and subnet validators 2 & 3 & 4 + removedValidators: []Staker{ + primaryStakers[2], primaryStakers[3], primaryStakers[4], + subnetStakers[2], subnetStakers[3], subnetStakers[4], + }, + expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, + expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, + }, + { + // Do nothing expectedPrimaryValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, expectedSubnetValidatorSet: map[ids.NodeID]*validators.GetValidatorOutput{}, }, } for currentIndex, diff := range diffs { - for _, added := range diff.addedValidators { - added := added - require.NoError(state.PutCurrentValidator(&added)) - } - for _, added := range diff.addedDelegators { - added := added - state.PutCurrentDelegator(&added) + d, err := NewDiffOn(state) + require.NoError(err) + + type subnetIDNodeID struct { + subnetID ids.ID + nodeID ids.NodeID } - for _, removed := range diff.removedDelegators { - removed := removed - state.DeleteCurrentDelegator(&removed) + var expectedValidators set.Set[subnetIDNodeID] + for _, added := range diff.addedValidators { + require.NoError(d.PutCurrentValidator(&added)) + + expectedValidators.Add(subnetIDNodeID{ + subnetID: added.SubnetID, + nodeID: added.NodeID, + }) } for _, removed := range diff.removedValidators { - removed := removed - state.DeleteCurrentValidator(&removed) + d.DeleteCurrentValidator(&removed) + + expectedValidators.Remove(subnetIDNodeID{ + subnetID: removed.SubnetID, + nodeID: removed.NodeID, + }) } + require.NoError(d.Apply(state)) + currentHeight := uint64(currentIndex + 1) state.SetHeight(currentHeight) require.NoError(state.Commit()) + // Verify that the current state is as expected. for _, added := range diff.addedValidators { + subnetNodeID := subnetIDNodeID{ + subnetID: added.SubnetID, + nodeID: added.NodeID, + } + if !expectedValidators.Contains(subnetNodeID) { + continue + } + gotValidator, err := state.GetCurrentValidator(added.SubnetID, added.NodeID) require.NoError(err) require.Equal(added, *gotValidator) @@ -1142,37 +987,47 @@ func TestStateAddRemoveValidator(t *testing.T) { require.ErrorIs(err, database.ErrNotFound) } + primaryValidatorSet := state.validators.GetMap(constants.PrimaryNetworkID) + delete(primaryValidatorSet, defaultValidatorNodeID) // Ignore the genesis validator + require.Equal(diff.expectedPrimaryValidatorSet, primaryValidatorSet) + + require.Equal(diff.expectedSubnetValidatorSet, state.validators.GetMap(subnetID)) + + // Verify that applying diffs against the current state results in the + // expected state. for i := 0; i < currentIndex; i++ { prevDiff := diffs[i] prevHeight := uint64(i + 1) - primaryValidatorSet := copyValidatorSet(diff.expectedPrimaryValidatorSet) - require.NoError(state.ApplyValidatorWeightDiffs( - context.Background(), - primaryValidatorSet, - currentHeight, - prevHeight+1, - constants.PrimaryNetworkID, - )) - requireEqualWeightsValidatorSet(require, prevDiff.expectedPrimaryValidatorSet, primaryValidatorSet) - - require.NoError(state.ApplyValidatorPublicKeyDiffs( - context.Background(), - primaryValidatorSet, - currentHeight, - prevHeight+1, - )) - requireEqualPublicKeysValidatorSet(require, prevDiff.expectedPrimaryValidatorSet, primaryValidatorSet) - - subnetValidatorSet := copyValidatorSet(diff.expectedSubnetValidatorSet) - require.NoError(state.ApplyValidatorWeightDiffs( - context.Background(), - subnetValidatorSet, - currentHeight, - prevHeight+1, - subnetID, - )) - requireEqualWeightsValidatorSet(require, prevDiff.expectedSubnetValidatorSet, subnetValidatorSet) + { + primaryValidatorSet := copyValidatorSet(diff.expectedPrimaryValidatorSet) + require.NoError(state.ApplyValidatorWeightDiffs( + context.Background(), + primaryValidatorSet, + currentHeight, + prevHeight+1, + constants.PrimaryNetworkID, + )) + require.NoError(state.ApplyValidatorPublicKeyDiffs( + context.Background(), + primaryValidatorSet, + currentHeight, + prevHeight+1, + )) + require.Equal(prevDiff.expectedPrimaryValidatorSet, primaryValidatorSet) + } + + { + subnetValidatorSet := copyValidatorSet(diff.expectedSubnetValidatorSet) + require.NoError(state.ApplyValidatorWeightDiffs( + context.Background(), + subnetValidatorSet, + currentHeight, + prevHeight+1, + subnetID, + )) + require.Equal(prevDiff.expectedSubnetValidatorSet, subnetValidatorSet) + } } } } @@ -1188,36 +1043,6 @@ func copyValidatorSet( return result } -func requireEqualWeightsValidatorSet( - require *require.Assertions, - expected map[ids.NodeID]*validators.GetValidatorOutput, - actual map[ids.NodeID]*validators.GetValidatorOutput, -) { - require.Len(actual, len(expected)) - for nodeID, expectedVdr := range expected { - require.Contains(actual, nodeID) - - actualVdr := actual[nodeID] - require.Equal(expectedVdr.NodeID, actualVdr.NodeID) - require.Equal(expectedVdr.Weight, actualVdr.Weight) - } -} - -func requireEqualPublicKeysValidatorSet( - require *require.Assertions, - expected map[ids.NodeID]*validators.GetValidatorOutput, - actual map[ids.NodeID]*validators.GetValidatorOutput, -) { - require.Len(actual, len(expected)) - for nodeID, expectedVdr := range expected { - require.Contains(actual, nodeID) - - actualVdr := actual[nodeID] - require.Equal(expectedVdr.NodeID, actualVdr.NodeID) - require.Equal(expectedVdr.PublicKey, actualVdr.PublicKey) - } -} - func TestParsedStateBlock(t *testing.T) { var ( require = require.New(t) From 91a7cd7f30798db77b7c7049d9fe662b81914e7c Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 24 Oct 2024 10:43:46 -0400 Subject: [PATCH 09/11] fix test --- vms/platformvm/state/state_test.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index e9096af13a1c..522e5a912401 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -278,15 +278,13 @@ func TestState_writeStakers(t *testing.T) { addStakerTx: addSubnetValidator, expectedCurrentValidator: subnetCurrentValidatorStaker, expectedValidatorSetOutput: &validators.GetValidatorOutput{ - NodeID: subnetCurrentValidatorStaker.NodeID, - PublicKey: primaryNetworkCurrentValidatorStaker.PublicKey, - Weight: subnetCurrentValidatorStaker.Weight, + NodeID: subnetCurrentValidatorStaker.NodeID, + Weight: subnetCurrentValidatorStaker.Weight, }, expectedWeightDiff: &ValidatorWeightDiff{ Decrease: false, Amount: subnetCurrentValidatorStaker.Weight, }, - expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](nil), }, "delete current primary network validator": { initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker}, @@ -344,7 +342,6 @@ func TestState_writeStakers(t *testing.T) { Decrease: true, Amount: subnetCurrentValidatorStaker.Weight, }, - expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](primaryNetworkCurrentValidatorStaker.PublicKey), }, } From c848feef30038c116ad8e7389c54abf92c8d69d5 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 24 Oct 2024 10:44:54 -0400 Subject: [PATCH 10/11] fix test --- vms/platformvm/state/state_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index c6f6c0d52ec7..f3c952ff90d8 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -278,13 +278,15 @@ func TestState_writeStakers(t *testing.T) { addStakerTx: addSubnetValidator, expectedCurrentValidator: subnetCurrentValidatorStaker, expectedValidatorSetOutput: &validators.GetValidatorOutput{ - NodeID: subnetCurrentValidatorStaker.NodeID, - Weight: subnetCurrentValidatorStaker.Weight, + NodeID: subnetCurrentValidatorStaker.NodeID, + PublicKey: primaryNetworkCurrentValidatorStaker.PublicKey, + Weight: subnetCurrentValidatorStaker.Weight, }, expectedWeightDiff: &ValidatorWeightDiff{ Decrease: false, Amount: subnetCurrentValidatorStaker.Weight, }, + expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](nil), }, "delete current primary network validator": { initialStakers: []*Staker{primaryNetworkCurrentValidatorStaker}, @@ -342,6 +344,7 @@ func TestState_writeStakers(t *testing.T) { Decrease: true, Amount: subnetCurrentValidatorStaker.Weight, }, + expectedPublicKeyDiff: maybe.Some[*bls.PublicKey](primaryNetworkCurrentValidatorStaker.PublicKey), }, } From 79c9b40074f82ae33ebf0e4ac8ac2191c3e81144 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Thu, 24 Oct 2024 10:49:15 -0400 Subject: [PATCH 11/11] nit --- vms/platformvm/state/state.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index cfb5888ecfa9..c70547265cf1 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -2079,12 +2079,11 @@ func (s *state) writeCurrentStakers(updateValidators bool, height uint64, codecV } // TODO: Move validator set management out of the state package - // - // Attempt to update the stake metrics if !updateValidators { return nil } + // Update the stake metrics totalWeight, err := s.validators.TotalWeight(constants.PrimaryNetworkID) if err != nil { return fmt.Errorf("failed to get total weight of primary network: %w", err)