This adds a single-threaded implementation of ABCI++, with no optimization for splitting out verifying the header and verifying the proposal.
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()
// 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)
}
}
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) ∧ 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:
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
}
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) {
precommit_extension ← ABCI.ExtendVote(h_p, round_p, nil)
broadcast ⟨PRECOMMIT, h_p, round_p, nil, precommit_extension⟩
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
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:
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 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.
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.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.