You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

354 lines
15 KiB

  1. # Consensus Reactor
  2. Consensus Reactor defines a reactor for the consensus service. It contains the ConsensusState service that
  3. manages the state of the Tendermint consensus internal state machine.
  4. When Consensus Reactor is started, it starts Broadcast Routine which starts ConsensusState service.
  5. Furthermore, for each peer that is added to the Consensus Reactor, it creates (and manages) the known peer state
  6. (that is used extensively in gossip routines) and starts the following three routines for the peer p:
  7. Gossip Data Routine, Gossip Votes Routine and QueryMaj23Routine. Finally, Consensus Reactor is responsible
  8. for decoding messages received from a peer and for adequate processing of the message depending on its type and content.
  9. The processing normally consists of updating the known peer state and for some messages
  10. (`ProposalMessage`, `BlockPartMessage` and `VoteMessage`) also forwarding message to ConsensusState module
  11. for further processing. In the following text we specify the core functionality of those separate unit of executions
  12. that are part of the Consensus Reactor.
  13. ## ConsensusState service
  14. Consensus State handles execution of the Tendermint BFT consensus algorithm. It processes votes and proposals,
  15. and upon reaching agreement, commits blocks to the chain and executes them against the application.
  16. The internal state machine receives input from peers, the internal validator and from a timer.
  17. Inside Consensus State we have the following units of execution: Timeout Ticker and Receive Routine.
  18. Timeout Ticker is a timer that schedules timeouts conditional on the height/round/step that are processed
  19. by the Receive Routine.
  20. ### Receive Routine of the ConsensusState service
  21. Receive Routine of the ConsensusState handles messages which may cause internal consensus state transitions.
  22. It is the only routine that updates RoundState that contains internal consensus state.
  23. Updates (state transitions) happen on timeouts, complete proposals, and 2/3 majorities.
  24. It receives messages from peers, internal validators and from Timeout Ticker
  25. and invokes the corresponding handlers, potentially updating the RoundState.
  26. The details of the protocol (together with formal proofs of correctness) implemented by the Receive Routine are
  27. discussed in separate document. For understanding of this document
  28. it is sufficient to understand that the Receive Routine manages and updates RoundState data structure that is
  29. then extensively used by the gossip routines to determine what information should be sent to peer processes.
  30. ## Round State
  31. RoundState defines the internal consensus state. It contains height, round, round step, a current validator set,
  32. a proposal and proposal block for the current round, locked round and block (if some block is being locked), set of
  33. received votes and last commit and last validators set.
  34. ```golang
  35. type RoundState struct {
  36. Height int64
  37. Round int
  38. Step RoundStepType
  39. Validators ValidatorSet
  40. Proposal Proposal
  41. ProposalBlock Block
  42. ProposalBlockParts PartSet
  43. LockedRound int
  44. LockedBlock Block
  45. LockedBlockParts PartSet
  46. Votes HeightVoteSet
  47. LastCommit VoteSet
  48. LastValidators ValidatorSet
  49. }
  50. ```
  51. Internally, consensus will run as a state machine with the following states:
  52. - RoundStepNewHeight
  53. - RoundStepNewRound
  54. - RoundStepPropose
  55. - RoundStepProposeWait
  56. - RoundStepPrevote
  57. - RoundStepPrevoteWait
  58. - RoundStepPrecommit
  59. - RoundStepPrecommitWait
  60. - RoundStepCommit
  61. ## Peer Round State
  62. Peer round state contains the known state of a peer. It is being updated by the Receive routine of
  63. Consensus Reactor and by the gossip routines upon sending a message to the peer.
  64. ```golang
  65. type PeerRoundState struct {
  66. Height int64 // Height peer is at
  67. Round int // Round peer is at, -1 if unknown.
  68. Step RoundStepType // Step peer is at
  69. Proposal bool // True if peer has proposal for this round
  70. ProposalBlockPartsHeader PartSetHeader
  71. ProposalBlockParts BitArray
  72. ProposalPOLRound int // Proposal's POL round. -1 if none.
  73. ProposalPOL BitArray // nil until ProposalPOLMessage received.
  74. Prevotes BitArray // All votes peer has for this round
  75. Precommits BitArray // All precommits peer has for this round
  76. LastCommitRound int // Round of commit for last height. -1 if none.
  77. LastCommit BitArray // All commit precommits of commit for last height.
  78. CatchupCommitRound int // Round that we have commit for. Not necessarily unique. -1 if none.
  79. CatchupCommit BitArray // All commit precommits peer has for this height & CatchupCommitRound
  80. }
  81. ```
  82. ## Receive method of Consensus reactor
  83. The entry point of the Consensus reactor is a receive method. When a message is received from a peer p,
  84. normally the peer round state is updated correspondingly, and some messages
  85. are passed for further processing, for example to ConsensusState service. We now specify the processing of messages
  86. in the receive method of Consensus reactor for each message type. In the following message handler, `rs` and `prs` denote
  87. `RoundState` and `PeerRoundState`, respectively.
  88. ### NewRoundStepMessage handler
  89. ```
  90. handleMessage(msg):
  91. if msg is from smaller height/round/step then return
  92. // Just remember these values.
  93. prsHeight = prs.Height
  94. prsRound = prs.Round
  95. prsCatchupCommitRound = prs.CatchupCommitRound
  96. prsCatchupCommit = prs.CatchupCommit
  97. Update prs with values from msg
  98. if prs.Height or prs.Round has been updated then
  99. reset Proposal related fields of the peer state
  100. if prs.Round has been updated and msg.Round == prsCatchupCommitRound then
  101. prs.Precommits = psCatchupCommit
  102. if prs.Height has been updated then
  103. if prsHeight+1 == msg.Height && prsRound == msg.LastCommitRound then
  104. prs.LastCommitRound = msg.LastCommitRound
  105. prs.LastCommit = prs.Precommits
  106. } else {
  107. prs.LastCommitRound = msg.LastCommitRound
  108. prs.LastCommit = nil
  109. }
  110. Reset prs.CatchupCommitRound and prs.CatchupCommit
  111. ```
  112. ### NewValidBlockMessage handler
  113. ```
  114. handleMessage(msg):
  115. if prs.Height != msg.Height then return
  116. if prs.Round != msg.Round && !msg.IsCommit then return
  117. prs.ProposalBlockPartsHeader = msg.BlockPartsHeader
  118. prs.ProposalBlockParts = msg.BlockParts
  119. ```
  120. ### HasVoteMessage handler
  121. ```
  122. handleMessage(msg):
  123. if prs.Height == msg.Height then
  124. prs.setHasVote(msg.Height, msg.Round, msg.Type, msg.Index)
  125. ```
  126. ### VoteSetMaj23Message handler
  127. ```
  128. handleMessage(msg):
  129. if prs.Height == msg.Height then
  130. Record in rs that a peer claim to have ⅔ majority for msg.BlockID
  131. Send VoteSetBitsMessage showing votes node has for that BlockId
  132. ```
  133. ### ProposalMessage handler
  134. ```
  135. handleMessage(msg):
  136. if prs.Height != msg.Height || prs.Round != msg.Round || prs.Proposal then return
  137. prs.Proposal = true
  138. if prs.ProposalBlockParts == empty set then // otherwise it is set in NewValidBlockMessage handler
  139. prs.ProposalBlockPartsHeader = msg.BlockPartsHeader
  140. prs.ProposalPOLRound = msg.POLRound
  141. prs.ProposalPOL = nil
  142. Send msg through internal peerMsgQueue to ConsensusState service
  143. ```
  144. ### ProposalPOLMessage handler
  145. ```
  146. handleMessage(msg):
  147. if prs.Height != msg.Height or prs.ProposalPOLRound != msg.ProposalPOLRound then return
  148. prs.ProposalPOL = msg.ProposalPOL
  149. ```
  150. ### BlockPartMessage handler
  151. ```
  152. handleMessage(msg):
  153. if prs.Height != msg.Height || prs.Round != msg.Round then return
  154. Record in prs that peer has block part msg.Part.Index
  155. Send msg trough internal peerMsgQueue to ConsensusState service
  156. ```
  157. ### VoteMessage handler
  158. ```
  159. handleMessage(msg):
  160. Record in prs that a peer knows vote with index msg.vote.ValidatorIndex for particular height and round
  161. Send msg trough internal peerMsgQueue to ConsensusState service
  162. ```
  163. ### VoteSetBitsMessage handler
  164. ```
  165. handleMessage(msg):
  166. Update prs for the bit-array of votes peer claims to have for the msg.BlockID
  167. ```
  168. ## Gossip Data Routine
  169. It is used to send the following messages to the peer: `BlockPartMessage`, `ProposalMessage` and
  170. `ProposalPOLMessage` on the DataChannel. The gossip data routine is based on the local RoundState (`rs`)
  171. and the known PeerRoundState (`prs`). The routine repeats forever the logic shown below:
  172. ```
  173. 1a) if rs.ProposalBlockPartsHeader == prs.ProposalBlockPartsHeader and the peer does not have all the proposal parts then
  174. Part = pick a random proposal block part the peer does not have
  175. Send BlockPartMessage(rs.Height, rs.Round, Part) to the peer on the DataChannel
  176. if send returns true, record that the peer knows the corresponding block Part
  177. Continue
  178. 1b) if (0 < prs.Height) and (prs.Height < rs.Height) then
  179. help peer catch up using gossipDataForCatchup function
  180. Continue
  181. 1c) if (rs.Height != prs.Height) or (rs.Round != prs.Round) then
  182. Sleep PeerGossipSleepDuration
  183. Continue
  184. // at this point rs.Height == prs.Height and rs.Round == prs.Round
  185. 1d) if (rs.Proposal != nil and !prs.Proposal) then
  186. Send ProposalMessage(rs.Proposal) to the peer
  187. if send returns true, record that the peer knows Proposal
  188. if 0 <= rs.Proposal.POLRound then
  189. polRound = rs.Proposal.POLRound
  190. prevotesBitArray = rs.Votes.Prevotes(polRound).BitArray()
  191. Send ProposalPOLMessage(rs.Height, polRound, prevotesBitArray)
  192. Continue
  193. 2) Sleep PeerGossipSleepDuration
  194. ```
  195. ### Gossip Data For Catchup
  196. This function is responsible for helping peer catch up if it is at the smaller height (prs.Height < rs.Height).
  197. The function executes the following logic:
  198. if peer does not have all block parts for prs.ProposalBlockPart then
  199. blockMeta = Load Block Metadata for height prs.Height from blockStore
  200. if (!blockMeta.BlockID.PartsHeader == prs.ProposalBlockPartsHeader) then
  201. Sleep PeerGossipSleepDuration
  202. return
  203. Part = pick a random proposal block part the peer does not have
  204. Send BlockPartMessage(prs.Height, prs.Round, Part) to the peer on the DataChannel
  205. if send returns true, record that the peer knows the corresponding block Part
  206. return
  207. else Sleep PeerGossipSleepDuration
  208. ## Gossip Votes Routine
  209. It is used to send the following message: `VoteMessage` on the VoteChannel.
  210. The gossip votes routine is based on the local RoundState (`rs`)
  211. and the known PeerRoundState (`prs`). The routine repeats forever the logic shown below:
  212. ```
  213. 1a) if rs.Height == prs.Height then
  214. if prs.Step == RoundStepNewHeight then
  215. vote = random vote from rs.LastCommit the peer does not have
  216. Send VoteMessage(vote) to the peer
  217. if send returns true, continue
  218. if prs.Step <= RoundStepPrevote and prs.Round != -1 and prs.Round <= rs.Round then
  219. Prevotes = rs.Votes.Prevotes(prs.Round)
  220. vote = random vote from Prevotes the peer does not have
  221. Send VoteMessage(vote) to the peer
  222. if send returns true, continue
  223. if prs.Step <= RoundStepPrecommit and prs.Round != -1 and prs.Round <= rs.Round then
  224. Precommits = rs.Votes.Precommits(prs.Round)
  225. vote = random vote from Precommits the peer does not have
  226. Send VoteMessage(vote) to the peer
  227. if send returns true, continue
  228. if prs.ProposalPOLRound != -1 then
  229. PolPrevotes = rs.Votes.Prevotes(prs.ProposalPOLRound)
  230. vote = random vote from PolPrevotes the peer does not have
  231. Send VoteMessage(vote) to the peer
  232. if send returns true, continue
  233. 1b) if prs.Height != 0 and rs.Height == prs.Height+1 then
  234. vote = random vote from rs.LastCommit peer does not have
  235. Send VoteMessage(vote) to the peer
  236. if send returns true, continue
  237. 1c) if prs.Height != 0 and rs.Height >= prs.Height+2 then
  238. Commit = get commit from BlockStore for prs.Height
  239. vote = random vote from Commit the peer does not have
  240. Send VoteMessage(vote) to the peer
  241. if send returns true, continue
  242. 2) Sleep PeerGossipSleepDuration
  243. ```
  244. ## QueryMaj23Routine
  245. It is used to send the following message: `VoteSetMaj23Message`. `VoteSetMaj23Message` is sent to indicate that a given
  246. BlockID has seen +2/3 votes. This routine is based on the local RoundState (`rs`) and the known PeerRoundState
  247. (`prs`). The routine repeats forever the logic shown below.
  248. ```
  249. 1a) if rs.Height == prs.Height then
  250. Prevotes = rs.Votes.Prevotes(prs.Round)
  251. if there is a ⅔ majority for some blockId in Prevotes then
  252. m = VoteSetMaj23Message(prs.Height, prs.Round, Prevote, blockId)
  253. Send m to peer
  254. Sleep PeerQueryMaj23SleepDuration
  255. 1b) if rs.Height == prs.Height then
  256. Precommits = rs.Votes.Precommits(prs.Round)
  257. if there is a ⅔ majority for some blockId in Precommits then
  258. m = VoteSetMaj23Message(prs.Height,prs.Round,Precommit,blockId)
  259. Send m to peer
  260. Sleep PeerQueryMaj23SleepDuration
  261. 1c) if rs.Height == prs.Height and prs.ProposalPOLRound >= 0 then
  262. Prevotes = rs.Votes.Prevotes(prs.ProposalPOLRound)
  263. if there is a ⅔ majority for some blockId in Prevotes then
  264. m = VoteSetMaj23Message(prs.Height,prs.ProposalPOLRound,Prevotes,blockId)
  265. Send m to peer
  266. Sleep PeerQueryMaj23SleepDuration
  267. 1d) if prs.CatchupCommitRound != -1 and 0 < prs.Height and
  268. prs.Height <= blockStore.Height() then
  269. Commit = LoadCommit(prs.Height)
  270. m = VoteSetMaj23Message(prs.Height,Commit.Round,Precommit,Commit.blockId)
  271. Send m to peer
  272. Sleep PeerQueryMaj23SleepDuration
  273. 2) Sleep PeerQueryMaj23SleepDuration
  274. ```
  275. ## Broadcast routine
  276. The Broadcast routine subscribes to an internal event bus to receive new round steps, votes messages and proposal
  277. heartbeat messages, and broadcasts messages to peers upon receiving those events.
  278. It broadcasts `NewRoundStepMessage` or `CommitStepMessage` upon new round state event. Note that
  279. broadcasting these messages does not depend on the PeerRoundState; it is sent on the StateChannel.
  280. Upon receiving VoteMessage it broadcasts `HasVoteMessage` message to its peers on the StateChannel.
  281. `ProposalHeartbeatMessage` is sent the same way on the StateChannel.
  282. ## Channels
  283. Defines 4 channels: state, data, vote and vote_set_bits. Each channel
  284. has `SendQueueCapacity` and `RecvBufferCapacity` and
  285. `RecvMessageCapacity` set to `maxMsgSize`.
  286. Sending incorrectly encoded data will result in stopping the peer.