package consensus
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
. "github.com/tendermint/tendermint/blocks"
|
|
. "github.com/tendermint/tendermint/common"
|
|
. "github.com/tendermint/tendermint/state"
|
|
)
|
|
|
|
var (
|
|
consensusStateKey = []byte("consensusState")
|
|
)
|
|
|
|
// Tracks consensus state across block heights and rounds.
|
|
type ConsensusState struct {
|
|
mtx sync.Mutex
|
|
height uint32 // Height we are working on.
|
|
validatorsR0 *ValidatorSet // A copy of the validators at round 0
|
|
lockedProposal *BlockPartSet // A BlockPartSet of the locked proposal.
|
|
startTime time.Time // Start of round 0 for this height.
|
|
commits *VoteSet // Commits for this height.
|
|
roundState *RoundState // The RoundState object for the current round.
|
|
commitTime time.Time // Time at which a block was found to be committed by +2/3.
|
|
}
|
|
|
|
func NewConsensusState(state *State) *ConsensusState {
|
|
cs := &ConsensusState{}
|
|
cs.Update(state)
|
|
return cs
|
|
}
|
|
|
|
func (cs *ConsensusState) LockProposal(blockPartSet *BlockPartSet) {
|
|
cs.mtx.Lock()
|
|
defer cs.mtx.Unlock()
|
|
cs.lockedProposal = blockPartSet
|
|
}
|
|
|
|
func (cs *ConsensusState) UnlockProposal() {
|
|
cs.mtx.Lock()
|
|
defer cs.mtx.Unlock()
|
|
cs.lockedProposal = nil
|
|
}
|
|
|
|
func (cs *ConsensusState) LockedProposal() *BlockPartSet {
|
|
cs.mtx.Lock()
|
|
defer cs.mtx.Unlock()
|
|
return cs.lockedProposal
|
|
}
|
|
|
|
func (cs *ConsensusState) RoundState() *RoundState {
|
|
cs.mtx.Lock()
|
|
defer cs.mtx.Unlock()
|
|
return cs.roundState
|
|
}
|
|
|
|
// Primarily gets called upon block commit by ConsensusManager.
|
|
func (cs *ConsensusState) Update(state *State) {
|
|
cs.mtx.Lock()
|
|
defer cs.mtx.Unlock()
|
|
|
|
// Sanity check state.
|
|
stateHeight := state.Height()
|
|
if stateHeight > 0 && stateHeight != cs.height+1 {
|
|
Panicf("Update() expected state height of %v but found %v", cs.height+1, stateHeight)
|
|
}
|
|
|
|
// Reset fields based on state.
|
|
cs.height = stateHeight
|
|
cs.validatorsR0 = state.Validators().Copy() // NOTE: immutable.
|
|
cs.lockedProposal = nil
|
|
cs.startTime = state.CommitTime().Add(newBlockWaitDuration) // NOTE: likely future time.
|
|
cs.commits = NewVoteSet(stateHeight, 0, VoteTypeCommit, cs.validatorsR0)
|
|
|
|
// Setup the roundState
|
|
cs.roundState = nil
|
|
cs.setupRound(0)
|
|
|
|
}
|
|
|
|
// If cs.roundState isn't at round, set up new roundState at round.
|
|
func (cs *ConsensusState) SetupRound(round uint16) {
|
|
cs.mtx.Lock()
|
|
defer cs.mtx.Unlock()
|
|
if cs.roundState != nil && cs.roundState.Round >= round {
|
|
return
|
|
}
|
|
cs.setupRound(round)
|
|
}
|
|
|
|
// Initialize roundState for given round.
|
|
// Involves incrementing validators for each past rand.
|
|
func (cs *ConsensusState) setupRound(round uint16) {
|
|
// Increment validator accums as necessary.
|
|
// We need to start with cs.validatorsR0 or cs.roundState.Validators
|
|
var validators *ValidatorSet
|
|
var validatorsRound uint16
|
|
if cs.roundState == nil {
|
|
// We have no roundState so we start from validatorsR0 at round 0.
|
|
validators = cs.validatorsR0.Copy()
|
|
validatorsRound = 0
|
|
} else {
|
|
// We have a previous roundState so we start from that.
|
|
validators = cs.roundState.Validators.Copy()
|
|
validatorsRound = cs.roundState.Round
|
|
}
|
|
// Increment all the way to round.
|
|
for r := validatorsRound; r < round; r++ {
|
|
validators.IncrementAccum()
|
|
}
|
|
|
|
roundState := NewRoundState(cs.height, round, cs.startTime, validators, cs.commits)
|
|
cs.roundState = roundState
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const (
|
|
RoundStepStart = uint8(0x00) // Round started.
|
|
RoundStepProposal = uint8(0x01) // Did propose, broadcasting proposal.
|
|
RoundStepBareVotes = uint8(0x02) // Did vote bare, broadcasting bare votes.
|
|
RoundStepPrecommits = uint8(0x03) // Did precommit, broadcasting precommits.
|
|
RoundStepCommitOrUnlock = uint8(0x04) // We committed at this round -- do not progress to the next round.
|
|
)
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// RoundState encapsulates all the state needed to engage in the consensus protocol.
|
|
type RoundState struct {
|
|
Height uint32 // Immutable
|
|
Round uint16 // Immutable
|
|
StartTime time.Time // Time in which consensus started for this height.
|
|
Expires time.Time // Time after which this round is expired.
|
|
Proposer *Validator // The proposer to propose a block for this round.
|
|
Validators *ValidatorSet // All validators with modified accumPower for this round.
|
|
Proposal *BlockPartSet // All block parts received for this round.
|
|
RoundBareVotes *VoteSet // All votes received for this round.
|
|
RoundPrecommits *VoteSet // All precommits received for this round.
|
|
Commits *VoteSet // A shared object for all commit votes of this height.
|
|
|
|
mtx sync.Mutex
|
|
step uint8 // mutable
|
|
}
|
|
|
|
func NewRoundState(height uint32, round uint16, startTime time.Time,
|
|
validators *ValidatorSet, commits *VoteSet) *RoundState {
|
|
|
|
proposer := validators.GetProposer()
|
|
blockPartSet := NewBlockPartSet(height, nil)
|
|
roundBareVotes := NewVoteSet(height, round, VoteTypeBare, validators)
|
|
roundPrecommits := NewVoteSet(height, round, VoteTypePrecommit, validators)
|
|
|
|
rs := &RoundState{
|
|
Height: height,
|
|
Round: round,
|
|
StartTime: startTime,
|
|
Expires: calcRoundStartTime(round+1, startTime),
|
|
Proposer: proposer,
|
|
Validators: validators,
|
|
Proposal: blockPartSet,
|
|
RoundBareVotes: roundBareVotes,
|
|
RoundPrecommits: roundPrecommits,
|
|
Commits: commits,
|
|
|
|
step: RoundStepStart,
|
|
}
|
|
return rs
|
|
}
|
|
|
|
// "source" is typically the Peer.Key of the peer that gave us this vote.
|
|
func (rs *RoundState) AddVote(vote *Vote, source string) (added bool, rank uint8, err error) {
|
|
switch vote.Type {
|
|
case VoteTypeBare:
|
|
return rs.RoundBareVotes.AddVote(vote, source)
|
|
case VoteTypePrecommit:
|
|
return rs.RoundPrecommits.AddVote(vote, source)
|
|
case VoteTypeCommit:
|
|
return rs.Commits.AddVote(vote, source)
|
|
default:
|
|
panic("Unknown vote type")
|
|
}
|
|
}
|
|
|
|
func (rs *RoundState) Expired() bool {
|
|
return time.Now().After(rs.Expires)
|
|
}
|
|
|
|
func (rs *RoundState) Step() uint8 {
|
|
rs.mtx.Lock()
|
|
defer rs.mtx.Unlock()
|
|
return rs.step
|
|
}
|
|
|
|
func (rs *RoundState) SetStep(step uint8) bool {
|
|
rs.mtx.Lock()
|
|
defer rs.mtx.Unlock()
|
|
if rs.step < step {
|
|
rs.step = step
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|