bsc/core/vote/vote_manager.go

220 lines
7.1 KiB
Go
Raw Normal View History

package vote
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/params"
)
// VoteManager will handle the vote produced by self.
type VoteManager struct {
mux *event.TypeMux
chain *core.BlockChain
chainconfig *params.ChainConfig
chainHeadCh chan core.ChainHeadEvent
chainHeadSub event.Subscription
pool *VotePool
signer *VoteSigner
journal *VoteJournal
engine consensus.PoSA
}
func NewVoteManager(mux *event.TypeMux, chainconfig *params.ChainConfig, chain *core.BlockChain, pool *VotePool, journalPath, blsPasswordPath, blsWalletPath string, engine consensus.PoSA) (*VoteManager, error) {
voteManager := &VoteManager{
mux: mux,
chain: chain,
chainconfig: chainconfig,
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
pool: pool,
engine: engine,
}
// Create voteSigner.
voteSigner, err := NewVoteSigner(blsPasswordPath, blsWalletPath)
if err != nil {
return nil, err
}
log.Info("Create voteSigner successfully")
voteManager.signer = voteSigner
// Create voteJournal
voteJournal, err := NewVoteJournal(journalPath)
if err != nil {
return nil, err
}
log.Info("Create voteJournal successfully")
voteManager.journal = voteJournal
// Subscribe to chain head event.
voteManager.chainHeadSub = voteManager.chain.SubscribeChainHeadEvent(voteManager.chainHeadCh)
go voteManager.loop()
return voteManager, nil
}
func (voteManager *VoteManager) loop() {
log.Debug("vote manager routine loop started")
events := voteManager.mux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{})
defer func() {
log.Debug("vote manager loop defer func occur")
if !events.Closed() {
log.Debug("event not closed, unsubscribed by vote manager loop")
events.Unsubscribe()
}
}()
dlEventCh := events.Chan()
startVote := true
for {
select {
case ev := <-dlEventCh:
if ev == nil {
log.Debug("dlEvent is nil, continue")
continue
}
switch ev.Data.(type) {
case downloader.StartEvent:
log.Debug("downloader is in startEvent mode, will not startVote")
startVote = false
case downloader.FailedEvent:
log.Debug("downloader is in FailedEvent mode, set startVote flag as true")
startVote = true
case downloader.DoneEvent:
log.Debug("downloader is in DoneEvent mode, set the startVote flag to true")
startVote = true
}
case cHead := <-voteManager.chainHeadCh:
if !startVote {
log.Debug("startVote flag is false, continue")
continue
}
if cHead.Block == nil {
log.Debug("cHead.Block is nil, continue")
continue
}
curHead := cHead.Block.Header()
// Check if cur validator is within the validatorSet at curHead
if !voteManager.engine.IsActiveValidatorAt(voteManager.chain, curHead) {
log.Debug("cur validator is not within the validatorSet at curHead")
continue
}
// Vote for curBlockHeader block.
vote := &types.VoteData{
TargetNumber: curHead.Number.Uint64(),
TargetHash: curHead.Hash(),
}
voteMessage := &types.VoteEnvelope{
Data: vote,
}
// Put Vote into journal and VotesPool if we are active validator and allow to sign it.
if ok, sourceNumber, sourceHash := voteManager.UnderRules(curHead); ok {
log.Debug("curHead is underRules for voting")
if sourceHash == (common.Hash{}) {
log.Debug("sourceHash is empty")
continue
}
voteMessage.Data.SourceNumber = sourceNumber
voteMessage.Data.SourceHash = sourceHash
if err := voteManager.signer.SignVote(voteMessage); err != nil {
log.Error("Failed to sign vote", "err", err)
votesSigningErrorMetric(vote.TargetNumber, vote.TargetHash).Inc(1)
continue
}
if err := voteManager.journal.WriteVote(voteMessage); err != nil {
log.Error("Failed to write vote into journal", "err", err)
voteJournalError.Inc(1)
continue
}
log.Debug("vote manager produced vote", "votedBlockNumber", voteMessage.Data.TargetNumber, "votedBlockHash", voteMessage.Data.TargetHash, "voteMessageHash", voteMessage.Hash())
voteManager.pool.PutVote(voteMessage)
votesManagerMetric(vote.TargetNumber, vote.TargetHash).Inc(1)
}
case <-voteManager.chainHeadSub.Err():
log.Debug("voteManager subscribed chainHead failed")
return
}
}
}
// UnderRules checks if the produced header under the following rules:
// A validator must not publish two distinct votes for the same height. (Rule 1)
// A validator must not vote within the span of its other votes . (Rule 2)
// Validators always vote for their canonical chains latest block. (Rule 3)
func (voteManager *VoteManager) UnderRules(header *types.Header) (bool, uint64, common.Hash) {
sourceNumber, sourceHash, err := voteManager.engine.GetJustifiedNumberAndHash(voteManager.chain, header)
if err != nil {
log.Error("failed to get the highest justified number and hash at cur header", "curHeader's BlockNumber", header.Number, "curHeader's BlockHash", header.Hash())
return false, 0, common.Hash{}
}
targetNumber := header.Number.Uint64()
voteDataBuffer := voteManager.journal.voteDataBuffer
//Rule 1: A validator must not publish two distinct votes for the same height.
if voteDataBuffer.Contains(targetNumber) {
log.Debug("err: A validator must not publish two distinct votes for the same height.")
return false, 0, common.Hash{}
}
//Rule 2: A validator must not vote within the span of its other votes.
for blockNumber := sourceNumber + 1; blockNumber < targetNumber; blockNumber++ {
if voteDataBuffer.Contains(blockNumber) {
voteData, ok := voteDataBuffer.Get(blockNumber)
if !ok {
log.Error("Failed to get voteData info from LRU cache.")
continue
}
if voteData.(*types.VoteData).SourceNumber > sourceNumber {
log.Debug("error: cur vote is within the span of other votes")
return false, 0, common.Hash{}
}
}
}
for blockNumber := targetNumber + 1; blockNumber <= targetNumber+upperLimitOfVoteBlockNumber; blockNumber++ {
if voteDataBuffer.Contains(blockNumber) {
voteData, ok := voteDataBuffer.Get(blockNumber)
if !ok {
log.Error("Failed to get voteData info from LRU cache.")
continue
}
if voteData.(*types.VoteData).SourceNumber < sourceNumber {
log.Debug("error: other votes are within span of cur vote")
return false, 0, common.Hash{}
}
}
}
// Rule 3: Validators always vote for their canonical chains latest block.
// Since the header subscribed to is the canonical chain, so this rule is satisified by default.
log.Debug("All three rules check passed")
return true, sourceNumber, sourceHash
}
// Metrics to monitor if voteManager worked in the expetected logic.
func votesManagerMetric(blockNumber uint64, blockHash common.Hash) metrics.Gauge {
return metrics.GetOrRegisterGauge(fmt.Sprintf("voteManager/blockNumber/%d/blockHash/%s", blockNumber, blockHash), nil)
}