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.

458 lines
13 KiB

  1. # ADR 030: Consensus Refactor
  2. ## Context
  3. One of the biggest challenges this project faces is to proof that the
  4. implementations of the specifications are correct, much like we strive to
  5. formaly verify our alogrithms and protocols we should work towards high
  6. confidence about the correctness of our program code. One of those is the core
  7. of Tendermint - Consensus - which currently resides in the `consensus` package.
  8. Over time there has been high friction making changes to the package due to the
  9. algorithm being scattered in a side-effectful container (the current
  10. `ConsensusState`). In order to test the algorithm a large object-graph needs to
  11. be set up and even than the non-deterministic parts of the container makes will
  12. prevent high certainty. Where ideally we have a 1-to-1 representation of the
  13. [spec](https://github.com/tendermint/spec), ready and easy to test for domain
  14. experts.
  15. Addresses:
  16. - [#1495](https://github.com/tendermint/tendermint/issues/1495)
  17. - [#1692](https://github.com/tendermint/tendermint/issues/1692)
  18. ## Decision
  19. To remedy these issues we plan a gradual, non-invasive refactoring of the
  20. `consensus` package. Starting of by isolating the consensus alogrithm into
  21. a pure function and a finite state machine to address the most pressuring issue
  22. of lack of confidence. Doing so while leaving the rest of the package in tact
  23. and have follow-up optional changes to improve the sepration of concerns.
  24. ### Implementation changes
  25. The core of Consensus can be modelled as a function with clear defined inputs:
  26. * `State` - data container for current round, height, etc.
  27. * `Event`- significant events in the network
  28. producing clear outputs;
  29. * `State` - updated input
  30. * `Message` - signal what actions to perform
  31. ```go
  32. type Event int
  33. const (
  34. EventUnknown Event = iota
  35. EventProposal
  36. Majority23PrevotesBlock
  37. Majority23PrecommitBlock
  38. Majority23PrevotesAny
  39. Majority23PrecommitAny
  40. TimeoutNewRound
  41. TimeoutPropose
  42. TimeoutPrevotes
  43. TimeoutPrecommit
  44. )
  45. type Message int
  46. const (
  47. MeesageUnknown Message = iota
  48. MessageProposal
  49. MessageVotes
  50. MessageDecision
  51. )
  52. type State struct {
  53. height uint64
  54. round uint64
  55. step uint64
  56. lockedValue interface{} // TODO: Define proper type.
  57. lockedRound interface{} // TODO: Define proper type.
  58. validValue interface{} // TODO: Define proper type.
  59. validRound interface{} // TODO: Define proper type.
  60. // From the original notes: valid(v)
  61. valid interface{} // TODO: Define proper type.
  62. // From the original notes: proposer(h, r)
  63. proposer interface{} // TODO: Define proper type.
  64. }
  65. func Consensus(Event, State) (State, Message) {
  66. // Consolidate implementation.
  67. }
  68. ```
  69. Tracking of relevant information to feed `Event` into the function and act on
  70. the output is left to the `ConsensusExecutor` (formerly `ConsensusState`).
  71. Benefits for testing surfacing nicely as testing for a sequence of events
  72. against algorithm could be as simple as the following example:
  73. ``` go
  74. func TestConsensusXXX(t *testing.T) {
  75. type expected struct {
  76. message Message
  77. state State
  78. }
  79. // Setup order of events, initial state and expectation.
  80. var (
  81. events = []struct {
  82. event Event
  83. want expected
  84. }{
  85. // ...
  86. }
  87. state = State{
  88. // ...
  89. }
  90. )
  91. for _, e := range events {
  92. sate, msg = Consensus(e.event, state)
  93. // Test message expectation.
  94. if msg != e.want.message {
  95. t.Fatalf("have %v, want %v", msg, e.want.message)
  96. }
  97. // Test state expectation.
  98. if !reflect.DeepEqual(state, e.want.state) {
  99. t.Fatalf("have %v, want %v", state, e.want.state)
  100. }
  101. }
  102. }
  103. ```
  104. ## Consensus Executor
  105. ## Consensus Core
  106. ```go
  107. type Event interface{}
  108. type EventNewHeight struct {
  109. Height int64
  110. ValidatorId int
  111. }
  112. type EventNewRound HeightAndRound
  113. type EventProposal struct {
  114. Height int64
  115. Round int
  116. Timestamp Time
  117. BlockID BlockID
  118. POLRound int
  119. Sender int
  120. }
  121. type Majority23PrevotesBlock struct {
  122. Height int64
  123. Round int
  124. BlockID BlockID
  125. }
  126. type Majority23PrecommitBlock struct {
  127. Height int64
  128. Round int
  129. BlockID BlockID
  130. }
  131. type HeightAndRound struct {
  132. Height int64
  133. Round int
  134. }
  135. type Majority23PrevotesAny HeightAndRound
  136. type Majority23PrecommitAny HeightAndRound
  137. type TimeoutPropose HeightAndRound
  138. type TimeoutPrevotes HeightAndRound
  139. type TimeoutPrecommit HeightAndRound
  140. type Message interface{}
  141. type MessageProposal struct {
  142. Height int64
  143. Round int
  144. BlockID BlockID
  145. POLRound int
  146. }
  147. type VoteType int
  148. const (
  149. VoteTypeUnknown VoteType = iota
  150. Prevote
  151. Precommit
  152. )
  153. type MessageVote struct {
  154. Height int64
  155. Round int
  156. BlockID BlockID
  157. Type VoteType
  158. }
  159. type MessageDecision struct {
  160. Height int64
  161. Round int
  162. BlockID BlockID
  163. }
  164. type TriggerTimeout struct {
  165. Height int64
  166. Round int
  167. Duration Duration
  168. }
  169. type RoundStep int
  170. const (
  171. RoundStepUnknown RoundStep = iota
  172. RoundStepPropose
  173. RoundStepPrevote
  174. RoundStepPrecommit
  175. RoundStepCommit
  176. )
  177. type State struct {
  178. Height int64
  179. Round int
  180. Step RoundStep
  181. LockedValue BlockID
  182. LockedRound int
  183. ValidValue BlockID
  184. ValidRound int
  185. ValidatorId int
  186. ValidatorSetSize int
  187. }
  188. func proposer(height int64, round int) int {}
  189. func getValue() BlockID {}
  190. func Consensus(event Event, state State) (State, Message, TriggerTimeout) {
  191. msg = nil
  192. timeout = nil
  193. switch event := event.(type) {
  194. case EventNewHeight:
  195. if event.Height > state.Height {
  196. state.Height = event.Height
  197. state.Round = -1
  198. state.Step = RoundStepPropose
  199. state.LockedValue = nil
  200. state.LockedRound = -1
  201. state.ValidValue = nil
  202. state.ValidRound = -1
  203. state.ValidatorId = event.ValidatorId
  204. }
  205. return state, msg, timeout
  206. case EventNewRound:
  207. if event.Height == state.Height and event.Round > state.Round {
  208. state.Round = eventRound
  209. state.Step = RoundStepPropose
  210. if proposer(state.Height, state.Round) == state.ValidatorId {
  211. proposal = state.ValidValue
  212. if proposal == nil {
  213. proposal = getValue()
  214. }
  215. msg = MessageProposal { state.Height, state.Round, proposal, state.ValidRound }
  216. }
  217. timeout = TriggerTimeout { state.Height, state.Round, timeoutPropose(state.Round) }
  218. }
  219. return state, msg, timeout
  220. case EventProposal:
  221. if event.Height == state.Height and event.Round == state.Round and
  222. event.Sender == proposal(state.Height, state.Round) and state.Step == RoundStepPropose {
  223. if event.POLRound >= state.LockedRound or event.BlockID == state.BlockID or state.LockedRound == -1 {
  224. msg = MessageVote { state.Height, state.Round, event.BlockID, Prevote }
  225. }
  226. state.Step = RoundStepPrevote
  227. }
  228. return state, msg, timeout
  229. case TimeoutPropose:
  230. if event.Height == state.Height and event.Round == state.Round and state.Step == RoundStepPropose {
  231. msg = MessageVote { state.Height, state.Round, nil, Prevote }
  232. state.Step = RoundStepPrevote
  233. }
  234. return state, msg, timeout
  235. case Majority23PrevotesBlock:
  236. if event.Height == state.Height and event.Round == state.Round and state.Step >= RoundStepPrevote and event.Round > state.ValidRound {
  237. state.ValidRound = event.Round
  238. state.ValidValue = event.BlockID
  239. if state.Step == RoundStepPrevote {
  240. state.LockedRound = event.Round
  241. state.LockedValue = event.BlockID
  242. msg = MessageVote { state.Height, state.Round, event.BlockID, Precommit }
  243. state.Step = RoundStepPrecommit
  244. }
  245. }
  246. return state, msg, timeout
  247. case Majority23PrevotesAny:
  248. if event.Height == state.Height and event.Round == state.Round and state.Step == RoundStepPrevote {
  249. timeout = TriggerTimeout { state.Height, state.Round, timeoutPrevote(state.Round) }
  250. }
  251. return state, msg, timeout
  252. case TimeoutPrevote:
  253. if event.Height == state.Height and event.Round == state.Round and state.Step == RoundStepPrevote {
  254. msg = MessageVote { state.Height, state.Round, nil, Precommit }
  255. state.Step = RoundStepPrecommit
  256. }
  257. return state, msg, timeout
  258. case Majority23PrecommitBlock:
  259. if event.Height == state.Height {
  260. state.Step = RoundStepCommit
  261. state.LockedValue = event.BlockID
  262. }
  263. return state, msg, timeout
  264. case Majority23PrecommitAny:
  265. if event.Height == state.Height and event.Round == state.Round {
  266. timeout = TriggerTimeout { state.Height, state.Round, timeoutPrecommit(state.Round) }
  267. }
  268. return state, msg, timeout
  269. case TimeoutPrecommit:
  270. if event.Height == state.Height and event.Round == state.Round {
  271. state.Round = state.Round + 1
  272. }
  273. return state, msg, timeout
  274. }
  275. }
  276. func ConsensusExecutor() {
  277. proposal = nil
  278. votes = HeightVoteSet { Height: 1 }
  279. state = State {
  280. Height: 1
  281. Round: 0
  282. Step: RoundStepPropose
  283. LockedValue: nil
  284. LockedRound: -1
  285. ValidValue: nil
  286. ValidRound: -1
  287. }
  288. event = EventNewHeight {1, id}
  289. state, msg, timeout = Consensus(event, state)
  290. event = EventNewRound {state.Height, 0}
  291. state, msg, timeout = Consensus(event, state)
  292. if msg != nil {
  293. send msg
  294. }
  295. if timeout != nil {
  296. trigger timeout
  297. }
  298. for {
  299. select {
  300. case message := <- msgCh:
  301. switch msg := message.(type) {
  302. case MessageProposal:
  303. case MessageVote:
  304. if msg.Height == state.Height {
  305. newVote = votes.AddVote(msg)
  306. if newVote {
  307. switch msg.Type {
  308. case Prevote:
  309. prevotes = votes.Prevotes(msg.Round)
  310. if prevotes.WeakCertificate() and msg.Round > state.Round {
  311. event = EventNewRound { msg.Height, msg.Round }
  312. state, msg, timeout = Consensus(event, state)
  313. state = handleStateChange(state, msg, timeout)
  314. }
  315. if blockID, ok = prevotes.TwoThirdsMajority(); ok and blockID != nil {
  316. if msg.Round == state.Round and hasBlock(blockID) {
  317. event = Majority23PrevotesBlock { msg.Height, msg.Round, blockID }
  318. state, msg, timeout = Consensus(event, state)
  319. state = handleStateChange(state, msg, timeout)
  320. }
  321. if proposal != nil and proposal.POLRound == msg.Round and hasBlock(blockID) {
  322. event = EventProposal {
  323. Height: state.Height
  324. Round: state.Round
  325. BlockID: blockID
  326. POLRound: proposal.POLRound
  327. Sender: message.Sender
  328. }
  329. state, msg, timeout = Consensus(event, state)
  330. state = handleStateChange(state, msg, timeout)
  331. }
  332. }
  333. if prevotes.HasTwoThirdsAny() and msg.Round == state.Round {
  334. event = Majority23PrevotesAny { msg.Height, msg.Round, blockID }
  335. state, msg, timeout = Consensus(event, state)
  336. state = handleStateChange(state, msg, timeout)
  337. }
  338. case Precommit:
  339. }
  340. }
  341. }
  342. case timeout := <- timeoutCh:
  343. case block := <- blockCh:
  344. }
  345. }
  346. }
  347. func handleStateChange(state, msg, timeout) State {
  348. if state.Step == Commit {
  349. state = ExecuteBlock(state.LockedValue)
  350. }
  351. if msg != nil {
  352. send msg
  353. }
  354. if timeout != nil {
  355. trigger timeout
  356. }
  357. }
  358. ```
  359. ### Implementation roadmap
  360. * implement proposed implementation
  361. * replace currently scattered calls in `ConsensusState` with calls to the new
  362. `Consensus` function
  363. * rename `ConsensusState` to `ConsensusExecutor` to avoid confusion
  364. * propose design for improved separation and clear information flow between
  365. `ConsensusExecutor` and `ConsensusReactor`
  366. ## Status
  367. Draft.
  368. ## Consequences
  369. ### Positive
  370. - isolated implementation of the algorithm
  371. - improved testability - simpler to proof correctness
  372. - clearer separation of concerns - easier to reason
  373. ### Negative
  374. ### Neutral