Files
tendermint/spec/abci++/v1.md
T

4.0 KiB
Raw Blame History

Tendermint v1 Markdown pseudocode

This adds hooks for the existing ABCI to the prior pseudocode

Initialization

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)

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:

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
}

Prevote timeout

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
    }
}

Receiving enough prevotes to 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
}

Precommit timeout

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)
    }
}

Upon Receiving 2f + 1 precommits

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.