Consensus Reactor
Consensus Reactor defines a reactor for the consensus service. It contains the ConsensusState service that
manages the state of the Tendermint consensus internal state machine.
When Consensus Reactor is started, it starts Broadcast Routine which starts ConsensusState service.
Furthermore, for each peer that is added to the Consensus Reactor, it creates (and manages) the known peer state
(that is used extensively in gossip routines) and starts the following three routines for the peer p:
Gossip Data Routine, Gossip Votes Routine and QueryMaj23Routine. Finally, Consensus Reactor is responsible
for decoding messages received from a peer and for adequate processing of the message depending on its type and content.
The processing normally consists of updating the known peer state and for some messages
(ProposalMessage, BlockPartMessage and VoteMessage) also forwarding message to ConsensusState module
for further processing. In the following text we specify the core functionality of those separate unit of executions
that are part of the Consensus Reactor.
ConsensusState service
Consensus State handles execution of the Tendermint BFT consensus algorithm. It processes votes and proposals, and upon reaching agreement, commits blocks to the chain and executes them against the application. The internal state machine receives input from peers, the internal validator and from a timer.
Inside Consensus State we have the following units of execution: Timeout Ticker and Receive Routine. Timeout Ticker is a timer that schedules timeouts conditional on the height/round/step that are processed by the Receive Routine.
Receive Routine of the ConsensusState service
Receive Routine of the ConsensusState handles messages which may cause internal consensus state transitions. It is the only routine that updates RoundState that contains internal consensus state. Updates (state transitions) happen on timeouts, complete proposals, and 2/3 majorities. It receives messages from peers, internal validators and from Timeout Ticker and invokes the corresponding handlers, potentially updating the RoundState. The details of the protocol (together with formal proofs of correctness) implemented by the Receive Routine are discussed in separate document. For understanding of this document it is sufficient to understand that the Receive Routine manages and updates RoundState data structure that is then extensively used by the gossip routines to determine what information should be sent to peer processes.
Round State
RoundState defines the internal consensus state. It contains height, round, round step, a current validator set, a proposal and proposal block for the current round, locked round and block (if some block is being locked), set of received votes and last commit and last validators set.
type RoundState struct {Height int64Round intStep RoundStepTypeValidators ValidatorSetProposal ProposalProposalBlock BlockProposalBlockParts PartSetLockedRound intLockedBlock BlockLockedBlockParts PartSetVotes HeightVoteSetLastCommit VoteSetLastValidators ValidatorSet}
Internally, consensus will run as a state machine with the following states:
- RoundStepNewHeight
- RoundStepNewRound
- RoundStepPropose
- RoundStepProposeWait
- RoundStepPrevote
- RoundStepPrevoteWait
- RoundStepPrecommit
- RoundStepPrecommitWait
- RoundStepCommit
Peer Round State
Peer round state contains the known state of a peer. It is being updated by the Receive routine of Consensus Reactor and by the gossip routines upon sending a message to the peer.
type PeerRoundState struct {Height int64 // Height peer is atRound int // Round peer is at, -1 if unknown.Step RoundStepType // Step peer is atProposal bool // True if peer has proposal for this roundProposalBlockPartsHeader PartSetHeaderProposalBlockParts BitArrayProposalPOLRound int // Proposal's POL round. -1 if none.ProposalPOL BitArray // nil until ProposalPOLMessage received.Prevotes BitArray // All votes peer has for this roundPrecommits BitArray // All precommits peer has for this roundLastCommitRound int // Round of commit for last height. -1 if none.LastCommit BitArray // All commit precommits of commit for last height.CatchupCommitRound int // Round that we have commit for. Not necessarily unique. -1 if none.CatchupCommit BitArray // All commit precommits peer has for this height & CatchupCommitRound}
Receive method of Consensus reactor
The entry point of the Consensus reactor is a receive method. When a message is received from a peer p,
normally the peer round state is updated correspondingly, and some messages
are passed for further processing, for example to ConsensusState service. We now specify the processing of messages
in the receive method of Consensus reactor for each message type. In the following message handler, rs and prs denote
RoundState and PeerRoundState, respectively.
NewRoundStepMessage handler
handleMessage(msg):if msg is from smaller height/round/step then return// Just remember these values.prsHeight = prs.HeightprsRound = prs.RoundprsCatchupCommitRound = prs.CatchupCommitRoundprsCatchupCommit = prs.CatchupCommitUpdate prs with values from msgif prs.Height or prs.Round has been updated thenreset Proposal related fields of the peer stateif prs.Round has been updated and msg.Round == prsCatchupCommitRound thenprs.Precommits = psCatchupCommitif prs.Height has been updated thenif prsHeight+1 == msg.Height && prsRound == msg.LastCommitRound thenprs.LastCommitRound = msg.LastCommitRoundprs.LastCommit = prs.Precommits} else {prs.LastCommitRound = msg.LastCommitRoundprs.LastCommit = nil}Reset prs.CatchupCommitRound and prs.CatchupCommit
NewValidBlockMessage handler
handleMessage(msg):if prs.Height != msg.Height then returnif prs.Round != msg.Round && !msg.IsCommit then returnprs.ProposalBlockPartsHeader = msg.BlockPartsHeaderprs.ProposalBlockParts = msg.BlockParts
HasVoteMessage handler
handleMessage(msg):if prs.Height == msg.Height thenprs.setHasVote(msg.Height, msg.Round, msg.Type, msg.Index)
VoteSetMaj23Message handler
handleMessage(msg):if prs.Height == msg.Height thenRecord in rs that a peer claim to have ⅔ majority for msg.BlockIDSend VoteSetBitsMessage showing votes node has for that BlockId
ProposalMessage handler
handleMessage(msg):if prs.Height != msg.Height || prs.Round != msg.Round || prs.Proposal then returnprs.Proposal = trueif prs.ProposalBlockParts == empty set then // otherwise it is set in NewValidBlockMessage handlerprs.ProposalBlockPartsHeader = msg.BlockPartsHeaderprs.ProposalPOLRound = msg.POLRoundprs.ProposalPOL = nilSend msg through internal peerMsgQueue to ConsensusState service
ProposalPOLMessage handler
handleMessage(msg):if prs.Height != msg.Height or prs.ProposalPOLRound != msg.ProposalPOLRound then returnprs.ProposalPOL = msg.ProposalPOL
BlockPartMessage handler
handleMessage(msg):if prs.Height != msg.Height || prs.Round != msg.Round then returnRecord in prs that peer has block part msg.Part.IndexSend msg trough internal peerMsgQueue to ConsensusState service
VoteMessage handler
handleMessage(msg):Record in prs that a peer knows vote with index msg.vote.ValidatorIndex for particular height and roundSend msg trough internal peerMsgQueue to ConsensusState service
VoteSetBitsMessage handler
handleMessage(msg):Update prs for the bit-array of votes peer claims to have for the msg.BlockID
Gossip Data Routine
It is used to send the following messages to the peer: BlockPartMessage, ProposalMessage and
ProposalPOLMessage on the DataChannel. The gossip data routine is based on the local RoundState (rs)
and the known PeerRoundState (prs). The routine repeats forever the logic shown below:
1a) if rs.ProposalBlockPartsHeader == prs.ProposalBlockPartsHeader and the peer does not have all the proposal parts thenPart = pick a random proposal block part the peer does not haveSend BlockPartMessage(rs.Height, rs.Round, Part) to the peer on the DataChannelif send returns true, record that the peer knows the corresponding block PartContinue1b) if (0 < prs.Height) and (prs.Height < rs.Height) thenhelp peer catch up using gossipDataForCatchup functionContinue1c) if (rs.Height != prs.Height) or (rs.Round != prs.Round) thenSleep PeerGossipSleepDurationContinue// at this point rs.Height == prs.Height and rs.Round == prs.Round1d) if (rs.Proposal != nil and !prs.Proposal) thenSend ProposalMessage(rs.Proposal) to the peerif send returns true, record that the peer knows Proposalif 0 <= rs.Proposal.POLRound thenpolRound = rs.Proposal.POLRoundprevotesBitArray = rs.Votes.Prevotes(polRound).BitArray()Send ProposalPOLMessage(rs.Height, polRound, prevotesBitArray)Continue2) Sleep PeerGossipSleepDuration
Gossip Data For Catchup
This function is responsible for helping peer catch up if it is at the smaller height (prs.Height < rs.Height). The function executes the following logic:
if peer does not have all block parts for prs.ProposalBlockPart thenblockMeta = Load Block Metadata for height prs.Height from blockStoreif (!blockMeta.BlockID.PartsHeader == prs.ProposalBlockPartsHeader) thenSleep PeerGossipSleepDurationreturnPart = pick a random proposal block part the peer does not haveSend BlockPartMessage(prs.Height, prs.Round, Part) to the peer on the DataChannelif send returns true, record that the peer knows the corresponding block Partreturnelse Sleep PeerGossipSleepDuration
Gossip Votes Routine
It is used to send the following message: VoteMessage on the VoteChannel.
The gossip votes routine is based on the local RoundState (rs)
and the known PeerRoundState (prs). The routine repeats forever the logic shown below:
1a) if rs.Height == prs.Height thenif prs.Step == RoundStepNewHeight thenvote = random vote from rs.LastCommit the peer does not haveSend VoteMessage(vote) to the peerif send returns true, continueif prs.Step <= RoundStepPrevote and prs.Round != -1 and prs.Round <= rs.Round thenPrevotes = rs.Votes.Prevotes(prs.Round)vote = random vote from Prevotes the peer does not haveSend VoteMessage(vote) to the peerif send returns true, continueif prs.Step <= RoundStepPrecommit and prs.Round != -1 and prs.Round <= rs.Round thenPrecommits = rs.Votes.Precommits(prs.Round)vote = random vote from Precommits the peer does not haveSend VoteMessage(vote) to the peerif send returns true, continueif prs.ProposalPOLRound != -1 thenPolPrevotes = rs.Votes.Prevotes(prs.ProposalPOLRound)vote = random vote from PolPrevotes the peer does not haveSend VoteMessage(vote) to the peerif send returns true, continue1b) if prs.Height != 0 and rs.Height == prs.Height+1 thenvote = random vote from rs.LastCommit peer does not haveSend VoteMessage(vote) to the peerif send returns true, continue1c) if prs.Height != 0 and rs.Height >= prs.Height+2 thenCommit = get commit from BlockStore for prs.Heightvote = random vote from Commit the peer does not haveSend VoteMessage(vote) to the peerif send returns true, continue2) Sleep PeerGossipSleepDuration
QueryMaj23Routine
It is used to send the following message: VoteSetMaj23Message. VoteSetMaj23Message is sent to indicate that a given
BlockID has seen +2/3 votes. This routine is based on the local RoundState (rs) and the known PeerRoundState
(prs). The routine repeats forever the logic shown below.
1a) if rs.Height == prs.Height thenPrevotes = rs.Votes.Prevotes(prs.Round)if there is a ⅔ majority for some blockId in Prevotes thenm = VoteSetMaj23Message(prs.Height, prs.Round, Prevote, blockId)Send m to peerSleep PeerQueryMaj23SleepDuration1b) if rs.Height == prs.Height thenPrecommits = rs.Votes.Precommits(prs.Round)if there is a ⅔ majority for some blockId in Precommits thenm = VoteSetMaj23Message(prs.Height,prs.Round,Precommit,blockId)Send m to peerSleep PeerQueryMaj23SleepDuration1c) if rs.Height == prs.Height and prs.ProposalPOLRound >= 0 thenPrevotes = rs.Votes.Prevotes(prs.ProposalPOLRound)if there is a ⅔ majority for some blockId in Prevotes thenm = VoteSetMaj23Message(prs.Height,prs.ProposalPOLRound,Prevotes,blockId)Send m to peerSleep PeerQueryMaj23SleepDuration1d) if prs.CatchupCommitRound != -1 and 0 < prs.Height andprs.Height <= blockStore.Height() thenCommit = LoadCommit(prs.Height)m = VoteSetMaj23Message(prs.Height,Commit.Round,Precommit,Commit.blockId)Send m to peerSleep PeerQueryMaj23SleepDuration2) Sleep PeerQueryMaj23SleepDuration
Broadcast routine
The Broadcast routine subscribes to an internal event bus to receive new round steps and votes messages, and broadcasts messages to peers upon receiving those
events.
It broadcasts NewRoundStepMessage or CommitStepMessage upon new round state event. Note that
broadcasting these messages does not depend on the PeerRoundState; it is sent on the StateChannel.
Upon receiving VoteMessage it broadcasts HasVoteMessage message to its peers on the StateChannel.
Channels
Defines 4 channels: state, data, vote and vote_set_bits. Each channel
has SendQueueCapacity and RecvBufferCapacity and
RecvMessageCapacity set to maxMsgSize.
Sending incorrectly encoded data will result in stopping the peer.
