github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/spec/abci++/v4.md (about) 1 # Tendermint v4 Markdown pseudocode 2 3 This is a multi-threaded implementation of ABCI++, 4 where ProcessProposal starts when the proposal is received, but ends before precommitting. 5 6 ### Initialization 7 8 ```go 9 h_p ← 0 10 round_p ← 0 11 step_p is one of {propose, prevote, precommit} 12 decision_p ← Vector() 13 lockedValue_p ← nil 14 validValue_p ← nil 15 validRound_p ← -1 16 ``` 17 18 ### StartRound(round) 19 20 ```go 21 function startRound(round) { 22 round_p ← round 23 step_p ← propose 24 if proposer(h_p, round_p) = p { 25 if validValue_p != nil { 26 proposal ← validValue_p 27 } else { 28 txdata ← mempool.GetBlock() 29 // getUnpreparedBlockProposal fills in header 30 unpreparedProposal ← getUnpreparedBlockProposal(txdata) 31 proposal ← ABCI.PrepareProposal(unpreparedProposal) 32 } 33 broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩ 34 } else { 35 schedule OnTimeoutPropose(h_p,round_p) to be executed after timeoutPropose(round_p) 36 } 37 } 38 ``` 39 40 ### ReceiveProposal 41 42 In the case where the local node is not locked on any round, the following is ran: 43 44 ```go 45 upon ⟨PROPOSAL, h_p, round_p, v, −1) from proposer(h_p, round_p) while step_p = propose do { 46 if valid(v) ∧ ABCI.VerifyHeader(h_p, v.header) ∧ (lockedRound_p = −1 ∨ lockedValue_p = v) { 47 // We fork process proposal into a parallel process 48 Fork ABCI.ProcessProposal(h_p, v) 49 broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩ 50 } else { 51 broadcast ⟨PREVOTE, h_p, round_p, nil⟩ 52 } 53 step_p ← prevote 54 } 55 ``` 56 57 In the case where the node is locked on a round, the following is ran: 58 59 ```go 60 upon ⟨PROPOSAL, h_p, round_p, v, vr⟩ 61 from proposer(h_p, round_p) 62 AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩ 63 while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do { 64 if valid(v) ∧ ABCI.VerifyHeader(h_p, v.header) ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) { 65 // We fork process proposal into a parallel process 66 Fork ABCI.ProcessProposal(h_p, v) 67 broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩ 68 } else { 69 broadcast ⟨PREVOTE, h_p, round_p, nil⟩ 70 } 71 step_p ← prevote 72 } 73 ``` 74 75 ### Prevote timeout 76 77 Upon receiving 2f + 1 prevotes, setup a timeout. 78 79 ```go 80 upon 2f + 1 ⟨PREVOTE, h_p, vr, -1⟩ 81 with step_p = prevote for the first time, do { 82 schedule OnTimeoutPrevote(h_p, round_p) to be executed after timeoutPrevote(round_p) 83 } 84 ``` 85 86 with OnTimeoutPrevote defined as: 87 88 ```go 89 def OnTimeoutPrevote(height, round) { 90 if (height = h_p && round = round_p && step_p = prevote) { 91 // Join the ProcessProposal, and output any evidence in case it has some. 92 processProposalOutput ← Join ABCI.ProcessProposal(h_p, v) 93 for evidence in processProposalOutput.evidence_list { 94 broadcast ⟨EVIDENCE, evidence⟩ 95 } 96 97 precommit_extension ← ABCI.ExtendVote(h_p, round_p, nil) 98 broadcast ⟨PRECOMMIT, h_p, round_p, nil, precommit_extension⟩ 99 step_p ← precommit 100 } 101 } 102 ``` 103 104 ### Receiving enough prevotes to precommit 105 106 The following code is ran upon receiving 2f + 1 prevotes for the same block 107 108 ```go 109 upon ⟨PROPOSAL, h_p, round_p, v, *⟩ 110 from proposer(h_p, round_p) 111 AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩ 112 while valid(v) ∧ step_p >= prevote for the first time do { 113 if (step_p = prevote) { 114 lockedValue_p ← v 115 lockedRound_p ← round_p 116 processProposalOutput ← Join ABCI.ProcessProposal(h_p, v) 117 // If the proposal is valid precommit as before. 118 // If it was invalid, precommit nil. 119 // Note that ABCI.ProcessProposal(h_p, v).accept is deterministic for all honest nodes. 120 precommit_value ← nil 121 if processProposalOutput.accept { 122 precommit_value ← id(v) 123 } 124 precommit_extension ← ABCI.ExtendVote(h_p, round_p, precommit_value) 125 broadcast ⟨PRECOMMIT, h_p, round_p, precommit_value, precommit_extension⟩ 126 for evidence in processProposalOutput.evidence_list { 127 broadcast ⟨EVIDENCE, evidence⟩ 128 } 129 130 step_p ← precommit 131 } 132 validValue_p ← v 133 validRound_p ← round_p 134 } 135 ``` 136 137 And upon receiving 2f + 1 prevotes for nil: 138 139 ```go 140 upon 2f + 1 ⟨PREVOTE, h_p, round_p, nil⟩ 141 while step_p = prevote do { 142 // Join ABCI.ProcessProposal, and broadcast any evidence if it exists. 143 processProposalOutput ← Join ABCI.ProcessProposal(h_p, v) 144 for evidence in processProposalOutput.evidence_list { 145 broadcast ⟨EVIDENCE, evidence⟩ 146 } 147 148 precommit_extension ← ABCI.ExtendVote(h_p, round_p, nil) 149 broadcast ⟨PRECOMMIT, h_p, round_p, nil, precommit_extension⟩ 150 step_p ← precommit 151 } 152 ``` 153 154 ### Upon receiving a precommit 155 156 Upon receiving a precommit `precommit`, we ensure that `ABCI.VerifyVoteExtension(precommit.precommit_extension) = true` 157 before accepting the precommit. This is akin to how we check the signature on precommits normally, hence its not wrapped 158 in the syntax of methods from the paper. 159 160 ### Precommit timeout 161 162 Upon receiving 2f + 1 precommits, setup a timeout. 163 164 ```go 165 upon 2f + 1 ⟨PRECOMMIT, h_p, vr, *⟩ for the first time, do { 166 schedule OnTimeoutPrecommit(h_p, round_p) to be executed after timeoutPrecommit(round_p) 167 } 168 ``` 169 170 with OnTimeoutPrecommit defined as: 171 172 ```go 173 def OnTimeoutPrecommit(height, round) { 174 if (height = h_p && round = round_p) { 175 StartRound(round_p + 1) 176 } 177 } 178 ``` 179 180 ### Upon Receiving 2f + 1 precommits 181 182 The following code is ran upon receiving 2f + 1 precommits for the same block 183 184 ```go 185 upon ⟨PROPOSAL, h_p, r, v, *⟩ 186 from proposer(h_p, r) 187 AND 2f + 1 ⟨ PRECOMMIT, h_p, r, id(v)⟩ 188 while decision_p[h_p] = nil do { 189 if (valid(v)) { 190 decision_p[h_p] ← v 191 h_p ← h_p + 1 192 reset lockedRound_p, lockedValue_p,validRound_p and validValue_p to initial values 193 ABCI.FinalizeBlock(id(v)) 194 StartRound(0) 195 } 196 } 197 ``` 198 199 If we don't see 2f + 1 precommits for the same block, we wait until we get 2f + 1 precommits, and the timeout occurs.