mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
226 lines
7.1 KiB
Go
226 lines
7.1 KiB
Go
package lcp
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"math/rand"
|
|
"sort"
|
|
|
|
"github.com/pavelkrolevets/go-ethereum/common"
|
|
"github.com/pavelkrolevets/go-ethereum/core/state"
|
|
"github.com/pavelkrolevets/go-ethereum/core/types"
|
|
"github.com/pavelkrolevets/go-ethereum/crypto"
|
|
"github.com/pavelkrolevets/go-ethereum/log"
|
|
"github.com/pavelkrolevets/go-ethereum/trie"
|
|
)
|
|
|
|
type EpochContext struct {
|
|
TimeStamp int64
|
|
Context *types.LCPContext
|
|
statedb *state.StateDB
|
|
}
|
|
|
|
// countVotes
|
|
func (ec *EpochContext) countVotes() (votes map[common.Address]*big.Int, err error) {
|
|
votes = map[common.Address]*big.Int{}
|
|
delegateTrie := ec.Context.DelegateTrie()
|
|
candidateTrie := ec.Context.CandidateTrie()
|
|
statedb := ec.statedb
|
|
|
|
iterCandidate := trie.NewIterator(candidateTrie.NodeIterator(nil))
|
|
existCandidate := iterCandidate.Next()
|
|
if !existCandidate {
|
|
return votes, errors.New("no candidates")
|
|
}
|
|
for existCandidate {
|
|
candidate := iterCandidate.Value
|
|
candidateAddr := common.BytesToAddress(candidate)
|
|
delegateIterator := trie.NewIterator(delegateTrie.PrefixIterator(candidate))
|
|
existDelegator := delegateIterator.Next()
|
|
if !existDelegator {
|
|
votes[candidateAddr] = new(big.Int)
|
|
existCandidate = iterCandidate.Next()
|
|
continue
|
|
}
|
|
for existDelegator {
|
|
delegator := delegateIterator.Value
|
|
score, ok := votes[candidateAddr]
|
|
if !ok {
|
|
score = new(big.Int)
|
|
}
|
|
delegatorAddr := common.BytesToAddress(delegator)
|
|
weight := statedb.GetBalance(delegatorAddr)
|
|
score.Add(score, weight)
|
|
votes[candidateAddr] = score
|
|
existDelegator = delegateIterator.Next()
|
|
}
|
|
existCandidate = iterCandidate.Next()
|
|
}
|
|
return votes, nil
|
|
}
|
|
|
|
func (ec *EpochContext) kickoutValidator(epoch int64) error {
|
|
validators, err := ec.Context.GetValidators()
|
|
var epochDuration int64
|
|
var blockInterval int64
|
|
var maxValidatorSize int64
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get validator: %s", err)
|
|
}
|
|
if len(validators) == 0 {
|
|
return errors.New("no validator could be kickout")
|
|
}
|
|
|
|
epochDuration = ec.Context.GetEpochInterval()
|
|
// First epoch duration may lt epoch interval,
|
|
// while the first block time wouldn't always align with epoch interval,
|
|
// so caculate the first epoch duartion with first block time instead of epoch interval,
|
|
// prevent the validators were kickout incorrectly.
|
|
if ec.TimeStamp-timeOfFirstBlock < epochDuration {
|
|
epochDuration = ec.TimeStamp - timeOfFirstBlock
|
|
}
|
|
blockInterval = ec.Context.GetPeriodBlock()
|
|
maxValidatorSize = ec.Context.GetMaxValidators()
|
|
needKickoutValidators := sortableAddresses{}
|
|
for _, validator := range validators {
|
|
key := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(key, uint64(epoch))
|
|
key = append(key, validator.Bytes()...)
|
|
cnt := int64(0)
|
|
if cntBytes := ec.Context.MintCntTrie().Get(key); cntBytes != nil {
|
|
cnt = int64(binary.BigEndian.Uint64(cntBytes))
|
|
}
|
|
if cnt < epochDuration/blockInterval/ maxValidatorSize /2 {
|
|
// not active validators need kickout
|
|
needKickoutValidators = append(needKickoutValidators, &sortableAddress{validator, big.NewInt(cnt)})
|
|
}
|
|
}
|
|
// no validators need kickout
|
|
needKickoutValidatorCnt := len(needKickoutValidators)
|
|
if needKickoutValidatorCnt <= 0 {
|
|
return nil
|
|
}
|
|
sort.Sort(sort.Reverse(needKickoutValidators))
|
|
|
|
candidateCount := 0
|
|
iter := trie.NewIterator(ec.Context.CandidateTrie().NodeIterator(nil))
|
|
for iter.Next() {
|
|
candidateCount++
|
|
if candidateCount >= needKickoutValidatorCnt+safeSize {
|
|
break
|
|
}
|
|
}
|
|
|
|
for i, validator := range needKickoutValidators {
|
|
// ensure candidate count greater than or equal to safeSize
|
|
if candidateCount <= safeSize {
|
|
log.Info("No more candidate can be kickout", "prevEpochID", epoch, "candidateCount", candidateCount, "needKickoutCount", len(needKickoutValidators)-i)
|
|
return nil
|
|
}
|
|
|
|
if err := ec.Context.KickoutCandidate(validator.address); err != nil {
|
|
return err
|
|
}
|
|
// if kickout success, candidateCount minus 1
|
|
candidateCount--
|
|
log.Info("Kickout candidate", "prevEpochID", epoch, "candidate", validator.address.String(), "mintCnt", validator.weight.String())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ec *EpochContext) lookupValidator(now int64) (validator common.Address, err error) {
|
|
validator = common.Address{}
|
|
offset := now % ec.Context.GetEpochInterval()
|
|
if offset%ec.Context.GetPeriodBlock() != 0 {
|
|
return common.Address{}, ErrInvalidMintBlockTime
|
|
}
|
|
offset /= ec.Context.GetPeriodBlock()
|
|
|
|
validators, err := ec.Context.GetValidators()
|
|
if err != nil {
|
|
return common.Address{}, err
|
|
}
|
|
validatorSize := len(validators)
|
|
if validatorSize == 0 {
|
|
return common.Address{}, errors.New("failed to lookup validator")
|
|
}
|
|
offset %= int64(validatorSize)
|
|
return validators[offset], nil
|
|
}
|
|
// Changed LCP context constants to vars from the tries
|
|
func (ec *EpochContext) tryElect(genesis, parent *types.Header) error {
|
|
genesisEpoch := genesis.Time.Int64() / ec.Context.GetEpochInterval()
|
|
prevEpoch := parent.Time.Int64() / ec.Context.GetEpochInterval()
|
|
currentEpoch := ec.TimeStamp / ec.Context.GetEpochInterval()
|
|
safeSize:= int(ec.Context.GetMaxValidators()*2/3 + 1)
|
|
|
|
prevEpochIsGenesis := prevEpoch == genesisEpoch
|
|
if prevEpochIsGenesis && prevEpoch < currentEpoch {
|
|
prevEpoch = currentEpoch - 1
|
|
}
|
|
|
|
prevEpochBytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(prevEpochBytes, uint64(prevEpoch))
|
|
iter := trie.NewIterator(ec.Context.MintCntTrie().PrefixIterator(prevEpochBytes))
|
|
for i := prevEpoch; i < currentEpoch; i++ {
|
|
// if prevEpoch is not genesis, kickout not active candidate
|
|
if !prevEpochIsGenesis && iter.Next() {
|
|
if err := ec.kickoutValidator(prevEpoch); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
votes, err := ec.countVotes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
candidates := sortableAddresses{}
|
|
for candidate, cnt := range votes {
|
|
candidates = append(candidates, &sortableAddress{candidate, cnt})
|
|
}
|
|
if len(candidates) < safeSize {
|
|
return errors.New("too few candidates")
|
|
}
|
|
sort.Sort(candidates)
|
|
if len(candidates) > int(ec.Context.GetMaxValidators()) {
|
|
candidates = candidates[:ec.Context.GetMaxValidators()]
|
|
}
|
|
|
|
// shuffle candidates
|
|
seed := int64(binary.LittleEndian.Uint32(crypto.Keccak512(parent.Hash().Bytes()))) + i
|
|
r := rand.New(rand.NewSource(seed))
|
|
for i := len(candidates) - 1; i > 0; i-- {
|
|
j := int(r.Int31n(int32(i + 1)))
|
|
candidates[i], candidates[j] = candidates[j], candidates[i]
|
|
}
|
|
sortedValidators := make([]common.Address, 0)
|
|
for _, candidate := range candidates {
|
|
sortedValidators = append(sortedValidators, candidate.address)
|
|
}
|
|
|
|
epochTrie, _ := types.NewEpochTrie(common.Hash{}, ec.Context.DB())
|
|
ec.Context.SetEpoch(epochTrie)
|
|
ec.Context.SetValidators(sortedValidators)
|
|
log.Info("Come to new epoch", "prevEpoch", i, "nextEpoch", i+1)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type sortableAddress struct {
|
|
address common.Address
|
|
weight *big.Int
|
|
}
|
|
type sortableAddresses []*sortableAddress
|
|
|
|
func (p sortableAddresses) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
func (p sortableAddresses) Len() int { return len(p) }
|
|
func (p sortableAddresses) Less(i, j int) bool {
|
|
if p[i].weight.Cmp(p[j].weight) < 0 {
|
|
return false
|
|
} else if p[i].weight.Cmp(p[j].weight) > 0 {
|
|
return true
|
|
} else {
|
|
return p[i].address.String() < p[j].address.String()
|
|
}
|
|
}
|