tendermint/consensus/state.go

205 lines
6.2 KiB
Go

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 ConsensusAgent.
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
}
}