mirror of
https://github.com/fluencelabs/tendermint
synced 2025-05-12 06:47:12 +00:00
153 lines
4.0 KiB
Markdown
153 lines
4.0 KiB
Markdown
|
# 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
|