Skip to content

Commit

Permalink
Merge pull request #6114 from multiversx/MX-15360-waiting-list-distri…
Browse files Browse the repository at this point in the history
…bution-leaving-nodes

POC: Distribute to waiting from auction based on leaving nodes
  • Loading branch information
mariusmihaic committed Apr 23, 2024
2 parents 560b15b + 5258bf0 commit b0f7a5c
Show file tree
Hide file tree
Showing 10 changed files with 561 additions and 9 deletions.
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)
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 {
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
}

0 comments on commit b0f7a5c

Please sign in to comment.