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.

370 lines
15 KiB

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