tendermint/docs/spec/reactors/consensus/consensus-reactor.md

352 lines
14 KiB
Markdown
Raw Permalink Normal View History

2018-01-08 13:12:09 +01:00
# 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
2018-01-08 13:12:09 +01:00
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.
2018-01-08 13:12:09 +01:00
## ConsensusState service
Consensus State handles execution of the Tendermint BFT consensus algorithm. It processes votes and proposals,
2018-01-08 13:12:09 +01:00
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.
2018-01-08 13:12:09 +01:00
### 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
2018-01-08 13:12:09 +01:00
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.
2018-01-08 13:12:09 +01:00
2018-01-25 05:02:26 +00:00
```golang
2018-01-08 13:12:09 +01:00
type RoundState struct {
Height int64
2018-01-08 13:12:09 +01:00
Round int
Step RoundStepType
Validators ValidatorSet
Proposal Proposal
ProposalBlock Block
ProposalBlockParts PartSet
LockedRound int
LockedBlock Block
LockedBlockParts PartSet
Votes HeightVoteSet
LastCommit VoteSet
2018-01-08 13:12:09 +01:00
LastValidators ValidatorSet
}
```
2018-01-08 13:12:09 +01:00
Internally, consensus will run as a state machine with the following states:
2018-01-25 05:02:26 +00:00
- RoundStepNewHeight
- RoundStepNewRound
- RoundStepPropose
- RoundStepProposeWait
- RoundStepPrevote
- RoundStepPrevoteWait
- RoundStepPrecommit
- RoundStepPrecommitWait
- RoundStepCommit
2018-01-08 13:12:09 +01:00
## 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.
2018-01-25 05:02:26 +00:00
```golang
2018-01-08 13:12:09 +01:00
type PeerRoundState struct {
Height int64 // Height peer is at
Round int // Round peer is at, -1 if unknown.
Step RoundStepType // Step peer is at
Proposal bool // True if peer has proposal for this round
ProposalBlockPartsHeader PartSetHeader
ProposalBlockParts BitArray
2018-01-08 13:12:09 +01:00
ProposalPOLRound int // Proposal's POL round. -1 if none.
ProposalPOL BitArray // nil until ProposalPOLMessage received.
Prevotes BitArray // All votes peer has for this round
Precommits BitArray // All precommits peer has for this round
LastCommitRound 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
}
```
2018-01-08 13:12:09 +01:00
## 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
2018-01-08 13:12:09 +01:00
are passed for further processing, for example to ConsensusState service. We now specify the processing of messages
2018-01-25 05:02:26 +00:00
in the receive method of Consensus reactor for each message type. In the following message handler, `rs` and `prs` denote
`RoundState` and `PeerRoundState`, respectively.
2018-01-08 13:12:09 +01:00
### NewRoundStepMessage handler
2018-01-08 13:12:09 +01:00
```
2018-01-08 13:12:09 +01:00
handleMessage(msg):
if msg is from smaller height/round/step then return
// Just remember these values.
prsHeight = prs.Height
prsRound = prs.Round
prsCatchupCommitRound = prs.CatchupCommitRound
prsCatchupCommit = prs.CatchupCommit
Update prs with values from msg
if prs.Height or prs.Round has been updated then
reset Proposal related fields of the peer state
2018-01-08 13:12:09 +01:00
if prs.Round has been updated and msg.Round == prsCatchupCommitRound then
prs.Precommits = psCatchupCommit
if prs.Height has been updated then
2018-01-08 13:12:09 +01:00
if prsHeight+1 == msg.Height && prsRound == msg.LastCommitRound then
prs.LastCommitRound = msg.LastCommitRound
prs.LastCommit = prs.Precommits
} else {
prs.LastCommitRound = msg.LastCommitRound
prs.LastCommit = nil
}
Reset prs.CatchupCommitRound and prs.CatchupCommit
```
2018-01-08 13:12:09 +01:00
### CommitStepMessage handler
```
2018-01-08 13:12:09 +01:00
handleMessage(msg):
if prs.Height == msg.Height then
2018-01-08 13:12:09 +01:00
prs.ProposalBlockPartsHeader = msg.BlockPartsHeader
prs.ProposalBlockParts = msg.BlockParts
```
2018-01-08 13:12:09 +01:00
### HasVoteMessage handler
```
2018-01-08 13:12:09 +01:00
handleMessage(msg):
if prs.Height == msg.Height then
2018-01-08 13:12:09 +01:00
prs.setHasVote(msg.Height, msg.Round, msg.Type, msg.Index)
```
2018-01-08 13:12:09 +01:00
### VoteSetMaj23Message handler
```
2018-01-08 13:12:09 +01:00
handleMessage(msg):
if prs.Height == msg.Height then
Record in rs that a peer claim to have ⅔ majority for msg.BlockID
Send VoteSetBitsMessage showing votes node has for that BlockId
```
2018-01-08 13:12:09 +01:00
### ProposalMessage handler
```
handleMessage(msg):
if prs.Height != msg.Height || prs.Round != msg.Round || prs.Proposal then return
2018-01-08 13:12:09 +01:00
prs.Proposal = true
prs.ProposalBlockPartsHeader = msg.BlockPartsHeader
prs.ProposalBlockParts = empty set
2018-01-08 13:12:09 +01:00
prs.ProposalPOLRound = msg.POLRound
prs.ProposalPOL = nil
2018-01-08 13:12:09 +01:00
Send msg through internal peerMsgQueue to ConsensusState service
```
2018-01-08 13:12:09 +01:00
### ProposalPOLMessage handler
```
2018-01-08 13:12:09 +01:00
handleMessage(msg):
if prs.Height != msg.Height or prs.ProposalPOLRound != msg.ProposalPOLRound then return
prs.ProposalPOL = msg.ProposalPOL
```
2018-01-08 13:12:09 +01:00
### BlockPartMessage handler
```
2018-01-08 13:12:09 +01:00
handleMessage(msg):
if prs.Height != msg.Height || prs.Round != msg.Round then return
Record in prs that peer has block part msg.Part.Index
2018-01-08 13:12:09 +01:00
Send msg trough internal peerMsgQueue to ConsensusState service
```
2018-01-08 13:12:09 +01:00
### VoteMessage handler
```
2018-01-08 13:12:09 +01:00
handleMessage(msg):
Record in prs that a peer knows vote with index msg.vote.ValidatorIndex for particular height and round
Send msg trough internal peerMsgQueue to ConsensusState service
```
2018-01-08 13:12:09 +01:00
### VoteSetBitsMessage handler
```
2018-01-08 13:12:09 +01:00
handleMessage(msg):
Update prs for the bit-array of votes peer claims to have for the msg.BlockID
```
2018-01-08 13:12:09 +01:00
## 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`)
2018-01-25 05:02:26 +00:00
and the known PeerRoundState (`prs`). The routine repeats forever the logic shown below:
2018-01-08 13:12:09 +01:00
```
1a) if rs.ProposalBlockPartsHeader == prs.ProposalBlockPartsHeader and the peer does not have all the proposal parts then
Part = pick a random proposal block part the peer does not have
Send BlockPartMessage(rs.Height, rs.Round, Part) to the peer on the DataChannel
2018-01-08 13:12:09 +01:00
if send returns true, record that the peer knows the corresponding block Part
Continue
2018-01-08 13:12:09 +01:00
1b) if (0 < prs.Height) and (prs.Height < rs.Height) then
help peer catch up using gossipDataForCatchup function
Continue
1c) if (rs.Height != prs.Height) or (rs.Round != prs.Round) then
2018-01-08 13:12:09 +01:00
Sleep PeerGossipSleepDuration
Continue
2018-01-08 13:12:09 +01:00
// at this point rs.Height == prs.Height and rs.Round == prs.Round
1d) if (rs.Proposal != nil and !prs.Proposal) then
2018-01-08 13:12:09 +01:00
Send ProposalMessage(rs.Proposal) to the peer
if send returns true, record that the peer knows Proposal
if 0 <= rs.Proposal.POLRound then
polRound = rs.Proposal.POLRound
prevotesBitArray = rs.Votes.Prevotes(polRound).BitArray()
2018-01-08 13:12:09 +01:00
Send ProposalPOLMessage(rs.Height, polRound, prevotesBitArray)
Continue
2018-01-08 13:12:09 +01:00
2) Sleep PeerGossipSleepDuration
2018-01-08 13:12:09 +01:00
```
### 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 then
2018-01-08 13:12:09 +01:00
blockMeta = Load Block Metadata for height prs.Height from blockStore
if (!blockMeta.BlockID.PartsHeader == prs.ProposalBlockPartsHeader) then
Sleep PeerGossipSleepDuration
return
Part = pick a random proposal block part the peer does not have
Send BlockPartMessage(prs.Height, prs.Round, Part) to the peer on the DataChannel
2018-01-08 13:12:09 +01:00
if send returns true, record that the peer knows the corresponding block Part
return
else Sleep PeerGossipSleepDuration
2018-01-08 13:12:09 +01:00
## 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`)
2018-01-25 05:02:26 +00:00
and the known PeerRoundState (`prs`). The routine repeats forever the logic shown below:
2018-01-08 13:12:09 +01:00
```
1a) if rs.Height == prs.Height then
if prs.Step == RoundStepNewHeight then
vote = random vote from rs.LastCommit the peer does not have
Send VoteMessage(vote) to the peer
2018-01-08 13:12:09 +01:00
if send returns true, continue
if prs.Step <= RoundStepPrevote and prs.Round != -1 and prs.Round <= rs.Round then
2018-01-08 13:12:09 +01:00
Prevotes = rs.Votes.Prevotes(prs.Round)
vote = random vote from Prevotes the peer does not have
Send VoteMessage(vote) to the peer
2018-01-08 13:12:09 +01:00
if send returns true, continue
if prs.Step <= RoundStepPrecommit and prs.Round != -1 and prs.Round <= rs.Round then
Precommits = rs.Votes.Precommits(prs.Round)
vote = random vote from Precommits the peer does not have
Send VoteMessage(vote) to the peer
2018-01-08 13:12:09 +01:00
if send returns true, continue
if prs.ProposalPOLRound != -1 then
2018-01-08 13:12:09 +01:00
PolPrevotes = rs.Votes.Prevotes(prs.ProposalPOLRound)
vote = random vote from PolPrevotes the peer does not have
Send VoteMessage(vote) to the peer
if send returns true, continue
2018-01-08 13:12:09 +01:00
1b) if prs.Height != 0 and rs.Height == prs.Height+1 then
vote = random vote from rs.LastCommit peer does not have
Send VoteMessage(vote) to the peer
2018-01-08 13:12:09 +01:00
if send returns true, continue
2018-01-08 13:12:09 +01:00
1c) if prs.Height != 0 and rs.Height >= prs.Height+2 then
Commit = get commit from BlockStore for prs.Height
vote = random vote from Commit the peer does not have
Send VoteMessage(vote) to the peer
2018-01-08 13:12:09 +01:00
if send returns true, continue
2) Sleep PeerGossipSleepDuration
2018-01-08 13:12:09 +01:00
```
## QueryMaj23Routine
It is used to send the following message: `VoteSetMaj23Message`. `VoteSetMaj23Message` is sent to indicate that a given
2018-01-25 05:02:26 +00:00
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.
2018-01-08 13:12:09 +01:00
```
1a) if rs.Height == prs.Height then
Prevotes = rs.Votes.Prevotes(prs.Round)
if there is a ⅔ majority for some blockId in Prevotes then
m = VoteSetMaj23Message(prs.Height, prs.Round, Prevote, blockId)
Send m to peer
Sleep PeerQueryMaj23SleepDuration
1b) if rs.Height == prs.Height then
Precommits = rs.Votes.Precommits(prs.Round)
if there is a ⅔ majority for some blockId in Precommits then
m = VoteSetMaj23Message(prs.Height,prs.Round,Precommit,blockId)
Send m to peer
Sleep PeerQueryMaj23SleepDuration
1c) if rs.Height == prs.Height and prs.ProposalPOLRound >= 0 then
Prevotes = rs.Votes.Prevotes(prs.ProposalPOLRound)
if there is a ⅔ majority for some blockId in Prevotes then
m = VoteSetMaj23Message(prs.Height,prs.ProposalPOLRound,Prevotes,blockId)
Send m to peer
Sleep PeerQueryMaj23SleepDuration
1d) if prs.CatchupCommitRound != -1 and 0 < prs.Height and
prs.Height <= blockStore.Height() then
2018-01-08 13:12:09 +01:00
Commit = LoadCommit(prs.Height)
m = VoteSetMaj23Message(prs.Height,Commit.Round,Precommit,Commit.blockId)
Send m to peer
Sleep PeerQueryMaj23SleepDuration
2) Sleep PeerQueryMaj23SleepDuration
```
## Broadcast routine
2018-01-25 05:02:26 +00:00
The Broadcast routine subscribes to an internal event bus to receive new round steps, votes messages and proposal
2018-01-08 13:12:09 +01:00
heartbeat messages, and broadcasts messages to peers upon receiving those events.
2018-01-25 05:02:26 +00:00
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.
2018-01-08 13:12:09 +01:00
`ProposalHeartbeatMessage` is sent the same way 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.