Skip to content

Commit

Permalink
Update validator refresh interval after kaia fork
Browse files Browse the repository at this point in the history
  • Loading branch information
hyeonLewis committed May 9, 2024
1 parent 4906425 commit fa99a3b
Show file tree
Hide file tree
Showing 15 changed files with 126 additions and 39 deletions.
2 changes: 1 addition & 1 deletion blockchain/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
if params.IsCheckpointInterval(num) {
bc.db.DeleteIstanbulSnapshot(hash)
}
if bc.Config().Istanbul.ProposerPolicy == params.WeightedRandom && params.IsStakingUpdateInterval(num) {
if bc.Config().Istanbul.ProposerPolicy == params.WeightedRandom && !bc.Config().IsDragonForkEnabled(new(big.Int).SetUint64(num)) && params.IsStakingUpdateInterval(num) {
bc.db.DeleteStakingInfo(num)
}
}
Expand Down
1 change: 1 addition & 0 deletions consensus/istanbul/backend/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,7 @@ func setTestStakingInfo(amounts []uint64) *reward.StakingManager {

// Save old StakingManager, overwrite to the fake one.
oldStakingManager := reward.GetStakingManager()
reward.SetTestStakingManagerIsDragonEnabled(func(u uint64) bool { return false })
reward.SetTestStakingManagerWithStakingInfoCache(stakingInfo)
return oldStakingManager
}
Expand Down
6 changes: 5 additions & 1 deletion consensus/istanbul/backend/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,13 @@ func (s *Snapshot) apply(headers []*types.Header, gov governance.Engine, addr co
govNode := pset.GoverningNode()
minStaking := pset.MinimumStakeBig().Uint64()

if err := snap.ValSet.RefreshValSet(number+1, chain.Config(), isSingle, govNode, minStaking); err != nil {
logger.Trace("Skip refreshing validators while creating snapshot", "snap.Number", snap.Number, "err", err)
}

pHeader := chain.GetHeaderByNumber(params.CalcProposerBlockNumber(number + 1))
if pHeader != nil {
if err := snap.ValSet.Refresh(pHeader.Hash(), pHeader.Number.Uint64(), chain.Config(), isSingle, govNode, minStaking); err != nil {
if err := snap.ValSet.RefreshProposer(pHeader.Hash(), pHeader.Number.Uint64(), chain.Config()); err != nil {
// There are three error cases and they just don't refresh proposers
// (1) no validator at all
// (2) invalid formatted hash
Expand Down
2 changes: 2 additions & 0 deletions consensus/istanbul/backend/testutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,9 @@ func makeTestStakingManager(addrs []common.Address, amounts []uint64) *reward.St

// Save old StakingManager, overwrite with the fake one.
oldStakingManager := reward.GetStakingManager()
reward.SetTestStakingManagerIsDragonEnabled(func(u uint64) bool { return false })
reward.SetTestStakingManagerWithStakingInfoCache(info)

return oldStakingManager
}

Expand Down
5 changes: 4 additions & 1 deletion consensus/istanbul/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,11 @@ type ValidatorSet interface {

IsSubSet() bool

// Refreshes a list of validators at given blockNum
RefreshValSet(blockNum uint64, config *params.ChainConfig, isSingle bool, governingNode common.Address, minStaking uint64) error

// Refreshes a list of candidate proposers with given hash and blockNum
Refresh(hash common.Hash, blockNum uint64, config *params.ChainConfig, isSingle bool, governingNode common.Address, minStaking uint64) error
RefreshProposer(hash common.Hash, blockNum uint64, config *params.ChainConfig) error

SetBlockNum(blockNum uint64)
SetMixHash(mixHash []byte)
Expand Down
7 changes: 6 additions & 1 deletion consensus/istanbul/validator/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,9 +377,14 @@ func (valSet *defaultSet) F() int {

func (valSet *defaultSet) Policy() istanbul.ProposerPolicy { return valSet.policy }

func (valSet *defaultSet) Refresh(hash common.Hash, blockNum uint64, config *params.ChainConfig, isSingle bool, governingNode common.Address, minStaking uint64) error {
func (valSet *defaultSet) RefreshValSet(blockNum uint64, config *params.ChainConfig, isSingle bool, governingNode common.Address, minStaking uint64) error {
return nil
}

func (valSet *defaultSet) RefreshProposer(hash common.Hash, blockNum uint64, config *params.ChainConfig) error {
return nil
}

func (valSet *defaultSet) SetBlockNum(blockNum uint64) { /* Do nothing */ }
func (valSet *defaultSet) SetMixHash(mixHash []byte) { /* Do nothing */ }
func (valSet *defaultSet) Proposers() []istanbul.Validator { return nil }
Expand Down
85 changes: 60 additions & 25 deletions consensus/istanbul/validator/weighted.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ func weightedRandomProposer(valSet istanbul.ValidatorSet, lastProposer common.Ad
return nil
}

// At Refresh(), proposers is already randomly shuffled considering weights.
// At RefreshProposer(), proposers is already randomly shuffled considering weights.
// So let's just round robin this array
blockNum := weightedCouncil.blockNum
picker := (blockNum + round - params.CalcProposerBlockNumber(blockNum+1)) % uint64(numProposers)
Expand Down Expand Up @@ -551,7 +551,7 @@ func (valSet *weightedCouncil) AddValidator(address common.Address) bool {
}
}

// TODO-Klaytn-Governance the new validator is added on validators only and demoted after `Refresh` method. It is better to update here if it is demoted ones.
// TODO-Klaytn-Governance the new validator is added on validators only and demoted after `RefreshValSet` method. It is better to update here if it is demoted ones.
// TODO-Klaytn-Issue1336 Update for governance implementation. How to determine initial value for rewardAddress and votingPower ?
valSet.validators = append(valSet.validators, newWeightedValidator(address, common.Address{}, 1000, 0))

Expand Down Expand Up @@ -640,15 +640,13 @@ func (valSet *weightedCouncil) F() int {

func (valSet *weightedCouncil) Policy() istanbul.ProposerPolicy { return valSet.policy }

// Refresh recalculates up-to-date proposers only when blockNum is the proposer update interval.
// It returns an error if it can't make up-to-date proposers
// RefreshValSet recalculates up-to-date validators at the staking block number.
// It returns an error if it can't make up-to-date validators
// - due to wrong parameters
// - due to lack of staking information
// It returns no error when weightedCouncil:
// - already has up-do-date proposers
// - successfully calculated up-do-date proposers
func (valSet *weightedCouncil) Refresh(hash common.Hash, blockNum uint64, config *params.ChainConfig, isSingle bool, governingNode common.Address, minStaking uint64) error {
// TODO-Klaytn-Governance divide the following logic into two parts: proposers update / validators update
// - successfully calculated up-to-date validators
func (valSet *weightedCouncil) RefreshValSet(blockNum uint64, config *params.ChainConfig, isSingle bool, governingNode common.Address, minStaking uint64) error {
valSet.validatorMu.Lock()
defer valSet.validatorMu.Unlock()

Expand All @@ -658,25 +656,31 @@ func (valSet *weightedCouncil) Refresh(hash common.Hash, blockNum uint64, config
return errors.New("No validator")
}

hashString := strings.TrimPrefix(hash.Hex(), "0x")
if len(hashString) > 15 {
hashString = hashString[:15]
}
seed, err := strconv.ParseInt(hashString, 16, 64)
if err != nil {
return err
blockNumBig := new(big.Int).SetUint64(blockNum)
chainRules := config.Rules(blockNumBig)

var newStakingInfo *reward.StakingInfo
if !chainRules.IsDragon {
stakingBlockNum := params.CalcProposerBlockNumber(blockNum) + 1
newStakingInfo = reward.GetStakingInfo(stakingBlockNum)
} else {
// blockNum is always bigger than 0
stakingBlockNum := blockNum - 1
if config.DragonCompatibleBlock.Cmp(blockNumBig) == 0 {
// Case of the next block is a dragon fork block, which means `stakingBlockNum` is not a dragon block.
// `GetStakingInfoFromAddressBook` does not check whether `stakingBlockNum` is staking info interval.
newStakingInfo = reward.GetStakingInfoFromAddressBook(stakingBlockNum)
} else if chainRules.IsDragon {
newStakingInfo = reward.GetStakingInfo(stakingBlockNum)
}
}

newStakingInfo := reward.GetStakingInfo(blockNum + 1)
if newStakingInfo == nil {
// Just return without updating proposer
return errors.New("skip refreshing proposers due to no staking info")
}
valSet.stakingInfo = newStakingInfo

blockNumBig := new(big.Int).SetUint64(blockNum)
chainRules := config.Rules(blockNumBig)

candidates := append(valSet.validators, valSet.demotedValidators...)
weightedValidators, stakingAmounts, err := getStakingAmountsOfValidators(candidates, newStakingInfo)
if err != nil {
Expand All @@ -690,11 +694,6 @@ func (valSet *weightedCouncil) Refresh(hash common.Hash, blockNum uint64, config
valSet.setValidators(weightedValidators, demotedValidators)
}

if valSet.proposersBlockNum == blockNum {
// proposers are already refreshed
return nil
}

// weight and gini were neutralized after Kore hard fork
if chainRules.IsKore {
setZeroWeight(weightedValidators)
Expand All @@ -703,9 +702,45 @@ func (valSet *weightedCouncil) Refresh(hash common.Hash, blockNum uint64, config
calcWeight(weightedValidators, stakingAmounts, totalStaking)
}

logger.Debug("Refresh valSet done.", "blockNum", blockNum, "valSet.blockNum", valSet.blockNum, "stakingInfo.BlockNum", valSet.stakingInfo.BlockNum)

return nil
}

// RefreshProposer recalculates up-to-date proposers only when blockNum is the proposer update interval.
// It returns an error if it can't make up-to-date proposers
// - due to wrong parameters
// - due to lack of staking information
// It returns no error when weightedCouncil:
// - already has up-to-date proposers
// - successfully calculated up-to-date proposers
func (valSet *weightedCouncil) RefreshProposer(hash common.Hash, blockNum uint64, config *params.ChainConfig) error {
valSet.validatorMu.Lock()
defer valSet.validatorMu.Unlock()

// Check errors
numValidators := len(valSet.validators)
if numValidators == 0 {
return errors.New("No validator")
}

hashString := strings.TrimPrefix(hash.Hex(), "0x")
if len(hashString) > 15 {
hashString = hashString[:15]
}
seed, err := strconv.ParseInt(hashString, 16, 64)
if err != nil {
return err
}

if valSet.proposersBlockNum == blockNum {
// proposers are already refreshed
return nil
}

valSet.refreshProposers(seed, blockNum)

logger.Debug("Refresh done.", "blockNum", blockNum, "hash", hash, "valSet.blockNum", valSet.blockNum, "stakingInfo.BlockNum", valSet.stakingInfo.BlockNum)
logger.Debug("Refresh proposer done.", "blockNum", blockNum, "hash", hash, "valSet.blockNum", valSet.blockNum)
logger.Debug("New proposers calculated", "new proposers", valSet.proposers)

return nil
Expand Down
1 change: 1 addition & 0 deletions datasync/downloader/downloader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func setTestGovernance(db database.DBManager) {
origStakingManager: reward.GetStakingManager(),
}

reward.SetTestStakingManagerIsDragonEnabled(func(u uint64) bool { return false })
reward.SetTestStakingManagerWithDB(db)
params.SetStakingUpdateInterval(testStakingUpdateInterval)
}
Expand Down
1 change: 1 addition & 0 deletions node/cn/api_backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,7 @@ func headerGovTest(t *testing.T, tt *rewindTest) {
dummy := reward.StakingInfo{BlockNum: stakingUpdateInterval}
blob, err = json.Marshal(dummy)
assert.Nil(t, err)
reward.SetTestStakingManagerIsDragonEnabled(func(u uint64) bool { return false })
reward.SetTestStakingManagerWithStakingInfoCache(&dummy)
assert.NotNil(t, reward.GetStakingManager())
params.SetStakingUpdateInterval(stakingUpdateInterval)
Expand Down
4 changes: 3 additions & 1 deletion node/cn/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,9 @@ func (s *CN) Start(srvr p2p.Server) error {
s.lesServer.Start(srvr)
}

reward.StakingManagerSubscribe()
if !s.chainConfig.IsDragonForkEnabled(s.blockchain.CurrentBlock().Number()) {
reward.StakingManagerSubscribe()
}

return nil
}
Expand Down
2 changes: 2 additions & 0 deletions node/cn/handler_msg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ func TestHandleStakingInfoRequestMsg(t *testing.T) {

testBlock := uint64(4)
testStakingInfo := newStakingInfo(testBlock)
reward.SetTestStakingManagerIsDragonEnabled(func(u uint64) bool { return false })
reward.SetTestStakingManagerWithStakingInfoCache(testStakingInfo)
params.SetStakingUpdateInterval(testBlock)

Expand Down Expand Up @@ -568,6 +569,7 @@ func TestHandleStakingInfoMsg(t *testing.T) {

testBlock := uint64(4)
testStakingInfo := newStakingInfo(testBlock)
reward.SetTestStakingManagerIsDragonEnabled(func(u uint64) bool { return false })
reward.SetTestStakingManagerWithStakingInfoCache(testStakingInfo)
params.SetStakingUpdateInterval(testBlock)

Expand Down
2 changes: 1 addition & 1 deletion params/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ func (c *ChainConfig) IsKIP160ForkBlock(num *big.Int) bool {
return c.Kip160CompatibleBlock.Cmp(num) == 0
}

// IsRandaoForkBlockParent returns whethere num is one block before the randao block.
// IsRandaoForkBlockParent returns whether num is one block before the randao block.
func (c *ChainConfig) IsRandaoForkBlockParent(num *big.Int) bool {
if c.RandaoCompatibleBlock == nil || num == nil {
return false
Expand Down
7 changes: 7 additions & 0 deletions reward/reward_distributor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ func TestRewardDistributor_GetBlockReward(t *testing.T) {
},
}

SetTestStakingManagerIsDragonEnabled(func(u uint64) bool { return false })
SetTestStakingManagerWithStakingInfoCache(stakingInfo)

for i, tc := range testcases {
Expand Down Expand Up @@ -699,6 +700,7 @@ func TestRewardDistributor_CalcDeferredReward(t *testing.T) {
},
}

SetTestStakingManagerIsDragonEnabled(func(u uint64) bool { return false })
SetTestStakingManagerWithStakingInfoCache(stakingInfo)

for _, tc := range testcases {
Expand Down Expand Up @@ -826,6 +828,8 @@ func TestRewardDistributor_CalcDeferredReward_StakingInfos(t *testing.T) {
},
}

SetTestStakingManagerIsDragonEnabled(func(u uint64) bool { return false })

for i, tc := range testcases {
if tc.stakingInfo == nil {
SetTestStakingManager(nil)
Expand Down Expand Up @@ -905,6 +909,7 @@ func TestRewardDistributor_CalcDeferredReward_Remainings(t *testing.T) {
},
}

SetTestStakingManagerIsDragonEnabled(func(u uint64) bool { return false })
SetTestStakingManagerWithStakingInfoCache(stakingInfo)

for _, tc := range testcases {
Expand Down Expand Up @@ -1289,6 +1294,8 @@ func Benchmark_CalcDeferredReward(b *testing.B) {

header, rules, pset := benchSetup()

SetTestStakingManagerIsDragonEnabled(func(u uint64) bool { return false })

b.ResetTimer()
for i := 0; i < b.N; i++ {
CalcDeferredReward(header, rules, pset)
Expand Down
38 changes: 30 additions & 8 deletions reward/staking_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ var (
once sync.Once
stakingManager *StakingManager

isDragonEnabled = func(blockNum uint64) bool {
return stakingManager.blockchain.Config().IsDragonForkEnabled(new(big.Int).SetUint64(blockNum))
}

// errors for staking manager
ErrStakingManagerNotSet = errors.New("staking manager is not set")
ErrChainHeadChanNotSet = errors.New("chain head channel is not set")
Expand Down Expand Up @@ -109,6 +113,9 @@ func NewStakingManager(bc blockChain, gh governanceHelper, db stakingInfoDB) *St
// If there is no staking info in either cache, db or state trie, the node cannot make a block.
// The information in state trie is deleted after state trie migration.
blockchain.RegisterMigrationPrerequisites(func(blockNum uint64) error {
if isDragonEnabled(blockNum) {
return nil
}
if err := CheckStakingInfoStored(blockNum); err != nil {
return err
}
Expand All @@ -129,7 +136,10 @@ func GetStakingManager() *StakingManager {
// GetStakingInfo returns a stakingInfo on the staking block of the given block number.
// Note that staking block is the block on which the associated staking information is stored and used during an interval.
func GetStakingInfo(blockNum uint64) *StakingInfo {
stakingBlockNumber := params.CalcStakingBlockNumber(blockNum)
stakingBlockNumber := blockNum
if !isDragonEnabled(blockNum) {
stakingBlockNumber = params.CalcStakingBlockNumber(blockNum)
}
logger.Debug("Staking information is requested", "blockNum", blockNum, "staking block number", stakingBlockNumber)
return GetStakingInfoOnStakingBlock(stakingBlockNumber)
}
Expand Down Expand Up @@ -198,15 +208,23 @@ func updateStakingInfo(blockNum uint64) (*StakingInfo, error) {
return nil, ErrStakingManagerNotSet
}

isDragon := isDragonEnabled(blockNum)

if !isDragon && !params.IsStakingUpdateInterval(blockNum) {
return nil, fmt.Errorf("not staking block number. blockNum: %d", blockNum)
}

stakingInfo, err := getStakingInfoFromAddressBook(blockNum)
if err != nil {
return nil, err
}

// Add to DB before setting Gini; DB will contain {Gini: -1}
if err := AddStakingInfoToDB(stakingInfo); err != nil {
logger.Debug("failed to write staking info to db", "err", err, "stakingInfo", stakingInfo)
return stakingInfo, err
if !isDragon {
if err := AddStakingInfoToDB(stakingInfo); err != nil {
logger.Debug("failed to write staking info to db", "err", err, "stakingInfo", stakingInfo)
return stakingInfo, err
}
}

// Fill in Gini coeff before adding to cache
Expand Down Expand Up @@ -318,11 +336,15 @@ func CheckStakingInfoStored(blockNum uint64) error {
return ErrStakingManagerNotSet
}

stakingBlockNumber := params.CalcStakingBlockNumber(blockNum)
stakingBlockNumber := blockNum

// skip checking if staking info is stored in DB
if _, err := getStakingInfoFromDB(stakingBlockNumber); err == nil {
return nil
if !isDragonEnabled(blockNum) {
stakingBlockNumber := params.CalcStakingBlockNumber(blockNum)

// skip checking if staking info is stored in DB
if _, err := getStakingInfoFromDB(stakingBlockNumber); err == nil {
return nil
}
}

// update staking info in DB and cache from address book
Expand Down
2 changes: 2 additions & 0 deletions reward/staking_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ func generateStakingManagerTestCases() []stakingManagerTestCase {
}

func newStakingManagerForTest(t *testing.T) {
SetTestStakingManagerIsDragonEnabled(func(u uint64) bool { return false })

// test if nil
assert.Nil(t, GetStakingManager())
assert.Nil(t, GetStakingInfo(123))
Expand Down

0 comments on commit fa99a3b

Please sign in to comment.