mirror of
https://github.com/fluencelabs/tendermint
synced 2025-05-28 21:51:22 +00:00
consensus: Add ADR for first stage consensus refactor (#2462)
This commit is contained in:
parent
d2be7482e1
commit
e6a55b7be0
152
docs/architecture/adr-030-consensus-refactor.md
Normal file
152
docs/architecture/adr-030-consensus-refactor.md
Normal 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
|
Loading…
x
Reference in New Issue
Block a user