Browse Source

Add three timeouts and align pseudocode better with existing algorithm

pull/7804/head
Milosevic, Zarko 7 years ago
committed by Zarko Milosevic
parent
commit
4a81d0a02f
4 changed files with 94 additions and 40 deletions
  1. +2
    -0
      .gitignore
  2. +6
    -0
      .idea/vcs.xml
  3. +86
    -40
      consensus.tex
  4. BIN
      paper.dvi

+ 2
- 0
.gitignore View File

@ -4,3 +4,5 @@
*.log
*.pdf
*.gz
*.dvi
.idea

+ 6
- 0
.idea/vcs.xml View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

+ 86
- 40
consensus.tex View File

@ -1,8 +1,8 @@
\section{Tendermint}
\label{sec:consensus}
\label{sec:tendermint}
\newcommand\Propose{\mathsf{PROPOSE}}
\newcommand\Propose{\mathsf{PROPOSAL}}
\newcommand\PrePrepare{\mathsf{INIT}}
\newcommand\Prepare{\mathsf{PREVOTE}}
\newcommand\Commit{\mathsf{PRECOMMIT}}
@ -13,6 +13,13 @@
\newcommand\NewPrePrepare{\mathsf{VC\mbox{-}INIT}}
\newcommand\coord{\mathsf{proposer}}
\newcommand\initState{init}
\newcommand\preVoted{pre\mbox{-}voted}
\newcommand\preCommitted{pre\mbox{-}committed}
\newcommand\timeoutPropose{timeoutPropose}
\newcommand\timeoutPrevote{timeoutPrevote}
\newcommand\timeoutCommit{timeoutPreCommit}
\begin{algorithm}[htb!]
\def\baselinestretch{1}
@ -22,11 +29,12 @@
\INIT{}
\STATE $v_p := v \in V$ \COMMENT{value considered for consensus}
\STATE $round_p := 0$ \COMMENT{current round number}
\STATE $vote_p := null$ \COMMENT{locked value}
\STATE $vote_p := nill$ \COMMENT{locked value}
\STATE $ts_p := -1$ \COMMENT{ if $ts_p \ge 0$, it represents round in which $vote_p$ has been locked}
\STATE $proof_p := \emptyset$ \COMMENT{set of signed messages as a proof that $vote_p$ has been locked in round $ts_p$}
\STATE $state_p \in \set{init=0,prepared=1,comitted=2}$, initially $init$
\STATE $decision_p = null$
\STATE $state_p \in \set{\initState=0,\preVoted=1,\preCommitted=2}$, initially $\initState$
\STATE $decision_p = nill$
\ENDINIT
\SPACE
@ -35,59 +43,95 @@
\ENDUPON
\SPACE
\UPON{receiving $\li{\Propose,round_p, vote, ts, proof}$ \From\ $\coord(round_p)$ \With\ $state_p = init$} \label{line:tab:recvInit}
\FUNCTION{$StartRound(round)$} \label{line:tab:startRound}
\STATE $round_p \assign round$
\STATE $state_p \assign init$
\IF{$\coord(round_p) = p$}
\IF{$ts_p \ge 0$}
\STATE $proposal \assign vote_p$
\ELSE
\STATE $proposal \assign v_p$
\ENDIF
\STATE \PBroadcast\ $\li{\Propose,round_p,proposal,ts_p, proof_p}$ to all \label{line:tab:send-propose}
\ENDIF
\STATE \textbf{after} $\timeoutPropose$ execute $OnTimeoutPropose(round_p)$
\ENDFUNCTION
\SPACE
\UPON{receiving $\li{\Propose,round_p, vote, ts, proof}$ \From\ $\coord(round_p)$ \With\ $state_p = \initState$} \label{line:tab:recvInit}
\IF{$isValidProposal(vote,ts,proof)$}
\STATE \PBroadcast \ $\li{\Prepare,round_p,vote}$ to all \label{line:tab:send-echo}
\STATE $state_p \assign prepared$ \label{line:tab:setStateToEchoed}
\STATE $state_p \assign \preVoted$ \label{line:tab:setStateToEchoed}
\IF{$vote \neq vote_p \wedge ts > ts_p$}
\STATE $vote_p \assign null$
\STATE $vote_p \assign nill$
\STATE $ts_p \assign -1$
\ENDIF
\ENDIF
\ENDUPON
\SPACE
\UPON{receiving $\li{\Prepare,round_p,vote}$ \From\ $2f+1$ processes \With\
$state_p \le prepared$} \label{line:tab:recvEcho}
\FUNCTION{$OnTimeoutPropose(round)$} \label{line:tab:onTimeoutPropose}
\IF{$round = round_p \wedge state_p = \initState$}
\STATE \PBroadcast \ $\li{\Prepare,round_p,nill}$ to all
\STATE $state_p \assign \preVoted$
\ENDIF
\ENDFUNCTION
\SPACE
\UPON{receiving $\li{\Prepare,round,vote}$ \From\ $2f+1$ processes \With\
($round = round_p \wedge state_p \le \preVoted$) or $round > round_p$} \label{line:tab:recvEcho}
\STATE $vote_p \assign vote$ \label{line:tab:setV}
\STATE $ts_p \assign round_p$ \label{line:tab:setTs}
\IF{$vote = nill$}
\STATE $ts_p \assign -1$
\ELSE
\STATE $ts_p \assign round$ \label{line:tab:setTs}
\ENDIF
\STATE $round_p \assign round$
\STATE \PBroadcast \ $\li{\Commit,round_p,vote_p}$ to all \label{line:tab:send-commit}
\STATE $state_p \assign committed$ \label{line:tab:setStateToCommitted}
\STATE $state_p \assign \preCommitted$ \label{line:tab:setStateToCommitted}
\STATE $proof_p \assign$ \{set of received signed messages\}
\ENDUPON
\SPACE
\UPON{receiving ($\li{\Commit,round,vote}$ \From\ $2f+1$ processes \With\ $decision_p \neq null$} \label{line:tab:recvCommit}
\STATE $decision_p \assign vote$ \label{line:tab:setStateToDecided}
\UPON{receiving $\li{\Prepare,round_p,*}$ \From\ $2f+1$ processes \With\
$state_p = \preVoted$ \COMMENT{a sign * denotes any value}}
\STATE \textbf{after} $\timeoutPrevote$ execute $OnTimeoutPrevote(round_p)$
\ENDUPON
\SPACE
\UPON{receiving message \textbf{with} $round > round_p$ \From\ $f+1$ processes} \label{line:tab:skipViews}
\STATE $StartRound(round)$
\ENDUPON
\FUNCTION{$OnTimeoutPrevote(round)$} \label{line:tab:onTimeoutPrevote}
\IF{$round = round_p \wedge state_p = \preVoted$}
\STATE \PBroadcast \ $\li{\Commit,round_p,nill}$ to all
\STATE $state_p \assign \preCommitted$
\ENDIF
\ENDFUNCTION
\SPACE
\UPON{receiving ($\li{\Commit,round,vote}$ \From\ $2f+1$ processes \With\ $round \ge round_p$} \label{line:tab:recvCommit}
\IF{$vote \neq nill \wedge decision_p = nill$}
\STATE $decision_p \assign vote$ \label{line:tab:setStateToDecided}
\ENDIF
\STATE $StartRound(round+1)$
\ENDUPON
\SPACE
\UPON{receiving $\li{\Commit,round_p,*}$ \From\ $2f+1$ processes \With\
$state_p = \preCommitted$ \COMMENT{a sign * denotes any value}}
\STATE \textbf{after} $\timeoutPreCommit$ execute $OnTimeoutPreCommit(round_p)$
\ENDUPON
\SPACE
\FUNCTION{$StartRound(round)$} \label{line:tab:startRound}
\STATE $round_p \assign round$
\STATE $state_p \assign init$
\IF{$\coord(round_p) = p$}
\IF{$ts_p \ge 0$}
\STATE $proposal \assign vote_p$
\ELSE
\STATE $proposal \assign v_p$
\ENDIF
\STATE \PBroadcast\ $\li{\Propose,round_p,proposal,ts_p, proof_p}$ to all \label{line:tab:send-propose}
\ENDIF
\STATE \textbf{after} $timeout$ execute $OnTimeout(round_p)$
\FUNCTION{$OnTimeoutPreCommit(round)$} \label{line:tab:onTimeoutCommit}
\IF{$round = round_p \wedge state_p = \preCommitted$}
\STATE $StartRound(round+1)$
\ENDIF
\ENDFUNCTION
\SPACE
\FUNCTION{$OnTimeout(round)$} \label{line:tab:onTimeoutStart}
\IF{$round = round_p$}
\STATE $StartRound(round+1)$ \label{line:tab:onTimeout}
\ENDIF
\ENDFUNCTION
\UPON{receiving message \textbf{with} $round > round_p$ \From\ $f+1$ processes} \label{line:tab:skipViews}
\STATE $StartRound(round)$
\ENDUPON
\SPACE
\FUNCTION{$isValidProposal(vote, ts, proof)$} \label{line:tab:isValidProposal}
@ -106,6 +150,8 @@ $state_p \le prepared$} \label{line:tab:recvEcho}
\end{algorithm}
\textbf{NOTE: The algorithm description and the corresponding proofs refers to the previous version of the pseudocode and needs to be updated!}
In this section we present a new Byzantine consensus algorithm called Tendermint\footnote{https://github.com/tendermint/tendermint}
(see Algorithm~\ref{alg:tendermint}).
The algorithm requires $n > 3f$ processes to tolerate at most f Byzantine faults, but for simplicity we present the algorithm for the case $n = 3f + 1$. The upon rules of Algorithm~\ref{alg:tendermint} are executed atomically.
@ -118,22 +164,22 @@ The locking mechanism has a role in ensuring agreement property of consensus, wh
Each round is composed of three phases (steps) called propose, prepare and commit. A round starts with a propose phase (lines 28-37) in which a dedicated proposer sends its proposal to all processes. If a proposer $p$ has locked some value $v$, then it proposes $v$; otherwise it proposes it's initial value $v_p$ (lines 32-35). In addition to proposed value, $\Propose$ message also contains $ts$ and a proof of locking a value (in case proposal is equal to locked value), that consist of a set of signed messages received by the process $p$ in the round in which the value was locked.
When a correct process receives a proposal message from a designated proposer (lines 11-17), it checks if the message is a valid proposal (lines 41-46). A correct process $p$ considers proposal as valid if (i) proposal value is equal to process locked value ($vote_p$) or if $p$ hasn't locked any value ($ts_p = -1$), or (ii) if proposal is different value but from a higher round and with a valid proof of locking. If a process accepts proposal as valid, it enters prepare phase by sending proposed value in $PREVOTE$ message to all processes. In case (ii), as there is a valid locked value in a higher round, the process unlocks it's value.
When a correct process receives a proposal message from a designated proposer (lines 11-17), it checks if the message is a valid proposal (lines 41-46). A correct process $p$ considers proposal as valid if (i) proposal value is equal to process locked value ($vote_p$) or if $p$ hasn't locked any value ($ts_p = -1$), or (ii) if proposal is different value but from a higher round and with a valid proof of locking. If a process accepts proposal as valid, it enters prepare phase by sending proposed value in $\Prepare$ message to all processes. In case (ii), as there is a valid locked value in a higher round, the process unlocks it's value.
When a correct process $p$ receives $2f+1$ $PREVOTE$ messages for the same value $v$, then the process locks a value $v$ (and saves a proof of locking into variable $proof_p$) and enters commit step by sending $v$ in $PRECOMMIT$ message (lines 18-23). When process received $2f+1$ $PRECOMMIT$ messages for some value $v$, it decides value $v$.
When a correct process $p$ receives $2f+1$ $\Prepare$ messages for the same value $v$, then the process locks a value $v$ (and saves a proof of locking into variable $proof_p$) and enters commit step by sending $v$ in $\Commit$ message (lines 18-23). When process received $2f+1$ $\Commit$ messages for some value $v$, it decides value $v$.
Note that algorithm relies on $state$ variable to prevent correct processes from sending more than one $PREVOTE$ and $PRECOMMIT$ messages in a single round $r$. For simplicity, the current version of the algorithm does not provide termination condition, i.e., processes continue round execution even after deciding. We also assume that round timeout is being incremented in each round so that eventually all correct processes are able to receive all messages from correct processes in a round, before the round timeout expires.
Note that algorithm relies on $state$ variable to prevent correct processes from sending more than one $\Prepare$ and $\Commit$ messages in a single round $r$. For simplicity, the current version of the algorithm does not provide termination condition, i.e., processes continue round execution even after deciding. We also assume that round timeout is being incremented in each round so that eventually all correct processes are able to receive all messages from correct processes in a round, before the round timeout expires.
Furthermore, the algorithm solves only single instance consensus problem. The multi-instance version of Tendermint will be addressed in the next version of the document. However, it should be relatively straightforward to modify Algorithm~\ref{alg:tendermint} to get multi-instance version with the following modifications: (i) adding consensus instance number in each message, (ii) having per instance algorithm state variables and (iii) adding logic for starting next consensus instance.
\subsection{Proof of correctness (sketch)}
\textbf{Agreement} Let assume that a first correct process that decides is $p$ and that $p$ decides value $v$ in round $r$. As $p$ decided $v$ this means that $p$ receives at least $2f+1$ $PRECOMMIT$ messages with value $v$ in the round $r$ (line 24). This means that at least $f+1$ correct processes have locked value $v$ in round $r$ and have it's variable $vote$ set to $v$ and $ts$ variable set to $r$. As correct processes can send only single $PRECOMMIT$ message per round, and there are at most $f$ faulty processes, then no correct process can receive $2f+1$ $PRECOMMIT$ messages in the round $r$ for some other value $v' \neq v$. Therefore, no correct process can decide value different than $v$ in the round $r$.
\textbf{Agreement} Let assume that a first correct process that decides is $p$ and that $p$ decides value $v$ in round $r$. As $p$ decided $v$ this means that $p$ receives at least $2f+1$ $\Commit$ messages with value $v$ in the round $r$ (line 24). This means that at least $f+1$ correct processes have locked value $v$ in round $r$ and have it's variable $vote$ set to $v$ and $ts$ variable set to $r$. As correct processes can send only single $\Commit$ message per round, and there are at most $f$ faulty processes, then no correct process can receive $2f+1$ $\Commit$ messages in the round $r$ for some other value $v' \neq v$. Therefore, no correct process can decide value different than $v$ in the round $r$.
Now let consider the smallest round $r' > r$ in which correct process $q$ locks some value $v'$. As $q$ locks value $v'$ this means that $q$ has received at least $2f+1$ $PREVOTE$ messages equal to $v'$ in the round $r'$. Therefore, at least $f+1$ correct processes has sent $v'$ in $PREVOTE$ message in the round $r'$. As $f+1$ correct process $q'$ locked value $v$ in the round $r$, this means that one of those processes sent $PROPOSE$ message with value $v'$ in the round $r'$. According to function $isValidProposal$, $q'$ received valid proof of locking for value $v'$. This is in contradiction with the assumption that $r'$ is the smallest round in which some correct process locks some value (TODO: improve this part, it is not very precise as it could be reasoned that a Byzantine process could create correct proof of locking for some value $v'$ and round greater than $r'$ which can be proved impossible with the similar reasoning). As no correct process can lock any value different from $v$ in rounds $r' > r$, then no correct process can decide value different from $v$.
Now let consider the smallest round $r' > r$ in which correct process $q$ locks some value $v'$. As $q$ locks value $v'$ this means that $q$ has received at least $2f+1$ $\Prepare$ messages equal to $v'$ in the round $r'$. Therefore, at least $f+1$ correct processes has sent $v'$ in $\Prepare$ message in the round $r'$. As $f+1$ correct process $q'$ locked value $v$ in the round $r$, this means that one of those processes sent $PROPOSE$ message with value $v'$ in the round $r'$. According to function $isValidProposal$, $q'$ received valid proof of locking for value $v'$. This is in contradiction with the assumption that $r'$ is the smallest round in which some correct process locks some value (TODO: improve this part, it is not very precise as it could be reasoned that a Byzantine process could create correct proof of locking for some value $v'$ and round greater than $r'$ which can be proved impossible with the similar reasoning). As no correct process can lock any value different from $v$ in rounds $r' > r$, then no correct process can decide value different from $v$.
\textbf{Termination}
The Algorithm~\ref{alg:tendermint} does not terminate as the following scenario is possible: we consider rounds after GST, starting from round $r$ and at round $r$ a correct process $p$ is the only process that locks some value $v$ among the correct processes ($0 \le ts_p \le r$). Assume that no correct process has decided. Let assume that $p$ would coordinate the round $r'>r$. In rounds smaller than $r'$ $p$ will reject any proposal from correct processes if it's not equal to $v$ (which is possible if all correct processes have different initial values), so it will not send $PREPARE$ message. As faulty processes could decide not to send any message, no correct processes will be able to lock a value and to decide in those rounds. Now let assume that in round $r'-1$ some correct process $q$ locks value $v' \neq v$ because a faulty process (together with all correct processes except $p$) sends $PREVOTE$ message with $v'$ to $q$. As $q$ locks value $v'$ in round higher than $r$, $q$ will reject proposal from $p$ in round $r'$ so if faulty processes stay silent, again no correct processes can decide in that round. This scenario can be repeated also with the rounds $k-1$ and $k$, in which the process $q$ is proposer.
The Algorithm~\ref{alg:tendermint} does not terminate as the following scenario is possible: we consider rounds after GST, starting from round $r$ and at round $r$ a correct process $p$ is the only process that locks some value $v$ among the correct processes ($0 \le ts_p \le r$). Assume that no correct process has decided. Let assume that $p$ would coordinate the round $r'>r$. In rounds smaller than $r'$ $p$ will reject any proposal from correct processes if it's not equal to $v$ (which is possible if all correct processes have different initial values), so it will not send $PREPARE$ message. As faulty processes could decide not to send any message, no correct processes will be able to lock a value and to decide in those rounds. Now let assume that in round $r'-1$ some correct process $q$ locks value $v' \neq v$ because a faulty process (together with all correct processes except $p$) sends $\Prepare$ message with $v'$ to $q$. As $q$ locks value $v'$ in round higher than $r$, $q$ will reject proposal from $p$ in round $r'$ so if faulty processes stay silent, again no correct processes can decide in that round. This scenario can be repeated also with the rounds $k-1$ and $k$, in which the process $q$ is proposer.
The property that needs to be ensured is the following: after GST, there exist a round such that a proposal from a correct process is accepted by all correct processes. The solution could be adding one more step (lock-release step) at the end of each round where correct processes would send it's locked value (together with ts and proof) to all processes, and then a correct process will release lock upon reception of proof of locking for a different value from higher round. We will also need to modify lines 35 and 36 to propose the value with the valid proof of locking from the highest round (if process is aware of such value) or initial value.


BIN
paper.dvi View File


Loading…
Cancel
Save