consensus: Add ADR for first stage consensus refactor (#2462)

This commit is contained in:
Alexander Simmerl 2018-10-05 02:35:35 +02:00 committed by Ethan Buchman
parent d2be7482e1
commit e6a55b7be0
1 changed files with 152 additions and 0 deletions

View File

@ -0,0 +1,152 @@
# ADR 030: Consensus Refactor
## Context
One of the biggest challenges this project faces is to proof that the
implementations of the specifications are correct, much like we strive to
formaly verify our alogrithms and protocols we should work towards high
confidence about the correctness of our program code. One of those is the core
of Tendermint - Consensus - which currently resides in the `consensus` package.
Over time there has been high friction making changes to the package due to the
algorithm being scattered in a side-effectful container (the current
`ConsensusState`). In order to test the algorithm a large object-graph needs to
be set up and even than the non-deterministic parts of the container makes will
prevent high certainty. Where ideally we have a 1-to-1 representation of the
[spec](https://github.com/tendermint/spec), ready and easy to test for domain
experts.
Addresses:
- [#1495](https://github.com/tendermint/tendermint/issues/1495)
- [#1692](https://github.com/tendermint/tendermint/issues/1692)
## Decision
To remedy these issues we plan a gradual, non-invasive refactoring of the
`consensus` package. Starting of by isolating the consensus alogrithm into
a pure function and a finite state machine to address the most pressuring issue
of lack of confidence. Doing so while leaving the rest of the package in tact
and have follow-up optional changes to improve the sepration of concerns.
### Implementation changes
The core of Consensus can be modelled as a function with clear defined inputs:
* `State` - data container for current round, height, etc.
* `Event`- significant events in the network
producing clear outputs;
* `State` - updated input
* `Message` - signal what actions to perform
```go
type Event int
const (
EventUnknown Event = iota
EventProposal
Majority23PrevotesBlock
Majority23PrecommitBlock
Majority23PrevotesAny
Majority23PrecommitAny
TimeoutNewRound
TimeoutPropose
TimeoutPrevotes
TimeoutPrecommit
)
type Message int
const (
MeesageUnknown Message = iota
MessageProposal
MessageVotes
MessageDecision
)
type State struct {
height uint64
round uint64
step uint64
lockedValue interface{} // TODO: Define proper type.
lockedRound interface{} // TODO: Define proper type.
validValue interface{} // TODO: Define proper type.
validRound interface{} // TODO: Define proper type.
// From the original notes: valid(v)
valid interface{} // TODO: Define proper type.
// From the original notes: proposer(h, r)
proposer interface{} // TODO: Define proper type.
}
func Consensus(Event, State) (State, Message) {
// Consolidate implementation.
}
```
Tracking of relevant information to feed `Event` into the function and act on
the output is left to the `ConsensusExecutor` (formerly `ConsensusState`).
Benefits for testing surfacing nicely as testing for a sequence of events
against algorithm could be as simple as the following example:
``` go
func TestConsensusXXX(t *testing.T) {
type expected struct {
message Message
state State
}
// Setup order of events, initial state and expectation.
var (
events = []struct {
event Event
want expected
}{
// ...
}
state = State{
// ...
}
)
for _, e := range events {
sate, msg = Consensus(e.event, state)
// Test message expectation.
if msg != e.want.message {
t.Fatalf("have %v, want %v", msg, e.want.message)
}
// Test state expectation.
if !reflect.DeepEqual(state, e.want.state) {
t.Fatalf("have %v, want %v", state, e.want.state)
}
}
}
```
### Implementation roadmap
* implement proposed implementation
* replace currently scattered calls in `ConsensusState` with calls to the new
`Consensus` function
* rename `ConsensusState` to `ConsensusExecutor` to avoid confusion
* propose design for improved separation and clear information flow between
`ConsensusExecutor` and `ConsensusReactor`
## Status
Draft.
## Consequences
### Positive
- isolated implementation of the algorithm
- improved testability - simpler to proof correctness
- clearer separation of concerns - easier to reason
### Negative
### Neutral