mirror of
https://github.com/tendermint/tendermint.git
synced 2026-02-03 10:32:05 +00:00
199 lines
6.0 KiB
Markdown
199 lines
6.0 KiB
Markdown
# Tendermint v4 Markdown pseudocode
|
||
|
||
This is a multi-threaded implementation of ABCI++,
|
||
where ProcessProposal starts when the proposal is received, but ends before precommitting.
|
||
|
||
### Initialization
|
||
|
||
```go
|
||
h_p ← 0
|
||
round_p ← 0
|
||
step_p is one of {propose, prevote, precommit}
|
||
decision_p ← Vector()
|
||
lockedValue_p ← nil
|
||
validValue_p ← nil
|
||
validRound_p ← -1
|
||
```
|
||
|
||
### StartRound(round)
|
||
|
||
```go
|
||
function startRound(round) {
|
||
round_p ← round
|
||
step_p ← propose
|
||
if proposer(h_p, round_p) = p {
|
||
if validValue_p != nil {
|
||
proposal ← validValue_p
|
||
} else {
|
||
txdata ← mempool.GetBlock()
|
||
// getUnpreparedBlockProposal fills in header
|
||
unpreparedProposal ← getUnpreparedBlockProposal(txdata)
|
||
proposal ← ABCI.PrepareProposal(unpreparedProposal)
|
||
}
|
||
broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩
|
||
} else {
|
||
schedule OnTimeoutPropose(h_p,round_p) to be executed after timeoutPropose(round_p)
|
||
}
|
||
}
|
||
```
|
||
|
||
### ReceiveProposal
|
||
|
||
In the case where the local node is not locked on any round, the following is ran:
|
||
|
||
```go
|
||
upon ⟨PROPOSAL, h_p, round_p, v, −1) from proposer(h_p, round_p) while step_p = propose do {
|
||
if valid(v) ∧ ABCI.VerifyHeader(h_p, v.header) ∧ (lockedRound_p = −1 ∨ lockedValue_p = v) {
|
||
// We fork process proposal into a parallel process
|
||
Fork ABCI.ProcessProposal(h_p, v)
|
||
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
|
||
} else {
|
||
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
|
||
}
|
||
step_p ← prevote
|
||
}
|
||
```
|
||
|
||
In the case where the node is locked on a round, the following is ran:
|
||
|
||
```go
|
||
upon ⟨PROPOSAL, h_p, round_p, v, vr⟩
|
||
from proposer(h_p, round_p)
|
||
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩
|
||
while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do {
|
||
if valid(v) ∧ ABCI.VerifyHeader(h_p, v.header) ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) {
|
||
// We fork process proposal into a parallel process
|
||
Fork ABCI.ProcessProposal(h_p, v)
|
||
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
|
||
} else {
|
||
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
|
||
}
|
||
step_p ← prevote
|
||
}
|
||
```
|
||
|
||
### Prevote timeout
|
||
|
||
Upon receiving 2f + 1 prevotes, setup a timeout.
|
||
|
||
```go
|
||
upon 2f + 1 ⟨PREVOTE, h_p, vr, -1⟩
|
||
with step_p = prevote for the first time, do {
|
||
schedule OnTimeoutPrevote(h_p, round_p) to be executed after timeoutPrevote(round_p)
|
||
}
|
||
```
|
||
|
||
with OnTimeoutPrevote defined as:
|
||
|
||
```go
|
||
def OnTimeoutPrevote(height, round) {
|
||
if (height = h_p && round = round_p && step_p = prevote) {
|
||
// Join the ProcessProposal, and output any evidence in case it has some.
|
||
processProposalOutput ← Join ABCI.ProcessProposal(h_p, v)
|
||
for evidence in processProposalOutput.evidence_list {
|
||
broadcast ⟨EVIDENCE, evidence⟩
|
||
}
|
||
|
||
precommit_extension ← ABCI.ExtendVote(h_p, round_p, nil)
|
||
broadcast ⟨PRECOMMIT, h_p, round_p, nil, precommit_extension⟩
|
||
step_p ← precommit
|
||
}
|
||
}
|
||
```
|
||
|
||
### Receiving enough prevotes to precommit
|
||
|
||
The following code is ran upon receiving 2f + 1 prevotes for the same block
|
||
|
||
```go
|
||
upon ⟨PROPOSAL, h_p, round_p, v, *⟩
|
||
from proposer(h_p, round_p)
|
||
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩
|
||
while valid(v) ∧ step_p >= prevote for the first time do {
|
||
if (step_p = prevote) {
|
||
lockedValue_p ← v
|
||
lockedRound_p ← round_p
|
||
processProposalOutput ← Join ABCI.ProcessProposal(h_p, v)
|
||
// If the proposal is valid precommit as before.
|
||
// If it was invalid, precommit nil.
|
||
// Note that ABCI.ProcessProposal(h_p, v).accept is deterministic for all honest nodes.
|
||
precommit_value ← nil
|
||
if processProposalOutput.accept {
|
||
precommit_value ← id(v)
|
||
}
|
||
precommit_extension ← ABCI.ExtendVote(h_p, round_p, precommit_value)
|
||
broadcast ⟨PRECOMMIT, h_p, round_p, precommit_value, precommit_extension⟩
|
||
for evidence in processProposalOutput.evidence_list {
|
||
broadcast ⟨EVIDENCE, evidence⟩
|
||
}
|
||
|
||
step_p ← precommit
|
||
}
|
||
validValue_p ← v
|
||
validRound_p ← round_p
|
||
}
|
||
```
|
||
|
||
And upon receiving 2f + 1 prevotes for nil:
|
||
|
||
```go
|
||
upon 2f + 1 ⟨PREVOTE, h_p, round_p, nil⟩
|
||
while step_p = prevote do {
|
||
// Join ABCI.ProcessProposal, and broadcast any evidence if it exists.
|
||
processProposalOutput ← Join ABCI.ProcessProposal(h_p, v)
|
||
for evidence in processProposalOutput.evidence_list {
|
||
broadcast ⟨EVIDENCE, evidence⟩
|
||
}
|
||
|
||
precommit_extension ← ABCI.ExtendVote(h_p, round_p, nil)
|
||
broadcast ⟨PRECOMMIT, h_p, round_p, nil, precommit_extension⟩
|
||
step_p ← precommit
|
||
}
|
||
```
|
||
|
||
### Upon receiving a precommit
|
||
|
||
Upon receiving a precommit `precommit`, we ensure that `ABCI.VerifyVoteExtension(precommit.precommit_extension) = true`
|
||
before accepting the precommit. This is akin to how we check the signature on precommits normally, hence its not wrapped
|
||
in the syntax of methods from the paper.
|
||
|
||
### Precommit timeout
|
||
|
||
Upon receiving 2f + 1 precommits, setup a timeout.
|
||
|
||
```go
|
||
upon 2f + 1 ⟨PRECOMMIT, h_p, vr, *⟩ for the first time, do {
|
||
schedule OnTimeoutPrecommit(h_p, round_p) to be executed after timeoutPrecommit(round_p)
|
||
}
|
||
```
|
||
|
||
with OnTimeoutPrecommit defined as:
|
||
|
||
```go
|
||
def OnTimeoutPrecommit(height, round) {
|
||
if (height = h_p && round = round_p) {
|
||
StartRound(round_p + 1)
|
||
}
|
||
}
|
||
```
|
||
|
||
### Upon Receiving 2f + 1 precommits
|
||
|
||
The following code is ran upon receiving 2f + 1 precommits for the same block
|
||
|
||
```go
|
||
upon ⟨PROPOSAL, h_p, r, v, *⟩
|
||
from proposer(h_p, r)
|
||
AND 2f + 1 ⟨ PRECOMMIT, h_p, r, id(v)⟩
|
||
while decision_p[h_p] = nil do {
|
||
if (valid(v)) {
|
||
decision_p[h_p] ← v
|
||
h_p ← h_p + 1
|
||
reset lockedRound_p, lockedValue_p,validRound_p and validValue_p to initial values
|
||
ABCI.FinalizeBlock(id(v))
|
||
StartRound(0)
|
||
}
|
||
}
|
||
```
|
||
|
||
If we don't see 2f + 1 precommits for the same block, we wait until we get 2f + 1 precommits, and the timeout occurs. |