@ -0,0 +1,156 @@ | |||
# Tendermint v0 Markdown pseudocode | |||
This translates the latex code for Tendermint consensus from the Tendermint paper into markdown. | |||
### Initialization | |||
```go | |||
h_p ← 0 | |||
round_p ← 0 | |||
step_p is one of {propose, prevote, precommit} | |||
decision_p ← Vector() | |||
lockedRound_p ← -1 | |||
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 { | |||
proposal ← getValue() | |||
} | |||
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) ∧ (lockedRound_p = −1 ∨ lockedValue_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) ∧ (lockedRound_p ≤ vr ∨ lockedValue_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, *⟩ 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 | |||
function OnTimeoutPrevote(height, round) { | |||
if (height = h_p && round = round_p && step_p = prevote) { | |||
broadcast ⟨PRECOMMIT, h_p, round_p, nil⟩ | |||
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, round_p, id(v)⟩ | |||
while valid(v) ∧ step_p >= prevote for the first time do { | |||
if (step_p = prevote) { | |||
lockedValue_p ← v | |||
lockedRound_p ← round_p | |||
broadcast ⟨PRECOMMIT, h_p, round_p, id(v)⟩ | |||
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 { | |||
broadcast ⟨PRECOMMIT, h_p, round_p, nil⟩ | |||
step_p ← precommit | |||
} | |||
``` | |||
### 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 | |||
function 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 | |||
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. |
@ -0,0 +1,162 @@ | |||
# Tendermint v1 Markdown pseudocode | |||
This adds hooks for the existing ABCI to the prior pseudocode | |||
### 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() | |||
// getBlockProposal fills in header | |||
proposal ← getBlockProposal(txdata) | |||
} | |||
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) ∧ (lockedRound_p = −1 ∨ lockedValue_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) ∧ (lockedRound_p ≤ vr ∨ lockedValue_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 | |||
function OnTimeoutPrevote(height, round) { | |||
if (height = h_p && round = round_p && step_p = prevote) { | |||
broadcast ⟨PRECOMMIT, h_p, round_p, nil⟩ | |||
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 | |||
broadcast ⟨PRECOMMIT, h_p, round_p, id(v)⟩ | |||
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 { | |||
broadcast ⟨PRECOMMIT, h_p, round_p, nil⟩ | |||
step_p ← precommit | |||
} | |||
``` | |||
### 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 | |||
function 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.BeginBlock(v.header) | |||
ABCI.DeliverTxs(v.data) | |||
ABCI.EndBlock() | |||
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. |
@ -0,0 +1,180 @@ | |||
# Tendermint v2 Markdown pseudocode | |||
This adds a single-threaded implementation of ABCI++, | |||
with no optimization for splitting out verifying the header and verifying the proposal. | |||
### 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 takes tx data, and fills in the unprepared header data | |||
unpreparedProposal ← getUnpreparedBlockProposal(txdata) | |||
// ABCI++: the proposer may reorder/update transactions in `unpreparedProposal` | |||
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.ProcessProposal(h_p, v).accept ∧ (lockedRound_p = −1 ∨ lockedValue_p = v) { | |||
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩ | |||
} else { | |||
broadcast ⟨PREVOTE, h_p, round_p, nil⟩ | |||
// Include any slashing evidence that may be sent in the process proposal response | |||
for evidence in ABCI.ProcessProposal(h_p, v).evidence_list { | |||
broadcast ⟨EVIDENCE, evidence⟩ | |||
} | |||
} | |||
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.ProcessProposal(h_p, v).accept ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) { | |||
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩ | |||
} else { | |||
broadcast ⟨PREVOTE, h_p, round_p, nil⟩ | |||
// Include any slashing evidence that may be sent in the process proposal response | |||
for evidence in ABCI.ProcessProposal(h_p, v).evidence_list { | |||
broadcast ⟨EVIDENCE, evidence⟩ | |||
} | |||
} | |||
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 | |||
function OnTimeoutPrevote(height, round) { | |||
if (height = h_p && round = round_p && step_p = prevote) { | |||
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 | |||
precommit_extension ← ABCI.ExtendVote(h_p, round_p, id(v)) | |||
broadcast ⟨PRECOMMIT, h_p, round_p, id(v), precommit_extension⟩ | |||
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 { | |||
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 | |||
function 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. |
@ -0,0 +1,201 @@ | |||
# Tendermint v3 Markdown pseudocode | |||
This is a single-threaded implementation of ABCI++, | |||
with an optimization for the ProcessProposal phase. | |||
Namely, processing of the header and the block data is separated into two different functions. | |||
### 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_header, −1) from proposer(h_p, round_p) while step_p = propose do { | |||
prevote_nil ← false | |||
// valid is Tendermints validation, ABCI.VerifyHeader is the applications | |||
if valid(v_header) ∧ ABCI.VerifyHeader(h_p, v_header) ∧ (lockedRound_p = −1 ∨ lockedValue_p = id(v_header)) { | |||
wait to receive proposal v corresponding to v_header | |||
// We split up the app's header verification from the remainder of its processing of the proposal | |||
if ABCI.ProcessProposal(h_p, v).accept { | |||
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩ | |||
} else { | |||
prevote_nil ← true | |||
// Include any slashing evidence that may be sent in the process proposal response | |||
for evidence in ABCI.ProcessProposal(h_p, v).evidence_list { | |||
broadcast ⟨EVIDENCE, evidence⟩ | |||
} | |||
} | |||
} else { | |||
prevote_nil ← true | |||
} | |||
if prevote_nil { | |||
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_header, vr⟩ | |||
from proposer(h_p, round_p) | |||
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v_header)⟩ | |||
while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do { | |||
prevote_nil ← false | |||
if valid(v) ∧ ABCI.VerifyHeader(h_p, v.header) ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) { | |||
wait to receive proposal v corresponding to v_header | |||
// We split up the app's header verification from the remainder of its processing of the proposal | |||
if ABCI.ProcessProposal(h_p, v).accept { | |||
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩ | |||
} else { | |||
prevote_nil ← true | |||
// Include any slashing evidence that may be sent in the process proposal response | |||
for evidence in ABCI.ProcessProposal(h_p, v).evidence_list { | |||
broadcast ⟨EVIDENCE, evidence⟩ | |||
} | |||
} | |||
} else { | |||
prevote_nil ← true | |||
} | |||
if prevote_nil { | |||
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 | |||
function OnTimeoutPrevote(height, round) { | |||
if (height = h_p && round = round_p && step_p = prevote) { | |||
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 | |||
precommit_extension ← ABCI.ExtendVote(h_p, round_p, id(v)) | |||
broadcast ⟨PRECOMMIT, h_p, round_p, id(v), precommit_extension⟩ | |||
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 { | |||
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 | |||
function 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. |
@ -0,0 +1,199 @@ | |||
# 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. |