package consensus import ( "bytes" "sync" "time" . "github.com/tendermint/tendermint/accounts" . "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/blocks" . "github.com/tendermint/tendermint/common" db_ "github.com/tendermint/tendermint/db" ) var ( consensusStateKey = []byte("consensusState") ) /* Determining the order of proposers at height h: A B C All validators A, B, and C [+10, +5, +2] (+17) Voting power [ 0, 0, 0] Genesis? [ 10, 5, 2] (+17) A [ -7, 5, 2] (-17) Round 0 proposer: A [ 3, 10, 4] (+17) B [ 3, -7, 4] (-17) Round 1 proposer: B [ 13, -2, 6] (+17) A [ -4, -2, 6] (-17) Round 2 proposer: A [ 6, 3, 8] (+17) C [ 6, 3, -9] (-17) Round 3 proposer: C [ 16, 8, -7] (+17) A [ -1, 8, -7] (-17) Round 4 proposer: A [ 9, 13, -5] (+17) B [ 9, -4, -5] (-17) Round 5 proposer: B [ 19, 1, -3] (+17) A [ 2, 1, -3] (-17) Round 6 proposer: A ........... ... For a node, once consensus has been reached at some round R, the moment the node sees +2/3 in votes for a proposal is when the consensus rounds for the *next* height h+1 begins. Round R+1 in the consensus rounds at height h+1 is the same as round R in the consensus rounds at height h (the parent block). We omit details of dealing with membership changes. */ func getProposer(validators map[uint64]*Validator) (proposer *Validator) { highestAccum := int64(0) for _, validator := range validators { if validator.Accum > highestAccum { highestAccum = validator.Accum proposer = validator } else if validator.Accum == highestAccum { if validator.Id < proposer.Id { // Seniority proposer = validator } } } return } func incrementAccum(validators map[uint64]*Validator) { totalDelta := int64(0) for _, validator := range validators { validator.Accum += int64(validator.VotingPower) totalDelta += int64(validator.VotingPower) } proposer := getProposer(validators) proposer.Accum -= totalDelta // NOTE: sum(validators) here should be zero. if true { totalAccum := int64(0) for _, validator := range validators { totalAccum += validator.Accum } if totalAccum != 0 { Panicf("Total Accum of validators did not equal 0. Got: ", totalAccum) } } } // Creates a deep copy of validators. // Caller can then modify the resulting validators' .Accum field without // modifying the original *Validator's. func copyValidators(validators map[uint64]*Validator) map[uint64]*Validator { mapCopy := map[uint64]*Validator{} for _, val := range validators { mapCopy[val.Id] = val.Copy() } return mapCopy } //----------------------------------------------------------------------------- // Handles consensus state tracking across block heights. // NOTE: When adding more fields, also reset it in Load() and CommitBlock() type ConsensusStateControl struct { mtx sync.Mutex db db_.Db // Where we store the validators list & other data. validatorsR0 map[uint64]*Validator // A copy of the validators at round 0 privValidator *PrivValidator // PrivValidator used to participate, if present. accountStore *AccountStore // Account storage height uint32 // Height we are working on. lockedProposal *BlockPartSet // A BlockPartSet of the locked proposal. startTime time.Time // Start of round 0 for this height. roundState *RoundState // The RoundState object for the current round. commits *VoteSet // Commits for this height. } func NewConsensusStateControl(db db_.Db, accountStore *AccountStore) *ConsensusStateControl { csc := &ConsensusStateControl{ db: db, accountStore: accountStore, } csc.Load() return csc } // Load the current state from db. func (csc *ConsensusStateControl) Load() { csc.mtx.Lock() defer csc.mtx.Unlock() buf := csc.db.Get(consensusStateKey) if len(buf) == 0 { height := uint32(0) validators := make(map[uint64]*Validator) // XXX BOOTSTRAP startTime := time.Now() // XXX BOOTSTRAP csc.setupHeight(height, validators, startTime) } else { reader := bytes.NewReader(buf) height := Readuint32(reader) validators := make(map[uint64]*Validator) startTime := ReadTime(reader) for reader.Len() > 0 { validator := ReadValidator(reader) validators[validator.Id] = validator } csc.setupHeight(height, validators, startTime.Time) } } // Save the current state onto db. // Doesn't save the round state, just initial state at round 0. func (csc *ConsensusStateControl) Save() { csc.mtx.Lock() defer csc.mtx.Unlock() var buf bytes.Buffer UInt32(csc.height).WriteTo(&buf) Time{csc.startTime}.WriteTo(&buf) for _, validator := range csc.validatorsR0 { validator.WriteTo(&buf) } csc.db.Set(consensusStateKey, buf.Bytes()) } // Finds more blocks from blockStore and commits them. func (csc *ConsensusStateControl) Update(blockStore *BlockStore) { csc.mtx.Lock() defer csc.mtx.Unlock() for h := csc.height + 1; h <= blockStore.Height(); h++ { block := blockStore.LoadBlock(h) // TODO: would be better to be able to override // the block commit time, but in the meantime, // just use the block time as proposed by the proposer. csc.CommitBlock(block, block.Header.Time.Time) } } func (csc *ConsensusStateControl) PrivValidator() *PrivValidator { csc.mtx.Lock() defer csc.mtx.Unlock() return csc.privValidator } func (csc *ConsensusStateControl) SetPrivValidator(privValidator *PrivValidator) error { csc.mtx.Lock() defer csc.mtx.Unlock() if csc.privValidator != nil { panic("ConsensusStateControl privValidator already set.") } csc.privValidator = privValidator return nil } // Set blockPartSet to nil to unlock. func (csc *ConsensusStateControl) LockProposal(blockPartSet *BlockPartSet) { csc.mtx.Lock() defer csc.mtx.Unlock() csc.lockedProposal = blockPartSet } func (csc *ConsensusStateControl) LockedProposal() *BlockPartSet { csc.mtx.Lock() defer csc.mtx.Unlock() return csc.lockedProposal } func (csc *ConsensusStateControl) StageBlock(block *Block) error { // XXX implement staging. return nil } // NOTE: assumes that block is valid. // NOTE: the block should be saved on the BlockStore before commiting here. // commitTime is usually set to the system clock time (time.Now()). func (csc *ConsensusStateControl) CommitBlock(block *Block, commitTime time.Time) error { csc.mtx.Lock() defer csc.mtx.Unlock() // Ensure that block is the next block needed. if block.Height != csc.height { return Errorf("Cannot commit block %v to csc. Expected height %v", block, csc.height+1) } // Update validator. validators := copyValidators(csc.validatorsR0) incrementAccum(validators) // TODO if there are new validators in the block, add them. // XXX: it's not commitTime we want... csc.setupHeight(block.Height+1, validators, commitTime) // Save the state. csc.Save() return nil } func (csc *ConsensusStateControl) RoundState() *RoundState { csc.mtx.Lock() defer csc.mtx.Unlock() return csc.roundState } func (csc *ConsensusStateControl) setupHeight(height uint32, validators map[uint64]*Validator, startTime time.Time) { if height > 0 && height != csc.height+1 { panic("setupHeight() cannot skip heights") } // Reset the state for the next height. csc.validatorsR0 = validators csc.height = height csc.lockedProposal = nil csc.startTime = startTime csc.commits = NewVoteSet(height, 0, VoteTypeCommit, validators) // Setup the roundState csc.roundState = nil csc.setupRound(0) } // If csc.roundSTate isn't at round, set up new roundState at round. func (csc *ConsensusStateControl) SetupRound(round uint16) { csc.mtx.Lock() defer csc.mtx.Unlock() if csc.roundState != nil && csc.roundState.Round >= round { return } csc.setupRound(round) } // Initialize roundState for given round. // Involves incrementing validators for each past rand. func (csc *ConsensusStateControl) setupRound(round uint16) { // Increment validator accums as necessary. // We need to start with csc.validatorsR0 or csc.roundState.Validators var validators map[uint64]*Validator = nil var validatorsRound uint16 if csc.roundState == nil { // We have no roundState so we start from validatorsR0 at round 0. validators = copyValidators(csc.validatorsR0) validatorsRound = 0 } else { // We have a previous roundState so we start from that. validators = copyValidators(csc.roundState.Validators) validatorsRound = csc.roundState.Round } // Increment all the way to round. for r := validatorsRound; r < round; r++ { incrementAccum(validators) } roundState := NewRoundState(csc.height, round, csc.startTime, validators, csc.commits) csc.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 map[uint64]*Validator // All validators with modified accumPower for this round. BlockPartSet *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 map[uint64]*Validator, commits *VoteSet) *RoundState { proposer := getProposer(validators) blockPartSet := NewBlockPartSet(height, round, &(proposer.Account)) 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, BlockPartSet: blockPartSet, RoundBareVotes: roundBareVotes, RoundPrecommits: roundPrecommits, Commits: commits, step: RoundStepStart, } return rs } func (rs *RoundState) AddVote(vote *Vote) (bool, error) { switch vote.Type { case VoteTypeBare: return rs.RoundBareVotes.AddVote(vote) case VoteTypePrecommit: return rs.RoundPrecommits.AddVote(vote) case VoteTypeCommit: return rs.Commits.AddVote(vote) 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 } }