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: soft auction list selection #4083

Merged
merged 32 commits into from
Jun 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
8b4d1b8
FEAT: First ugly version
mariusmihaic May 17, 2022
2f9c1c8
FEAT: First ugly working version
mariusmihaic May 17, 2022
e7f6b9c
FEAT: Intermediary code
mariusmihaic May 19, 2022
0ab80fc
FEAT: Stable code
mariusmihaic May 19, 2022
e9ca4d3
FEAT: Do not add unqualified nodes in auction
mariusmihaic May 19, 2022
2c41f17
CLN: Quick fix broken test
mariusmihaic May 19, 2022
dcf9f5b
FIX: not selecting unqualified nodes for auction
mariusmihaic May 20, 2022
f06c188
CLN: Start refactor
mariusmihaic May 20, 2022
a5659dc
CLN: Refactor 2
mariusmihaic May 20, 2022
7e22f59
CLN: Refactor 3
mariusmihaic May 20, 2022
900ed74
CLN: Refactor 4
mariusmihaic May 23, 2022
c9f2fb0
CLN: Refactor 5
mariusmihaic May 23, 2022
4f21dc5
Merge branch 'EN-12187-auction-list-selection-subcomp' into EN-12196-…
mariusmihaic May 23, 2022
5a363a0
FIX: After merges
mariusmihaic May 23, 2022
c3217c1
FIX: After merges 2
mariusmihaic May 23, 2022
b932f59
CLN: Refactor 5
mariusmihaic May 23, 2022
b3b9129
CLN: Refactor 6
mariusmihaic May 23, 2022
fd6898f
CLN: Refactor 7
mariusmihaic May 23, 2022
b162246
FEAT: First test for calcSoftAuctionNodesConfig + bugfixes
mariusmihaic May 24, 2022
ba05416
CLN: Test
mariusmihaic May 24, 2022
8d324c9
FEAT: Add edge case tests for calcSoftAuctionNodesConfig
mariusmihaic May 24, 2022
43a8338
FEAT: > 99% code coverage
mariusmihaic May 25, 2022
2a760b9
FEAT: Add SoftAuctionConfig and integrate it
mariusmihaic May 25, 2022
09d3efc
FEAT: Add getAuctionConfig test + split test files
mariusmihaic May 25, 2022
8dd0ee3
FIX: Broken tests
mariusmihaic May 25, 2022
64ef325
FIX: General fixes 1
mariusmihaic May 26, 2022
85d08d9
FIX: General fixes 2
mariusmihaic May 26, 2022
75e0b5a
Merge branch 'feat/liquid-staking' into EN-12196-implement-soft-auction
mariusmihaic May 26, 2022
a05cdd3
FIX: General fixes 3
mariusmihaic May 26, 2022
2623fb7
Merge branch 'feat/liquid-staking' into EN-12196-implement-soft-auction
mariusmihaic May 27, 2022
0ea0111
Merge branch 'feat/liquid-staking' into EN-12196-implement-soft-auction
mariusmihaic May 31, 2022
5e24f07
FIX: Review findings
mariusmihaic Jun 3, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions cmd/node/config/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -926,3 +926,9 @@
Capacity = 1000
Type = "SizeLRU"
SizeInBytes = 314572800 #300MB

# Changing this config is not backwards compatible
[SoftAuctionConfig]
TopUpStep = "10000000000000000000" # 10 EGLD
MinTopUp = "1" # 0.00...01 EGLD , should be very low, but != zero
raduchis marked this conversation as resolved.
Show resolved Hide resolved
MaxTopUp = "32000000000000000000000000" # 32 mil EGLD
8 changes: 8 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ type Config struct {
VMOutputCacher CacheConfig

PeersRatingConfig PeersRatingConfig
SoftAuctionConfig SoftAuctionConfig
}

// PeersRatingConfig will hold settings related to peers rating
Expand Down Expand Up @@ -589,3 +590,10 @@ type ResolverConfig struct {
NumTotalPeers uint32
NumFullHistoryPeers uint32
}

// SoftAuctionConfig represents the config options for soft auction selecting used in staking v4
type SoftAuctionConfig struct {
TopUpStep string
MinTopUp string
MaxTopUp string
}
9 changes: 6 additions & 3 deletions epochStart/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ var ErrSystemValidatorSCCall = errors.New("system validator sc call failed")
// ErrOwnerDoesntHaveEligibleNodesInEpoch signals that the owner doesn't have any eligible nodes in epoch
var ErrOwnerDoesntHaveEligibleNodesInEpoch = errors.New("owner has no eligible nodes in epoch")

// ErrOwnerDoesntHaveNodesInEpoch signals that the owner has no nodes in epoch
var ErrOwnerDoesntHaveNodesInEpoch = errors.New("owner has no nodes in epoch")

// ErrInvalidMaxHardCapForMissingNodes signals that the maximum hardcap value for missing nodes is invalid
var ErrInvalidMaxHardCapForMissingNodes = errors.New("invalid max hardcap for missing nodes")

Expand Down Expand Up @@ -323,9 +326,6 @@ var ErrNilScheduledDataSyncerFactory = errors.New("nil scheduled data syncer fac
// ErrCouldNotInitLiquidStakingSystemSC signals that liquid staking system sc init failed
var ErrCouldNotInitLiquidStakingSystemSC = errors.New("could not init liquid staking system sc")

// ErrSortAuctionList signals that an error occurred while trying to sort auction list
var ErrSortAuctionList = errors.New("error while trying to sort auction list")

// ErrReceivedNewListNodeInStakingV4 signals that a new node has been assigned in common.NewList instead of common.AuctionList after staking v4
var ErrReceivedNewListNodeInStakingV4 = errors.New("new node has been assigned in common.NewList instead of common.AuctionList after staking v4")

Expand All @@ -335,5 +335,8 @@ var ErrNilMaxNodesChangeConfigProvider = errors.New("nil nodes config provider h
// ErrNilAuctionListSelector signals that a nil auction list selector has been provided
var ErrNilAuctionListSelector = errors.New("nil auction list selector has been provided")

// ErrOwnerHasNoStakedNode signals that the owner has no staked node
var ErrOwnerHasNoStakedNode = errors.New("owner has no staked node")

// ErrUint32SubtractionOverflow signals uint32 subtraction overflowed
var ErrUint32SubtractionOverflow = errors.New("uint32 subtraction overflowed")
8 changes: 7 additions & 1 deletion epochStart/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ 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
ComputeUnQualifiedNodes(validatorInfos state.ShardValidatorsInfoMapHandler) ([][]byte, map[string][][]byte, error)
Expand Down Expand Up @@ -212,6 +214,10 @@ type MaxNodesChangeConfigProvider interface {

// AuctionListSelector handles selection of nodes from auction list to be sent to waiting list, based on their top up
type AuctionListSelector interface {
SelectNodesFromAuctionList(validatorsInfoMap state.ShardValidatorsInfoMapHandler, randomness []byte) error
SelectNodesFromAuctionList(
validatorsInfoMap state.ShardValidatorsInfoMapHandler,
unqualifiedOwners map[string]struct{},
randomness []byte,
) error
IsInterfaceNil() bool
}
202 changes: 202 additions & 0 deletions epochStart/metachain/auctionListDisplayer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package metachain

import (
"encoding/hex"
"fmt"
"math/big"
"strconv"
"strings"

"github.com/ElrondNetwork/elrond-go-core/core"
"github.com/ElrondNetwork/elrond-go-core/display"
logger "github.com/ElrondNetwork/elrond-go-logger"
"github.com/ElrondNetwork/elrond-go/state"
)

const maxPubKeyDisplayableLen = 20
const maxNumOfDecimalsToDisplay = 5

func (als *auctionListSelector) displayMinRequiredTopUp(topUp *big.Int, startTopUp *big.Int) {
raduchis marked this conversation as resolved.
Show resolved Hide resolved
if log.GetLevel() > logger.LogDebug {
return
}

if topUp.Cmp(als.softAuctionConfig.minTopUp) > 0 {
topUp = big.NewInt(0).Sub(topUp, als.softAuctionConfig.step)
}

iteratedValues := big.NewInt(0).Sub(topUp, startTopUp)
iterations := big.NewInt(0).Div(iteratedValues, als.softAuctionConfig.step).Int64()
iterations++

log.Debug("auctionListSelector: found min required",
"topUp", topUp.String(),
"after num of iterations", iterations,
)
}

func getShortKey(pubKey []byte) string {
pubKeyHex := hex.EncodeToString(pubKey)
displayablePubKey := pubKeyHex

pubKeyLen := len(displayablePubKey)
if pubKeyLen > maxPubKeyDisplayableLen {
displayablePubKey = pubKeyHex[:maxPubKeyDisplayableLen/2] + "..." + pubKeyHex[pubKeyLen-maxPubKeyDisplayableLen/2:]
}

return displayablePubKey
}

func getShortDisplayableBlsKeys(list []state.ValidatorInfoHandler) string {
pubKeys := ""

for idx, validator := range list {
pubKeys += getShortKey(validator.GetPublicKey())
addDelimiter := idx != len(list)-1
if addDelimiter {
pubKeys += ", "
}
}

return pubKeys
}

func getPrettyValue(val *big.Int, denominator *big.Int) string {
first := big.NewInt(0).Div(val, denominator).String()
decimals := big.NewInt(0).Mod(val, denominator).String()

zeroesCt := (len(denominator.String()) - len(decimals)) - 1
zeroesCt = core.MaxInt(zeroesCt, 0)
zeroes := strings.Repeat("0", zeroesCt)

second := zeroes + decimals
if len(second) > maxNumOfDecimalsToDisplay {
second = second[:maxNumOfDecimalsToDisplay]
}

return first + "." + second
}

func (als *auctionListSelector) displayOwnersData(ownersData map[string]*ownerData) {
if log.GetLevel() > logger.LogDebug {
return
}

tableHeader := []string{
"Owner",
"Num staked nodes",
"Num active nodes",
"Num auction nodes",
"Total top up",
"Top up per node",
"Auction list nodes",
}

lines := make([]*display.LineData, 0, len(ownersData))
for ownerPubKey, owner := range ownersData {
line := []string{
hex.EncodeToString([]byte(ownerPubKey)),
strconv.Itoa(int(owner.numStakedNodes)),
strconv.Itoa(int(owner.numActiveNodes)),
strconv.Itoa(int(owner.numAuctionNodes)),
getPrettyValue(owner.totalTopUp, als.softAuctionConfig.denominator),
getPrettyValue(owner.topUpPerNode, als.softAuctionConfig.denominator),
getShortDisplayableBlsKeys(owner.auctionList),
}
lines = append(lines, display.NewLineData(false, line))
}

displayTable(tableHeader, lines, "Initial nodes config in auction list")
}

func (als *auctionListSelector) displayOwnersSelectedNodes(ownersData map[string]*ownerData) {
if log.GetLevel() > logger.LogDebug {
return
}

tableHeader := []string{
"Owner",
"Num staked nodes",
"TopUp per node",
"Total top up",
"Num auction nodes",
"Num qualified auction nodes",
"Num active nodes",
"Qualified top up per node",
"Selected auction list nodes",
}

lines := make([]*display.LineData, 0, len(ownersData))
for ownerPubKey, owner := range ownersData {
line := []string{
hex.EncodeToString([]byte(ownerPubKey)),
strconv.Itoa(int(owner.numStakedNodes)),
getPrettyValue(owner.topUpPerNode, als.softAuctionConfig.denominator),
getPrettyValue(owner.totalTopUp, als.softAuctionConfig.denominator),
strconv.Itoa(int(owner.numAuctionNodes)),
strconv.Itoa(int(owner.numQualifiedAuctionNodes)),
strconv.Itoa(int(owner.numActiveNodes)),
getPrettyValue(owner.qualifiedTopUpPerNode, als.softAuctionConfig.denominator),
getShortDisplayableBlsKeys(owner.auctionList[:owner.numQualifiedAuctionNodes]),
}
lines = append(lines, display.NewLineData(false, line))
}

displayTable(tableHeader, lines, "Selected nodes config from auction list")
}

func (als *auctionListSelector) displayAuctionList(
auctionList []state.ValidatorInfoHandler,
ownersData map[string]*ownerData,
numOfSelectedNodes uint32,
) {
if log.GetLevel() > logger.LogDebug {
return
}

tableHeader := []string{"Owner", "Registered key", "Qualified TopUp per node"}
lines := make([]*display.LineData, 0, len(auctionList))
blsKeysOwnerMap := getBlsKeyOwnerMap(ownersData)
for idx, validator := range auctionList {
pubKey := validator.GetPublicKey()
owner, found := blsKeysOwnerMap[string(pubKey)]
if !found {
log.Error("auctionListSelector.displayAuctionList could not find owner for",
"bls key", hex.EncodeToString(pubKey))
continue
}

qualifiedTopUp := ownersData[owner].qualifiedTopUpPerNode
horizontalLine := uint32(idx) == numOfSelectedNodes-1
line := display.NewLineData(horizontalLine, []string{
hex.EncodeToString([]byte(owner)),
hex.EncodeToString(pubKey),
getPrettyValue(qualifiedTopUp, als.softAuctionConfig.denominator),
})
lines = append(lines, line)
}

displayTable(tableHeader, lines, "Final selected nodes from auction list")
}

func getBlsKeyOwnerMap(ownersData map[string]*ownerData) map[string]string {
ret := make(map[string]string)
for ownerPubKey, owner := range ownersData {
for _, blsKey := range owner.auctionList {
ret[string(blsKey.GetPublicKey())] = ownerPubKey
}
}

return ret
}

func displayTable(tableHeader []string, lines []*display.LineData, message string) {
table, err := display.CreateTableString(tableHeader, lines)
if err != nil {
log.Error("could not create table", "error", err)
return
}

msg := fmt.Sprintf("%s\n%s", message, table)
log.Debug(msg)
}
61 changes: 61 additions & 0 deletions epochStart/metachain/auctionListDisplayer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package metachain

import (
"math"
"math/big"
"testing"

"github.com/stretchr/testify/require"
)

func TestGetPrettyValue(t *testing.T) {
require.Equal(t, "1234.0", getPrettyValue(big.NewInt(1234), big.NewInt(1)))
require.Equal(t, "123.4", getPrettyValue(big.NewInt(1234), big.NewInt(10)))
require.Equal(t, "12.34", getPrettyValue(big.NewInt(1234), big.NewInt(100)))
require.Equal(t, "1.234", getPrettyValue(big.NewInt(1234), big.NewInt(1000)))
require.Equal(t, "0.1234", getPrettyValue(big.NewInt(1234), big.NewInt(10000)))
require.Equal(t, "0.01234", getPrettyValue(big.NewInt(1234), big.NewInt(100000)))
require.Equal(t, "0.00123", getPrettyValue(big.NewInt(1234), big.NewInt(1000000)))
require.Equal(t, "0.00012", getPrettyValue(big.NewInt(1234), big.NewInt(10000000)))
require.Equal(t, "0.00001", getPrettyValue(big.NewInt(1234), big.NewInt(100000000)))
require.Equal(t, "0.00000", getPrettyValue(big.NewInt(1234), big.NewInt(1000000000)))
require.Equal(t, "0.00000", getPrettyValue(big.NewInt(1234), big.NewInt(10000000000)))

require.Equal(t, "1.0", getPrettyValue(big.NewInt(1), big.NewInt(1)))
require.Equal(t, "0.1", getPrettyValue(big.NewInt(1), big.NewInt(10)))
require.Equal(t, "0.01", getPrettyValue(big.NewInt(1), big.NewInt(100)))
require.Equal(t, "0.001", getPrettyValue(big.NewInt(1), big.NewInt(1000)))
require.Equal(t, "0.0001", getPrettyValue(big.NewInt(1), big.NewInt(10000)))
require.Equal(t, "0.00001", getPrettyValue(big.NewInt(1), big.NewInt(100000)))
require.Equal(t, "0.00000", getPrettyValue(big.NewInt(1), big.NewInt(1000000)))
require.Equal(t, "0.00000", getPrettyValue(big.NewInt(1), big.NewInt(10000000)))

oneEGLD := big.NewInt(1000000000000000000)
denominationEGLD := big.NewInt(int64(math.Pow10(18)))

require.Equal(t, "0.00000", getPrettyValue(big.NewInt(0), denominationEGLD))
require.Equal(t, "1.00000", getPrettyValue(oneEGLD, denominationEGLD))
require.Equal(t, "1.10000", getPrettyValue(big.NewInt(1100000000000000000), denominationEGLD))
require.Equal(t, "1.10000", getPrettyValue(big.NewInt(1100000000000000001), denominationEGLD))
require.Equal(t, "1.11000", getPrettyValue(big.NewInt(1110000000000000001), denominationEGLD))
require.Equal(t, "0.11100", getPrettyValue(big.NewInt(111000000000000001), denominationEGLD))
require.Equal(t, "0.01110", getPrettyValue(big.NewInt(11100000000000001), denominationEGLD))
require.Equal(t, "0.00111", getPrettyValue(big.NewInt(1110000000000001), denominationEGLD))
require.Equal(t, "0.00011", getPrettyValue(big.NewInt(111000000000001), denominationEGLD))
require.Equal(t, "0.00001", getPrettyValue(big.NewInt(11100000000001), denominationEGLD))
require.Equal(t, "0.00000", getPrettyValue(big.NewInt(1110000000001), denominationEGLD))
require.Equal(t, "0.00000", getPrettyValue(big.NewInt(111000000001), denominationEGLD))

require.Equal(t, "2.00000", getPrettyValue(big.NewInt(0).Mul(oneEGLD, big.NewInt(2)), denominationEGLD))
require.Equal(t, "20.00000", getPrettyValue(big.NewInt(0).Mul(oneEGLD, big.NewInt(20)), denominationEGLD))
require.Equal(t, "2000000.00000", getPrettyValue(big.NewInt(0).Mul(oneEGLD, big.NewInt(2000000)), denominationEGLD))

require.Equal(t, "3.22220", getPrettyValue(big.NewInt(0).Add(oneEGLD, big.NewInt(2222200000000000000)), denominationEGLD))
require.Equal(t, "1.22222", getPrettyValue(big.NewInt(0).Add(oneEGLD, big.NewInt(222220000000000000)), denominationEGLD))
require.Equal(t, "1.02222", getPrettyValue(big.NewInt(0).Add(oneEGLD, big.NewInt(22222000000000000)), denominationEGLD))
require.Equal(t, "1.00222", getPrettyValue(big.NewInt(0).Add(oneEGLD, big.NewInt(2222200000000000)), denominationEGLD))
require.Equal(t, "1.00022", getPrettyValue(big.NewInt(0).Add(oneEGLD, big.NewInt(222220000000000)), denominationEGLD))
require.Equal(t, "1.00002", getPrettyValue(big.NewInt(0).Add(oneEGLD, big.NewInt(22222000000000)), denominationEGLD))
require.Equal(t, "1.00000", getPrettyValue(big.NewInt(0).Add(oneEGLD, big.NewInt(2222200000000)), denominationEGLD))
require.Equal(t, "1.00000", getPrettyValue(big.NewInt(0).Add(oneEGLD, big.NewInt(222220000000)), denominationEGLD))
}