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

Staking V4: Refactor stakingDataProvider to pre-fill auction data #4155

Merged
18 changes: 18 additions & 0 deletions epochStart/dtos.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package epochStart

import (
"math/big"

"github.com/ElrondNetwork/elrond-go/state"
)

// OwnerData is a struct containing relevant information about owner's nodes data
type OwnerData struct {
NumStakedNodes int64
NumActiveNodes int64
NumAuctionNodes int64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think NumAuctionNodes can be removed because are duplicated information with the len(AuctionList)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, good suggestion; removed it from here and from stakingDataProvider.
However, I've kept it in auctionListSelector, since there we do a lot of iterations and I don't want to re-calculate len every time.

TotalTopUp *big.Int
TopUpPerNode *big.Int
AuctionList []state.ValidatorInfoHandler
Qualified bool
}
3 changes: 3 additions & 0 deletions epochStart/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,3 +340,6 @@ var ErrOwnerHasNoStakedNode = errors.New("owner has no staked node")

// ErrUint32SubtractionOverflow signals uint32 subtraction overflowed
var ErrUint32SubtractionOverflow = errors.New("uint32 subtraction overflowed")

// ErrReceivedAuctionValidatorsBeforeStakingV4 signals that an auction node has been provided before enabling staking v4
var ErrReceivedAuctionValidatorsBeforeStakingV4 = errors.New("auction node has been provided before enabling staking v4")
9 changes: 4 additions & 5 deletions epochStart/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,12 @@ type StakingDataProvider interface {
GetTotalStakeEligibleNodes() *big.Int
GetTotalTopUpStakeEligibleNodes() *big.Int
GetNodeStakedTopUp(blsKey []byte) (*big.Int, error)
GetNumStakedNodes(owner []byte) (int64, error)
GetTotalTopUp(owner []byte) (*big.Int, error)
PrepareStakingData(keys map[uint32][][]byte) error
FillValidatorInfo(blsKey []byte) error
PrepareStakingData(validatorsMap state.ShardValidatorsInfoMapHandler) error
FillValidatorInfo(validator state.ValidatorInfoHandler) error
ComputeUnQualifiedNodes(validatorInfos state.ShardValidatorsInfoMapHandler) ([][]byte, map[string][][]byte, error)
GetBlsKeyOwner(blsKey []byte) (string, error)
GetNumOfValidatorsInCurrentEpoch() uint32
GetOwnersData() map[string]*OwnerData
Clean()
EpochConfirmed(epoch uint32, timestamp uint64)
IsInterfaceNil() bool
Expand Down Expand Up @@ -216,7 +216,6 @@ type MaxNodesChangeConfigProvider interface {
type AuctionListSelector interface {
SelectNodesFromAuctionList(
validatorsInfoMap state.ShardValidatorsInfoMapHandler,
unqualifiedOwners map[string]struct{},
randomness []byte,
) error
IsInterfaceNil() bool
Expand Down
8 changes: 4 additions & 4 deletions epochStart/metachain/auctionListDisplayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func getPrettyValue(val *big.Int, denominator *big.Int) string {
return first + "." + second
}

func (als *auctionListSelector) displayOwnersData(ownersData map[string]*ownerData) {
func (als *auctionListSelector) displayOwnersData(ownersData map[string]*ownerAuctionData) {
if log.GetLevel() > logger.LogDebug {
return
}
Expand Down Expand Up @@ -109,7 +109,7 @@ func (als *auctionListSelector) displayOwnersData(ownersData map[string]*ownerDa
displayTable(tableHeader, lines, "Initial nodes config in auction list")
}

func (als *auctionListSelector) displayOwnersSelectedNodes(ownersData map[string]*ownerData) {
func (als *auctionListSelector) displayOwnersSelectedNodes(ownersData map[string]*ownerAuctionData) {
if log.GetLevel() > logger.LogDebug {
return
}
Expand Down Expand Up @@ -147,7 +147,7 @@ func (als *auctionListSelector) displayOwnersSelectedNodes(ownersData map[string

func (als *auctionListSelector) displayAuctionList(
auctionList []state.ValidatorInfoHandler,
ownersData map[string]*ownerData,
ownersData map[string]*ownerAuctionData,
numOfSelectedNodes uint32,
) {
if log.GetLevel() > logger.LogDebug {
Expand Down Expand Up @@ -179,7 +179,7 @@ func (als *auctionListSelector) displayAuctionList(
displayTable(tableHeader, lines, "Final selected nodes from auction list")
}

func getBlsKeyOwnerMap(ownersData map[string]*ownerData) map[string]string {
func getBlsKeyOwnerMap(ownersData map[string]*ownerAuctionData) map[string]string {
ret := make(map[string]string)
for ownerPubKey, owner := range ownersData {
for _, blsKey := range owner.auctionList {
Expand Down
133 changes: 28 additions & 105 deletions epochStart/metachain/auctionListSelector.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package metachain

import (
"encoding/hex"
"fmt"
"math"
"math/big"
Expand All @@ -16,11 +15,11 @@ import (
"github.com/ElrondNetwork/elrond-go/state"
)

type ownerData struct {
type ownerAuctionData struct {
numStakedNodes int64
numActiveNodes int64
numAuctionNodes int64
numQualifiedAuctionNodes int64
numStakedNodes int64
totalTopUp *big.Int
topUpPerNode *big.Int
qualifiedTopUpPerNode *big.Int
Expand Down Expand Up @@ -137,23 +136,20 @@ func checkNilArgs(args AuctionListSelectorArgs) error {
// to common.SelectNodesFromAuctionList
func (als *auctionListSelector) SelectNodesFromAuctionList(
validatorsInfoMap state.ShardValidatorsInfoMapHandler,
unqualifiedOwners map[string]struct{},
randomness []byte,
) error {
if len(randomness) == 0 {
return process.ErrNilRandSeed
}

ownersData, auctionListSize, currNumOfValidators, err := als.getAuctionDataAndNumOfValidators(validatorsInfoMap, unqualifiedOwners)
if err != nil {
return err
}
ownersData, auctionListSize := als.getAuctionData()
if auctionListSize == 0 {
log.Info("auctionListSelector.SelectNodesFromAuctionList: empty auction list; skip selection")
return nil
}

currNodesConfig := als.nodesConfigProvider.GetCurrentNodesConfig()
currNumOfValidators := als.stakingDataProvider.GetNumOfValidatorsInCurrentEpoch()
numOfShuffledNodes := currNodesConfig.NodesToShufflePerShard * (als.shardCoordinator.NumberOfShards() + 1)
numOfValidatorsAfterShuffling, err := safeSub(currNumOfValidators, numOfShuffledNodes)
if err != nil {
Expand Down Expand Up @@ -198,107 +194,34 @@ func (als *auctionListSelector) SelectNodesFromAuctionList(
return als.sortAuctionList(ownersData, numOfAvailableNodeSlots, validatorsInfoMap, randomness)
}

func (als *auctionListSelector) getAuctionDataAndNumOfValidators(
validatorsInfoMap state.ShardValidatorsInfoMapHandler,
unqualifiedOwners map[string]struct{},
) (map[string]*ownerData, uint32, uint32, error) {
ownersData := make(map[string]*ownerData)
numOfValidators := uint32(0)
func (als *auctionListSelector) getAuctionData() (map[string]*ownerAuctionData, uint32) {
ownersData := make(map[string]*ownerAuctionData)
numOfNodesInAuction := uint32(0)

for _, validator := range validatorsInfoMap.GetAllValidatorsInfo() {
blsKey := validator.GetPublicKey()
owner, err := als.stakingDataProvider.GetBlsKeyOwner(blsKey)
if err != nil {
return nil, 0, 0, err
}

if isInAuction(validator) {
_, isUnqualified := unqualifiedOwners[owner]
if isUnqualified {
log.Debug("auctionListSelector: found node in auction with unqualified owner, do not add it to selection",
"owner", hex.EncodeToString([]byte(owner)),
"bls key", hex.EncodeToString(blsKey),
)
continue
for owner, ownerData := range als.stakingDataProvider.GetOwnersData() {
if ownerData.Qualified && ownerData.NumAuctionNodes > 0 {
ownersData[owner] = &ownerAuctionData{
numActiveNodes: ownerData.NumActiveNodes,
numAuctionNodes: ownerData.NumAuctionNodes,
numQualifiedAuctionNodes: ownerData.NumAuctionNodes,
numStakedNodes: ownerData.NumStakedNodes,
totalTopUp: ownerData.TotalTopUp,
topUpPerNode: ownerData.TopUpPerNode,
qualifiedTopUpPerNode: ownerData.TopUpPerNode,
auctionList: make([]state.ValidatorInfoHandler, len(ownerData.AuctionList)),
}

err = als.addOwnerData(owner, validator, ownersData)
if err != nil {
return nil, 0, 0, err
}

numOfNodesInAuction++
continue
}
if isValidator(validator) {
numOfValidators++
copy(ownersData[owner].auctionList, ownerData.AuctionList)
numOfNodesInAuction += uint32(ownerData.NumAuctionNodes)
}
}

return ownersData, numOfNodesInAuction, numOfValidators, nil
return ownersData, numOfNodesInAuction
}

func isInAuction(validator state.ValidatorInfoHandler) bool {
return validator.GetList() == string(common.AuctionList)
}

func (als *auctionListSelector) addOwnerData(
owner string,
validator state.ValidatorInfoHandler,
ownersData map[string]*ownerData,
) error {
ownerPubKey := []byte(owner)
validatorPubKey := validator.GetPublicKey()
stakedNodes, err := als.stakingDataProvider.GetNumStakedNodes(ownerPubKey)
if err != nil {
return fmt.Errorf("auctionListSelector.addOwnerData: error getting num staked nodes: %w, owner: %s, node: %s",
err,
hex.EncodeToString(ownerPubKey),
hex.EncodeToString(validatorPubKey),
)
}
if stakedNodes == 0 {
return fmt.Errorf("auctionListSelector.addOwnerData error: %w, owner: %s, node: %s",
epochStart.ErrOwnerHasNoStakedNode,
hex.EncodeToString(ownerPubKey),
hex.EncodeToString(validatorPubKey),
)
}

totalTopUp, err := als.stakingDataProvider.GetTotalTopUp(ownerPubKey)
if err != nil {
return fmt.Errorf("auctionListSelector.addOwnerData: error getting total top up: %w, owner: %s, node: %s",
err,
hex.EncodeToString(ownerPubKey),
hex.EncodeToString(validatorPubKey),
)
}

data, exists := ownersData[owner]
if exists {
data.numAuctionNodes++
data.numQualifiedAuctionNodes++
data.numActiveNodes--
data.auctionList = append(data.auctionList, validator)
} else {
stakedNodesBigInt := big.NewInt(stakedNodes)
topUpPerNode := big.NewInt(0).Div(totalTopUp, stakedNodesBigInt)
ownersData[owner] = &ownerData{
numAuctionNodes: 1,
numQualifiedAuctionNodes: 1,
numActiveNodes: stakedNodes - 1,
numStakedNodes: stakedNodes,
totalTopUp: big.NewInt(0).SetBytes(totalTopUp.Bytes()),
topUpPerNode: topUpPerNode,
qualifiedTopUpPerNode: topUpPerNode,
auctionList: []state.ValidatorInfoHandler{validator},
}
}

return nil
}

// TODO: Move this in elrond-go-core
func safeSub(a, b uint32) (uint32, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have safeSub for uint64 already in go-core

if a < b {
Expand All @@ -308,7 +231,7 @@ func safeSub(a, b uint32) (uint32, error) {
}

func (als *auctionListSelector) sortAuctionList(
ownersData map[string]*ownerData,
ownersData map[string]*ownerAuctionData,
numOfAvailableNodeSlots uint32,
validatorsInfoMap state.ShardValidatorsInfoMapHandler,
randomness []byte,
Expand All @@ -319,9 +242,9 @@ func (als *auctionListSelector) sortAuctionList(
}

func (als *auctionListSelector) calcSoftAuctionNodesConfig(
data map[string]*ownerData,
data map[string]*ownerAuctionData,
numAvailableSlots uint32,
) map[string]*ownerData {
) map[string]*ownerAuctionData {
ownersData := copyOwnersData(data)
minTopUp, maxTopUp := als.getMinMaxPossibleTopUp(ownersData)
log.Debug("auctionListSelector: calc min and max possible top up",
Expand All @@ -344,7 +267,7 @@ func (als *auctionListSelector) calcSoftAuctionNodesConfig(
return previousConfig
}

func (als *auctionListSelector) getMinMaxPossibleTopUp(ownersData map[string]*ownerData) (*big.Int, *big.Int) {
func (als *auctionListSelector) getMinMaxPossibleTopUp(ownersData map[string]*ownerAuctionData) (*big.Int, *big.Int) {
min := big.NewInt(0).SetBytes(als.softAuctionConfig.maxTopUp.Bytes())
max := big.NewInt(0).SetBytes(als.softAuctionConfig.minTopUp.Bytes())

Expand All @@ -367,10 +290,10 @@ func (als *auctionListSelector) getMinMaxPossibleTopUp(ownersData map[string]*ow
return min, max
}

func copyOwnersData(ownersData map[string]*ownerData) map[string]*ownerData {
ret := make(map[string]*ownerData)
func copyOwnersData(ownersData map[string]*ownerAuctionData) map[string]*ownerAuctionData {
ret := make(map[string]*ownerAuctionData)
for owner, data := range ownersData {
ret[owner] = &ownerData{
ret[owner] = &ownerAuctionData{
numActiveNodes: data.numActiveNodes,
numAuctionNodes: data.numAuctionNodes,
numQualifiedAuctionNodes: data.numQualifiedAuctionNodes,
Expand All @@ -386,7 +309,7 @@ func copyOwnersData(ownersData map[string]*ownerData) map[string]*ownerData {
return ret
}

func calcNodesConfig(ownersData map[string]*ownerData, topUp *big.Int) int64 {
func calcNodesConfig(ownersData map[string]*ownerAuctionData, topUp *big.Int) int64 {
numNodesQualifyingForTopUp := int64(0)

for ownerPubKey, owner := range ownersData {
Expand Down