Skip to content

Commit

Permalink
kaiax/valset: Organize
Browse files Browse the repository at this point in the history
  • Loading branch information
blukat29 committed Dec 17, 2024
1 parent 3c715a0 commit 78dfdce
Show file tree
Hide file tree
Showing 4 changed files with 283 additions and 269 deletions.
131 changes: 0 additions & 131 deletions kaiax/valset/impl/getter.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
package impl

import (
"math/big"
"sort"

"github.com/kaiachain/kaia/blockchain/types"
"github.com/kaiachain/kaia/common"
"github.com/kaiachain/kaia/consensus/istanbul"
"github.com/kaiachain/kaia/kaiax/gov"
"github.com/kaiachain/kaia/kaiax/staking"
"github.com/kaiachain/kaia/kaiax/valset"
)

Expand All @@ -22,73 +15,6 @@ func (v *ValsetModule) GetCouncil(num uint64) ([]common.Address, error) {
}
}

func (v *ValsetModule) getCouncil(num uint64) (*valset.AddressSet, error) {
if num == 0 {
return v.getCouncilGenesis()
}

// First try to get from the (migrated) DB.
if council, ok, err := v.getCouncilDB(num); err != nil {
return nil, err
} else if ok {
return council, nil
} else {
// Then fall back to the legacy istanbul snapshot.
council, _, err := v.getCouncilFromIstanbulSnapshot(num, false)
return council, err
}
}

// getCouncilGenesis parses the genesis council from the header's extraData.
func (v *ValsetModule) getCouncilGenesis() (*valset.AddressSet, error) {
header := v.Chain.GetHeaderByNumber(0)
if header == nil {
return nil, errNoHeader
}
istanbulExtra, err := types.ExtractIstanbulExtra(header)
if err != nil {
return nil, err
}
return valset.NewAddressSet(istanbulExtra.Validators), nil
}

func (v *ValsetModule) getCouncilDB(num uint64) (*valset.AddressSet, bool, error) {
pMinVoteNum := ReadLowestScannedVoteNum(v.ChainKv)
if pMinVoteNum == nil {
return nil, false, errNoLowestScannedNum
}
nums := ReadValidatorVoteBlockNums(v.ChainKv)
if nums == nil {
return nil, false, errNoVoteBlockNums
}

voteNum := lastNumLessThan(nums, num)
if voteNum < *pMinVoteNum {
// found voteNum is not one of the scanned vote nums, i.e. the migration is not yet complete.
// Return false to indicate that the data is not yet available.
return nil, false, nil
} else {
council := valset.NewAddressSet(ReadCouncil(v.ChainKv, voteNum))
return council, true, nil
}
}

// lastNumLessThan returns the last (rightmost) number in the list that is less than the given number.
// If no such number exists, it returns 0.
// Suppose nums = [10, 20, 30]. If num = 25, the result is 20. If num = 7, the result is 0.
func lastNumLessThan(nums []uint64, num uint64) uint64 {
// idx is the smallest index that is greater than or equal to `num`.
// idx-1 is the largest index that is less than `num`.
idx := sort.Search(len(nums), func(i int) bool {
return nums[i] >= num
})
if idx > 0 && nums[idx-1] < num {
return nums[idx-1]
} else {
return 0
}
}

// GetDemotedValidators are subtract of qualified from council(N)
func (v *ValsetModule) GetDemotedValidators(num uint64) ([]common.Address, error) {
council, err := v.getCouncil(num)
Expand All @@ -114,63 +40,6 @@ func (v *ValsetModule) getQualifiedValidators(num uint64) (*valset.AddressSet, e
return council.Subtract(demoted), nil
}

// getDemotedValidators returns the demoted validators at the given block number.
func (v *ValsetModule) getDemotedValidators(council *valset.AddressSet, num uint64) (*valset.AddressSet, error) {
if num == 0 {
return valset.NewAddressSet(nil), nil
}

pset := v.GovModule.EffectiveParamSet(num)
rules := v.Chain.Config().Rules(new(big.Int).SetUint64(num))

switch istanbul.ProposerPolicy(pset.ProposerPolicy) {
case istanbul.RoundRobin, istanbul.Sticky:
// All council members are qualified for both RoundRobin and Sticky.
return valset.NewAddressSet(nil), nil
case istanbul.WeightedRandom:
// All council members are qualified for WeightedRandom before Istanbul hardfork.
if !rules.IsIstanbul {
return valset.NewAddressSet(nil), nil
}
// Otherwise, filter out based on staking amounts.
si, err := v.StakingModule.GetStakingInfo(num)
if err != nil {
return nil, err
}
return filterValidatorsIstanbul(council, si, pset), nil
default:
return nil, errInvalidProposerPolicy
}
}

func filterValidatorsIstanbul(council *valset.AddressSet, si *staking.StakingInfo, pset gov.ParamSet) *valset.AddressSet {
var (
demoted = valset.NewAddressSet(nil)
singleMode = pset.GovernanceMode == "single"
governingNode = pset.GoverningNode
minStake = pset.MinimumStake.Uint64() // in KAIA
stakingAmounts = collectStakingAmounts(council.List(), si)
)

// First filter by staking amounts.
for _, node := range council.List() {
if uint64(stakingAmounts[node]) < minStake {
demoted.Add(node)
}
}

// If all validators are demoted, then no one is demoted.
if demoted.Len() == len(council.List()) {
demoted = valset.NewAddressSet(nil)
}

// Under single governnace mode, governing node cannot be demoted.
if singleMode && demoted.Contains(governingNode) {
demoted.Remove(governingNode)
}
return demoted
}

// GetCommittee returns the current block's committee.
func (v *ValsetModule) GetCommittee(num uint64, round uint64) ([]common.Address, error) {
if num == 0 {
Expand Down
198 changes: 198 additions & 0 deletions kaiax/valset/impl/getter_council.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package impl

import (
"sort"

"github.com/kaiachain/kaia/blockchain/types"
"github.com/kaiachain/kaia/common"
"github.com/kaiachain/kaia/kaiax/gov"
"github.com/kaiachain/kaia/kaiax/gov/headergov"
"github.com/kaiachain/kaia/kaiax/valset"
)

func (v *ValsetModule) getCouncil(num uint64) (*valset.AddressSet, error) {
if num == 0 {
return v.getCouncilGenesis()
}

// First try to get from the (migrated) DB.
if council, ok, err := v.getCouncilDB(num); err != nil {
return nil, err
} else if ok {
return council, nil
} else {
// Then fall back to the legacy istanbul snapshot.
council, _, err := v.getCouncilFromIstanbulSnapshot(num, false)
return council, err
}
}

// getCouncilGenesis parses the genesis council from the header's extraData.
func (v *ValsetModule) getCouncilGenesis() (*valset.AddressSet, error) {
header := v.Chain.GetHeaderByNumber(0)
if header == nil {
return nil, errNoHeader
}
istanbulExtra, err := types.ExtractIstanbulExtra(header)
if err != nil {
return nil, err
}
return valset.NewAddressSet(istanbulExtra.Validators), nil
}

func (v *ValsetModule) getCouncilDB(num uint64) (*valset.AddressSet, bool, error) {
pMinVoteNum := ReadLowestScannedVoteNum(v.ChainKv)
if pMinVoteNum == nil {
return nil, false, errNoLowestScannedNum
}
nums := ReadValidatorVoteBlockNums(v.ChainKv)
if nums == nil {
return nil, false, errNoVoteBlockNums
}

voteNum := lastNumLessThan(nums, num)
if voteNum < *pMinVoteNum {
// found voteNum is not one of the scanned vote nums, i.e. the migration is not yet complete.
// Return false to indicate that the data is not yet available.
return nil, false, nil
} else {
council := valset.NewAddressSet(ReadCouncil(v.ChainKv, voteNum))
return council, true, nil
}
}

// lastNumLessThan returns the last (rightmost) number in the list that is less than the given number.
// If no such number exists, it returns 0.
// Suppose nums = [10, 20, 30]. If num = 25, the result is 20. If num = 7, the result is 0.
func lastNumLessThan(nums []uint64, num uint64) uint64 {
// idx is the smallest index that is greater than or equal to `num`.
// idx-1 is the largest index that is less than `num`.
idx := sort.Search(len(nums), func(i int) bool {
return nums[i] >= num
})
if idx > 0 && nums[idx-1] < num {
return nums[idx-1]
} else {
return 0
}
}

// getCouncilFromIstanbulSnapshot re-generates the council at the given targetNum.
// Returns the council at targetNum, the nearest snapshot number, and error if any.
//
// The council is calculated from the nearest istanbul snapshot (at snapshotNum)
// plus the validator votes in the range [snapshotNum+1, targetNum-1]. Note that
// snapshot at snapshotNum already reflects the validator vote at snapshotNum,
// so we apply the votes starting from snapshotNum+1.
//
// If write is true, ValidatorVoteBlockNums and Council in the extended range
// [snapshotNum+1, targetNum] are written to the database. Note that this time
// the targetNum is included in the range for completeness. This property is
// useful for snapshot interval-wise migration.
func (v *ValsetModule) getCouncilFromIstanbulSnapshot(targetNum uint64, write bool) (*valset.AddressSet, uint64, error) {
if targetNum == 0 {
council, err := v.getCouncilGenesis()
return council, 0, err
}
// Load council at the nearest istanbul snapshot. This is the result
// applying the votes up to the snapshotNum.
snapshotNum := roundDown(targetNum-1, istanbulCheckpointInterval)
header := v.Chain.GetHeaderByNumber(snapshotNum)
if header == nil {
return nil, 0, errNoHeader
}
council := valset.NewAddressSet(ReadIstanbulSnapshot(v.ChainKv, header.Hash()))
if council.Len() == 0 {
return nil, 0, ErrNoIstanbulSnapshot(snapshotNum)
}

// Apply the votes in the interval [snapshotNum+1, targetNum-1].
for n := snapshotNum + 1; n < targetNum; n++ {
if err := v.replayBlock(council, n, write); err != nil {
return nil, 0, err
}
}
// Apply the vote at targetNum to write to database, but do not return the modified council.
if write {
// Apply the vote at targetNum and write to database, but do not affect the returning council.
if err := v.replayBlock(council.Copy(), targetNum, true); err != nil {
return nil, 0, err
}
}
return council, snapshotNum, nil
}

func (v *ValsetModule) replayBlock(council *valset.AddressSet, num uint64, write bool) error {
header := v.Chain.GetHeaderByNumber(num)
if header == nil {
return errNoHeader
}
governingNode := v.GovModule.EffectiveParamSet(num).GoverningNode
if applyVote(header, council, governingNode) && write {
insertValidatorVoteBlockNums(v.ChainKv, num)
writeCouncil(v.ChainKv, num, council.List())
}
return nil
}

// applyVote modifies the given council *in-place* by the validator vote in the given header.
// governingNode, if specified, is not affected by the vote.
// Returns true if the council is modified, false otherwise.
func applyVote(header *types.Header, council *valset.AddressSet, governingNode common.Address) bool {
voteKey, addresses, ok := parseValidatorVote(header)
if !ok {
return false
}

originalSize := council.Len()
for _, address := range addresses {
if address == governingNode {
continue
}
switch voteKey {
case gov.AddValidator:
if !council.Contains(address) {
council.Add(address)
}
case gov.RemoveValidator:
if council.Contains(address) {
council.Remove(address)
}
}
}
return originalSize != council.Len()
}

func parseValidatorVote(header *types.Header) (gov.ValidatorParamName, []common.Address, bool) {
// Check that a vote exists and is a validator vote.
voteBytes := headergov.VoteBytes(header.Vote)
if len(voteBytes) == 0 {
return "", nil, false
}
vote, err := voteBytes.ToVoteData()
if err != nil {
return "", nil, false
}
voteKey := gov.ValidatorParamName(vote.Name())
_, isValidatorVote := gov.ValidatorParams[voteKey]
if !isValidatorVote {
return "", nil, false
}

// Type cast the vote value. It can be a single address or a list of addresses.
var addresses []common.Address
switch voteValue := vote.Value().(type) {
case common.Address:
addresses = []common.Address{voteValue}
case []common.Address:
addresses = voteValue
default:
return "", nil, false
}

return voteKey, addresses, true
}

func roundDown(n, p uint64) uint64 {
return n - (n % p)
}
Loading

0 comments on commit 78dfdce

Please sign in to comment.