This adds hooks for the existing ABCI to the prior pseudocode
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
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)
}
}
In the case where the local node is not locked on any round, the following is ran:
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:
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
}
Upon receiving 2f + 1 prevotes, setup a timeout.
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:
function OnTimeoutPrevote(height, round) {
if (height = h_p && round = round_p && step_p = prevote) {
broadcast ⟨PRECOMMIT, h_p, round_p, nil⟩
step_p ← precommit
}
}
The following code is ran upon receiving 2f + 1 prevotes for the same block
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:
upon 2f + 1 ⟨PREVOTE, h_p, round_p, nil⟩
while step_p = prevote do {
broadcast ⟨PRECOMMIT, h_p, round_p, nil⟩
step_p ← precommit
}
Upon receiving 2f + 1 precommits, setup a timeout.
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:
function OnTimeoutPrecommit(height, round) {
if (height = h_p && round = round_p) {
StartRound(round_p + 1)
}
}
The following code is ran upon receiving 2f + 1 precommits for the same block
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.