go-ethereum/consensus/lcp/epoch_cotext.go
2018-08-20 14:41:08 +08:00

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()
}
}