Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC: Distribute to waiting from auction based on leaving nodes #6114

Merged
7 changes: 7 additions & 0 deletions epochStart/dtos.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,10 @@ type OwnerData struct {
AuctionList []state.ValidatorInfoHandler
Qualified bool
}

// ValidatorStatsInEpoch holds validator stats in an epoch
type ValidatorStatsInEpoch struct {
Eligible map[uint32]int
Waiting map[uint32]int
Leaving map[uint32]int
}
1 change: 1 addition & 0 deletions epochStart/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ type StakingDataProvider interface {
ComputeUnQualifiedNodes(validatorInfos state.ShardValidatorsInfoMapHandler) ([][]byte, map[string][][]byte, error)
GetBlsKeyOwner(blsKey []byte) (string, error)
GetNumOfValidatorsInCurrentEpoch() uint32
GetCurrentEpochValidatorStats() ValidatorStatsInEpoch
GetOwnersData() map[string]*OwnerData
Clean()
IsInterfaceNil() bool
Expand Down
66 changes: 60 additions & 6 deletions epochStart/metachain/auctionListSelector.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func (als *auctionListSelector) SelectNodesFromAuctionList(

currNodesConfig := als.nodesConfigProvider.GetCurrentNodesConfig()
currNumOfValidators := als.stakingDataProvider.GetNumOfValidatorsInCurrentEpoch()
numOfShuffledNodes := currNodesConfig.NodesToShufflePerShard * (als.shardCoordinator.NumberOfShards() + 1)
numOfShuffledNodes, numForcedToStay := als.computeNumShuffledNodes(currNodesConfig)
raduchis marked this conversation as resolved.
Show resolved Hide resolved
numOfValidatorsAfterShuffling, err := safeSub(currNumOfValidators, numOfShuffledNodes)
if err != nil {
log.Warn(fmt.Sprintf("auctionListSelector.SelectNodesFromAuctionList: %v when trying to compute numOfValidatorsAfterShuffling = %v - %v (currNumOfValidators - numOfShuffledNodes)",
Expand All @@ -210,12 +210,13 @@ func (als *auctionListSelector) SelectNodesFromAuctionList(
}

maxNumNodes := currNodesConfig.MaxNumNodes
availableSlots, err := safeSub(maxNumNodes, numOfValidatorsAfterShuffling)
numValidatorsAfterShufflingWithForcedToStay := numOfValidatorsAfterShuffling + numForcedToStay
availableSlots, err := safeSub(maxNumNodes, numValidatorsAfterShufflingWithForcedToStay)
if availableSlots == 0 || err != nil {
log.Info(fmt.Sprintf("auctionListSelector.SelectNodesFromAuctionList: %v or zero value when trying to compute availableSlots = %v - %v (maxNodes - numOfValidatorsAfterShuffling); skip selecting nodes from auction list",
log.Info(fmt.Sprintf("auctionListSelector.SelectNodesFromAuctionList: %v or zero value when trying to compute availableSlots = %v - %v (maxNodes - numOfValidatorsAfterShuffling+numForcedToStay); skip selecting nodes from auction list",
err,
maxNumNodes,
numOfValidatorsAfterShuffling,
numValidatorsAfterShufflingWithForcedToStay,
))
return nil
}
Expand All @@ -224,9 +225,10 @@ func (als *auctionListSelector) SelectNodesFromAuctionList(
"max nodes", maxNumNodes,
"current number of validators", currNumOfValidators,
"num of nodes which will be shuffled out", numOfShuffledNodes,
"num of validators after shuffling", numOfValidatorsAfterShuffling,
"num forced to stay", numForcedToStay,
"num of validators after shuffling with forced to stay", numValidatorsAfterShufflingWithForcedToStay,
"auction list size", auctionListSize,
fmt.Sprintf("available slots (%v - %v)", maxNumNodes, numOfValidatorsAfterShuffling), availableSlots,
fmt.Sprintf("available slots (%v - %v)", maxNumNodes, numValidatorsAfterShufflingWithForcedToStay), availableSlots,
)

als.auctionListDisplayer.DisplayOwnersData(ownersData)
Expand Down Expand Up @@ -272,6 +274,58 @@ func isInAuction(validator state.ValidatorInfoHandler) bool {
return validator.GetList() == string(common.AuctionList)
}

func (als *auctionListSelector) computeNumShuffledNodes(currNodesConfig config.MaxNodesChangeConfig) (uint32, uint32) {
numNodesToShufflePerShard := currNodesConfig.NodesToShufflePerShard
numTotalToShuffleOut := numNodesToShufflePerShard * (als.shardCoordinator.NumberOfShards() + 1)
epochStats := als.stakingDataProvider.GetCurrentEpochValidatorStats()

actuallyNumLeaving := uint32(0)
forcedToStay := uint32(0)

for shardID := uint32(0); shardID < als.shardCoordinator.NumberOfShards(); shardID++ {
leavingInShard, forcedToStayInShard := computeActuallyNumLeaving(shardID, epochStats, numNodesToShufflePerShard)
actuallyNumLeaving += leavingInShard
forcedToStay += forcedToStayInShard
}

leavingInMeta, forcedToStayInMeta := computeActuallyNumLeaving(core.MetachainShardId, epochStats, numNodesToShufflePerShard)
actuallyNumLeaving += leavingInMeta
forcedToStay += forcedToStayInMeta

finalShuffledOut, err := safeSub(numTotalToShuffleOut, actuallyNumLeaving)
if err != nil {
log.Error("auctionListSelector.computeNumShuffledNodes error computing finalShuffledOut, returning default values",
"error", err, "numTotalToShuffleOut", numTotalToShuffleOut, "actuallyNumLeaving", actuallyNumLeaving)
return numTotalToShuffleOut, 0
}

return finalShuffledOut, forcedToStay
}

func computeActuallyNumLeaving(shardID uint32, epochStats epochStart.ValidatorStatsInEpoch, numNodesToShuffledPerShard uint32) (uint32, uint32) {
numLeavingInShard := uint32(epochStats.Leaving[shardID])
numActiveInShard := uint32(epochStats.Waiting[shardID] + epochStats.Eligible[shardID])

log.Debug("auctionListSelector.computeActuallyNumLeaving computing",
"shardID", shardID, "numLeavingInShard", numLeavingInShard, "numActiveInShard", numActiveInShard)

actuallyLeaving := uint32(0)
forcedToStay := uint32(0)
if numLeavingInShard <= numNodesToShuffledPerShard && numActiveInShard > numLeavingInShard {
actuallyLeaving = numLeavingInShard
}

if numLeavingInShard > numNodesToShuffledPerShard {
actuallyLeaving = numNodesToShuffledPerShard
forcedToStay = numLeavingInShard - numNodesToShuffledPerShard
}

log.Debug("auctionListSelector.computeActuallyNumLeaving computed",
"actuallyLeaving", actuallyLeaving, "forcedToStay", forcedToStay)

return actuallyLeaving, forcedToStay
}

// TODO: Move this in elrond-go-core
func safeSub(a, b uint32) (uint32, error) {
if a < b {
Expand Down
51 changes: 51 additions & 0 deletions epochStart/metachain/stakingDataProvider.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type stakingDataProvider struct {
minNodePrice *big.Int
numOfValidatorsInCurrEpoch uint32
enableEpochsHandler common.EnableEpochsHandler
validatorStatsInEpoch epochStart.ValidatorStatsInEpoch
}

// StakingDataProviderArgs is a struct placeholder for all arguments required to create a NewStakingDataProvider
Expand Down Expand Up @@ -77,6 +78,11 @@ func NewStakingDataProvider(args StakingDataProviderArgs) (*stakingDataProvider,
totalEligibleStake: big.NewInt(0),
totalEligibleTopUpStake: big.NewInt(0),
enableEpochsHandler: args.EnableEpochsHandler,
validatorStatsInEpoch: epochStart.ValidatorStatsInEpoch{
Eligible: make(map[uint32]int),
Waiting: make(map[uint32]int),
Leaving: make(map[uint32]int),
},
}

return sdp, nil
Expand All @@ -89,6 +95,11 @@ func (sdp *stakingDataProvider) Clean() {
sdp.totalEligibleStake.SetInt64(0)
sdp.totalEligibleTopUpStake.SetInt64(0)
sdp.numOfValidatorsInCurrEpoch = 0
sdp.validatorStatsInEpoch = epochStart.ValidatorStatsInEpoch{
Eligible: make(map[uint32]int),
Waiting: make(map[uint32]int),
Leaving: make(map[uint32]int),
}
sdp.mutStakingData.Unlock()
}

Expand Down Expand Up @@ -200,6 +211,7 @@ func (sdp *stakingDataProvider) getAndFillOwnerStats(validator state.ValidatorIn
sdp.numOfValidatorsInCurrEpoch++
}

sdp.updateEpochStats(validator)
return ownerData, nil
}

Expand Down Expand Up @@ -532,6 +544,45 @@ func (sdp *stakingDataProvider) GetNumOfValidatorsInCurrentEpoch() uint32 {
return sdp.numOfValidatorsInCurrEpoch
}

func (sdp *stakingDataProvider) updateEpochStats(validator state.ValidatorInfoHandler) {
validatorCurrentList := common.PeerType(validator.GetList())
shardID := validator.GetShardId()

if validatorCurrentList == common.EligibleList {
sdp.validatorStatsInEpoch.Eligible[shardID]++
return
}

if validatorCurrentList == common.WaitingList {
sdp.validatorStatsInEpoch.Waiting[shardID]++
return
}

validatorPreviousList := common.PeerType(validator.GetPreviousList())
if sdp.isValidatorLeaving(validatorCurrentList, validatorPreviousList) {
sdp.validatorStatsInEpoch.Leaving[shardID]++
}
}

func (sdp *stakingDataProvider) isValidatorLeaving(validatorCurrentList, validatorPreviousList common.PeerType) bool {
if validatorCurrentList != common.LeavingList {
raduchis marked this conversation as resolved.
Show resolved Hide resolved
return false
}

// If no previous list is set, means that staking v4 is not activated or node is leaving right before activation
// and this node will be considered as eligible by the nodes coordinator with old code.
// Otherwise, it will have it set, and we should check its previous list in the current epoch
return len(validatorPreviousList) == 0 || validatorPreviousList == common.EligibleList || validatorPreviousList == common.WaitingList
}

// GetCurrentEpochValidatorStats returns the current epoch validator stats
func (sdp *stakingDataProvider) GetCurrentEpochValidatorStats() epochStart.ValidatorStatsInEpoch {
sdp.mutStakingData.RLock()
defer sdp.mutStakingData.RUnlock()

return sdp.validatorStatsInEpoch
}

// IsInterfaceNil return true if underlying object is nil
func (sdp *stakingDataProvider) IsInterfaceNil() bool {
return sdp == nil
Expand Down
4 changes: 2 additions & 2 deletions epochStart/metachain/systemSCs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2093,7 +2093,7 @@ func TestSystemSCProcessor_ProcessSystemSmartContractStakingV4Enabled(t *testing
t.Parallel()

args, _ := createFullArgumentsForSystemSCProcessing(config.EnableEpochs{}, testscommon.CreateMemUnit())
nodesConfigProvider, _ := notifier.NewNodesConfigProvider(args.EpochNotifier, []config.MaxNodesChangeConfig{{MaxNumNodes: 8}})
nodesConfigProvider, _ := notifier.NewNodesConfigProvider(args.EpochNotifier, []config.MaxNodesChangeConfig{{MaxNumNodes: 9}})

auctionCfg := config.SoftAuctionConfig{
TopUpStep: "10",
Expand Down Expand Up @@ -2179,7 +2179,7 @@ func TestSystemSCProcessor_ProcessSystemSmartContractStakingV4Enabled(t *testing
will not participate in auction selection
- owner6 does not have enough stake for 2 nodes => one of his auction nodes(pubKey14) will be unStaked at the end of the epoch =>
his other auction node(pubKey15) will not participate in auction selection
- MaxNumNodes = 8
- MaxNumNodes = 9
- EligibleBlsKeys = 5 (pubKey0, pubKey1, pubKey3, pubKe13, pubKey17)
- QualifiedAuctionBlsKeys = 7 (pubKey2, pubKey4, pubKey5, pubKey7, pubKey9, pubKey10, pubKey11)
We can only select (MaxNumNodes - EligibleBlsKeys = 3) bls keys from AuctionList to be added to NewList
Expand Down
2 changes: 2 additions & 0 deletions integrationTests/chainSimulator/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/multiversx/mx-chain-core-go/data/api"
"github.com/multiversx/mx-chain-core-go/data/transaction"
crypto "github.com/multiversx/mx-chain-crypto-go"
"github.com/multiversx/mx-chain-go/node/chainSimulator/dtos"
"github.com/multiversx/mx-chain-go/node/chainSimulator/process"
)
Expand All @@ -22,4 +23,5 @@ type ChainSimulator interface {
GetInitialWalletKeys() *dtos.InitialWalletKeys
GetAccount(address dtos.WalletAddress) (api.AccountResponse, error)
ForceResetValidatorStatisticsCache() error
GetValidatorPrivateKeys() []crypto.PrivateKey
}