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.

366 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. ```go
  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
  84. received from a peer p, normally the peer round state is updated
  85. correspondingly, and some messages are passed for further processing, for
  86. example to ConsensusState service. We now specify the processing of messages in
  87. the receive method of Consensus reactor for each message type. In the following
  88. message handler, `rs` and `prs` denote `RoundState` and `PeerRoundState`,
  89. respectively.
  90. ### NewRoundStepMessage handler
  91. ```go
  92. handleMessage(msg):
  93. if msg is from smaller height/round/step then return
  94. // Just remember these values.
  95. prsHeight = prs.Height
  96. prsRound = prs.Round
  97. prsCatchupCommitRound = prs.CatchupCommitRound
  98. prsCatchupCommit = prs.CatchupCommit
  99. Update prs with values from msg
  100. if prs.Height or prs.Round has been updated then
  101. reset Proposal related fields of the peer state
  102. if prs.Round has been updated and msg.Round == prsCatchupCommitRound then
  103. prs.Precommits = psCatchupCommit
  104. if prs.Height has been updated then
  105. if prsHeight+1 == msg.Height && prsRound == msg.LastCommitRound then
  106. prs.LastCommitRound = msg.LastCommitRound
  107. prs.LastCommit = prs.Precommits
  108. } else {
  109. prs.LastCommitRound = msg.LastCommitRound
  110. prs.LastCommit = nil
  111. }
  112. Reset prs.CatchupCommitRound and prs.CatchupCommit
  113. ```
  114. ### NewValidBlockMessage handler
  115. ```go
  116. handleMessage(msg):
  117. if prs.Height != msg.Height then return
  118. if prs.Round != msg.Round && !msg.IsCommit then return
  119. prs.ProposalBlockPartsHeader = msg.BlockPartsHeader
  120. prs.ProposalBlockParts = msg.BlockParts
  121. ```
  122. The number of block parts is limited to 1601 (`types.MaxBlockPartsCount`) to
  123. protect the node against DOS attacks.
  124. ### HasVoteMessage handler
  125. ```go
  126. handleMessage(msg):
  127. if prs.Height == msg.Height then
  128. prs.setHasVote(msg.Height, msg.Round, msg.Type, msg.Index)
  129. ```
  130. ### VoteSetMaj23Message handler
  131. ```go
  132. handleMessage(msg):
  133. if prs.Height == msg.Height then
  134. Record in rs that a peer claim to have ⅔ majority for msg.BlockID
  135. Send VoteSetBitsMessage showing votes node has for that BlockId
  136. ```
  137. ### ProposalMessage handler
  138. ```go
  139. handleMessage(msg):
  140. if prs.Height != msg.Height || prs.Round != msg.Round || prs.Proposal then return
  141. prs.Proposal = true
  142. if prs.ProposalBlockParts == empty set then // otherwise it is set in NewValidBlockMessage handler
  143. prs.ProposalBlockPartsHeader = msg.BlockPartsHeader
  144. prs.ProposalPOLRound = msg.POLRound
  145. prs.ProposalPOL = nil
  146. Send msg through internal peerMsgQueue to ConsensusState service
  147. ```
  148. ### ProposalPOLMessage handler
  149. ```go
  150. handleMessage(msg):
  151. if prs.Height != msg.Height or prs.ProposalPOLRound != msg.ProposalPOLRound then return
  152. prs.ProposalPOL = msg.ProposalPOL
  153. ```
  154. The number of votes is limited to 10000 (`types.MaxVotesCount`) to protect the
  155. node against DOS attacks.
  156. ### BlockPartMessage handler
  157. ```go
  158. handleMessage(msg):
  159. if prs.Height != msg.Height || prs.Round != msg.Round then return
  160. Record in prs that peer has block part msg.Part.Index
  161. Send msg trough internal peerMsgQueue to ConsensusState service
  162. ```
  163. ### VoteMessage handler
  164. ```go
  165. handleMessage(msg):
  166. Record in prs that a peer knows vote with index msg.vote.ValidatorIndex for particular height and round
  167. Send msg trough internal peerMsgQueue to ConsensusState service
  168. ```
  169. ### VoteSetBitsMessage handler
  170. ```go
  171. handleMessage(msg):
  172. Update prs for the bit-array of votes peer claims to have for the msg.BlockID
  173. ```
  174. The number of votes is limited to 10000 (`types.MaxVotesCount`) to protect the
  175. node against DOS attacks.
  176. ## Gossip Data Routine
  177. It is used to send the following messages to the peer: `BlockPartMessage`, `ProposalMessage` and
  178. `ProposalPOLMessage` on the DataChannel. The gossip data routine is based on the local RoundState (`rs`)
  179. and the known PeerRoundState (`prs`). The routine repeats forever the logic shown below:
  180. ```go
  181. 1a) if rs.ProposalBlockPartsHeader == prs.ProposalBlockPartsHeader and the peer does not have all the proposal parts then
  182. Part = pick a random proposal block part the peer does not have
  183. Send BlockPartMessage(rs.Height, rs.Round, Part) to the peer on the DataChannel
  184. if send returns true, record that the peer knows the corresponding block Part
  185. Continue
  186. 1b) if (0 < prs.Height) and (prs.Height < rs.Height) then
  187. help peer catch up using gossipDataForCatchup function
  188. Continue
  189. 1c) if (rs.Height != prs.Height) or (rs.Round != prs.Round) then
  190. Sleep PeerGossipSleepDuration
  191. Continue
  192. // at this point rs.Height == prs.Height and rs.Round == prs.Round
  193. 1d) if (rs.Proposal != nil and !prs.Proposal) then
  194. Send ProposalMessage(rs.Proposal) to the peer
  195. if send returns true, record that the peer knows Proposal
  196. if 0 <= rs.Proposal.POLRound then
  197. polRound = rs.Proposal.POLRound
  198. prevotesBitArray = rs.Votes.Prevotes(polRound).BitArray()
  199. Send ProposalPOLMessage(rs.Height, polRound, prevotesBitArray)
  200. Continue
  201. 2) Sleep PeerGossipSleepDuration
  202. ```
  203. ### Gossip Data For Catchup
  204. This function is responsible for helping peer catch up if it is at the smaller height (prs.Height < rs.Height).
  205. The function executes the following logic:
  206. ```go
  207. if peer does not have all block parts for prs.ProposalBlockPart then
  208. blockMeta = Load Block Metadata for height prs.Height from blockStore
  209. if (!blockMeta.BlockID.PartsHeader == prs.ProposalBlockPartsHeader) then
  210. Sleep PeerGossipSleepDuration
  211. return
  212. Part = pick a random proposal block part the peer does not have
  213. Send BlockPartMessage(prs.Height, prs.Round, Part) to the peer on the DataChannel
  214. if send returns true, record that the peer knows the corresponding block Part
  215. return
  216. else Sleep PeerGossipSleepDuration
  217. ```
  218. ## Gossip Votes Routine
  219. It is used to send the following message: `VoteMessage` on the VoteChannel.
  220. The gossip votes routine is based on the local RoundState (`rs`)
  221. and the known PeerRoundState (`prs`). The routine repeats forever the logic shown below:
  222. ```go
  223. 1a) if rs.Height == prs.Height then
  224. if prs.Step == RoundStepNewHeight then
  225. vote = random vote from rs.LastCommit the peer does not have
  226. Send VoteMessage(vote) to the peer
  227. if send returns true, continue
  228. if prs.Step <= RoundStepPrevote and prs.Round != -1 and prs.Round <= rs.Round then
  229. Prevotes = rs.Votes.Prevotes(prs.Round)
  230. vote = random vote from Prevotes the peer does not have
  231. Send VoteMessage(vote) to the peer
  232. if send returns true, continue
  233. if prs.Step <= RoundStepPrecommit and prs.Round != -1 and prs.Round <= rs.Round then
  234. Precommits = rs.Votes.Precommits(prs.Round)
  235. vote = random vote from Precommits the peer does not have
  236. Send VoteMessage(vote) to the peer
  237. if send returns true, continue
  238. if prs.ProposalPOLRound != -1 then
  239. PolPrevotes = rs.Votes.Prevotes(prs.ProposalPOLRound)
  240. vote = random vote from PolPrevotes the peer does not have
  241. Send VoteMessage(vote) to the peer
  242. if send returns true, continue
  243. 1b) if prs.Height != 0 and rs.Height == prs.Height+1 then
  244. vote = random vote from rs.LastCommit peer does not have
  245. Send VoteMessage(vote) to the peer
  246. if send returns true, continue
  247. 1c) if prs.Height != 0 and rs.Height >= prs.Height+2 then
  248. Commit = get commit from BlockStore for prs.Height
  249. vote = random vote from Commit the peer does not have
  250. Send VoteMessage(vote) to the peer
  251. if send returns true, continue
  252. 2) Sleep PeerGossipSleepDuration
  253. ```
  254. ## QueryMaj23Routine
  255. It is used to send the following message: `VoteSetMaj23Message`. `VoteSetMaj23Message` is sent to indicate that a given
  256. BlockID has seen +2/3 votes. This routine is based on the local RoundState (`rs`) and the known PeerRoundState
  257. (`prs`). The routine repeats forever the logic shown below.
  258. ```go
  259. 1a) if rs.Height == prs.Height then
  260. Prevotes = rs.Votes.Prevotes(prs.Round)
  261. if there is a ⅔ majority for some blockId in Prevotes then
  262. m = VoteSetMaj23Message(prs.Height, prs.Round, Prevote, blockId)
  263. Send m to peer
  264. Sleep PeerQueryMaj23SleepDuration
  265. 1b) if rs.Height == prs.Height then
  266. Precommits = rs.Votes.Precommits(prs.Round)
  267. if there is a ⅔ majority for some blockId in Precommits then
  268. m = VoteSetMaj23Message(prs.Height,prs.Round,Precommit,blockId)
  269. Send m to peer
  270. Sleep PeerQueryMaj23SleepDuration
  271. 1c) if rs.Height == prs.Height and prs.ProposalPOLRound >= 0 then
  272. Prevotes = rs.Votes.Prevotes(prs.ProposalPOLRound)
  273. if there is a ⅔ majority for some blockId in Prevotes then
  274. m = VoteSetMaj23Message(prs.Height,prs.ProposalPOLRound,Prevotes,blockId)
  275. Send m to peer
  276. Sleep PeerQueryMaj23SleepDuration
  277. 1d) if prs.CatchupCommitRound != -1 and 0 < prs.Height and
  278. prs.Height <= blockStore.Height() then
  279. Commit = LoadCommit(prs.Height)
  280. m = VoteSetMaj23Message(prs.Height,Commit.Round,Precommit,Commit.BlockID)
  281. Send m to peer
  282. Sleep PeerQueryMaj23SleepDuration
  283. 2) Sleep PeerQueryMaj23SleepDuration
  284. ```
  285. ## Broadcast routine
  286. 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
  287. events.
  288. It broadcasts `NewRoundStepMessage` or `CommitStepMessage` upon new round state event. Note that
  289. broadcasting these messages does not depend on the PeerRoundState; it is sent on the StateChannel.
  290. Upon receiving VoteMessage it broadcasts `HasVoteMessage` message to its peers on the StateChannel.
  291. ## Channels
  292. Defines 4 channels: state, data, vote and vote_set_bits. Each channel
  293. has `SendQueueCapacity` and `RecvBufferCapacity` and
  294. `RecvMessageCapacity` set to `maxMsgSize`.
  295. Sending incorrectly encoded data will result in stopping the peer.