|
|
- -------------------- MODULE TendermintPBT_002_draft ---------------------------
- (*
- A TLA+ specification of a simplified Tendermint consensus, with added clocks
- and proposer-based timestamps. This TLA+ specification extends and modifies
- the Tendermint TLA+ specification for fork accountability:
- https://github.com/tendermint/spec/blob/master/spec/light-client/accountability/TendermintAcc_004_draft.tla
-
- * Version 2. A preliminary specification.
-
- Zarko Milosevic, Igor Konnov, Informal Systems, 2019-2020.
- Ilina Stoilkovska, Josef Widder, Informal Systems, 2021.
- Jure Kukovec, Informal Systems, 2022.
- *)
-
- EXTENDS Integers, FiniteSets, Apalache, typedefs
-
- (********************* PROTOCOL PARAMETERS **********************************)
- \* General protocol parameters
- CONSTANTS
- \* @type: Set(PROCESS);
- Corr, \* the set of correct processes
- \* @type: Set(PROCESS);
- Faulty, \* the set of Byzantine processes, may be empty
- \* @type: Int;
- N, \* the total number of processes: correct, defective, and Byzantine
- \* @type: Int;
- T, \* an upper bound on the number of Byzantine processes
- \* @type: Set(VALUE);
- ValidValues, \* the set of valid values, proposed both by correct and faulty
- \* @type: Set(VALUE);
- InvalidValues, \* the set of invalid values, never proposed by the correct ones
- \* @type: ROUND;
- MaxRound, \* the maximal round number
- \* @type: ROUND -> PROCESS;
- Proposer \* the proposer function from Rounds to AllProcs
-
- \* Time-related parameters
- CONSTANTS
- \* @type: TIME;
- MaxTimestamp, \* the maximal value of the clock tick
- \* @type: TIME;
- MinTimestamp, \* the minimal value of the clock tick
- \* @type: TIME;
- Delay, \* message delay
- \* @type: TIME;
- Precision \* clock precision: the maximal difference between two local clocks
-
- ASSUME(N = Cardinality(Corr \union Faulty))
-
- (*************************** DEFINITIONS ************************************)
- \* @type: Set(PROCESS);
- AllProcs == Corr \union Faulty \* the set of all processes
- \* @type: Set(ROUND);
- Rounds == 0..MaxRound \* the set of potential rounds
- \* @type: Set(TIME);
- Timestamps == 0..MaxTimestamp \* the set of clock ticks
- \* @type: ROUND;
- NilRound == -1 \* a special value to denote a nil round, outside of Rounds
- \* @type: TIME;
- NilTimestamp == -1 \* a special value to denote a nil timestamp, outside of Ticks
- \* @type: Set(ROUND);
- RoundsOrNil == Rounds \union {NilRound}
- \* @type: Set(VALUE);
- Values == ValidValues \union InvalidValues \* the set of all values
- \* @type: VALUE;
- NilValue == "None" \* a special value for a nil round, outside of Values
- \* @type: Set(PROPOSAL);
- Proposals == Values \X Timestamps \X Rounds
- \* @type: PROPOSAL;
- NilProposal == <<NilValue, NilTimestamp, NilRound>>
- \* @type: Set(VALUE);
- ValuesOrNil == Values \union {NilValue}
- \* @type: Set(DECISION);
- Decisions == Proposals \X Rounds
- \* @type: DECISION;
- NilDecision == <<NilProposal, NilRound>>
-
- ValidProposals == ValidValues \X (MinTimestamp..MaxTimestamp) \X Rounds
- \* a value hash is modeled as identity
- \* @type: (t) => t;
- Id(v) == v
-
- \* The validity predicate
- \* @type: (PROPOSAL) => Bool;
- IsValid(p) == p \in ValidProposals
-
- \* Time validity check. If we want MaxTimestamp = \infty, set ValidTime(t) == TRUE
- ValidTime(t) == t < MaxTimestamp
-
- \* @type: (PROPMESSAGE) => VALUE;
- MessageValue(msg) == msg.proposal[1]
- \* @type: (PROPMESSAGE) => TIME;
- MessageTime(msg) == msg.proposal[2]
- \* @type: (PROPMESSAGE) => ROUND;
- MessageRound(msg) == msg.proposal[3]
-
- \* @type: (TIME, TIME) => Bool;
- IsTimely(processTime, messageTime) ==
- /\ processTime >= messageTime - Precision
- /\ processTime <= messageTime + Precision + Delay
-
- \* the two thresholds that are used in the algorithm
- \* @type: Int;
- THRESHOLD1 == T + 1 \* at least one process is not faulty
- \* @type: Int;
- THRESHOLD2 == 2 * T + 1 \* a quorum when having N > 3 * T
-
- \* @type: (TIME, TIME) => TIME;
- Min2(a,b) == IF a <= b THEN a ELSE b
- \* @type: (Set(TIME)) => TIME;
- Min(S) == FoldSet( Min2, MaxTimestamp, S )
- \* Min(S) == CHOOSE x \in S : \A y \in S : x <= y
-
- \* @type: (TIME, TIME) => TIME;
- Max2(a,b) == IF a >= b THEN a ELSE b
- \* @type: (Set(TIME)) => TIME;
- Max(S) == FoldSet( Max2, NilTimestamp, S )
- \* Max(S) == CHOOSE x \in S : \A y \in S : y <= x
-
- \* @type: (Set(MESSAGE)) => Int;
- Card(S) ==
- LET
- \* @type: (Int, MESSAGE) => Int;
- PlusOne(i, m) == i + 1
- IN FoldSet( PlusOne, 0, S )
-
- (********************* PROTOCOL STATE VARIABLES ******************************)
- VARIABLES
- \* @type: PROCESS -> ROUND;
- round, \* a process round number
- \* @type: PROCESS -> STEP;
- step, \* a process step
- \* @type: PROCESS -> DECISION;
- decision, \* process decision
- \* @type: PROCESS -> VALUE;
- lockedValue, \* a locked value
- \* @type: PROCESS -> ROUND;
- lockedRound, \* a locked round
- \* @type: PROCESS -> PROPOSAL;
- validValue, \* a valid value
- \* @type: PROCESS -> ROUND;
- validRound \* a valid round
-
- coreVars ==
- <<round, step, decision, lockedValue,
- lockedRound, validValue, validRound>>
-
- \* time-related variables
- VARIABLES
- \* @type: PROCESS -> TIME;
- localClock, \* a process local clock: Corr -> Ticks
- \* @type: TIME;
- realTime \* a reference Newtonian real time
-
- temporalVars == <<localClock, realTime>>
-
- \* book-keeping variables
- VARIABLES
- \* @type: ROUND -> Set(PROPMESSAGE);
- msgsPropose, \* PROPOSE messages broadcast in the system, Rounds -> Messages
- \* @type: ROUND -> Set(PREMESSAGE);
- msgsPrevote, \* PREVOTE messages broadcast in the system, Rounds -> Messages
- \* @type: ROUND -> Set(PREMESSAGE);
- msgsPrecommit, \* PRECOMMIT messages broadcast in the system, Rounds -> Messages
- \* @type: Set(MESSAGE);
- evidence, \* the messages that were used by the correct processes to make transitions
- \* @type: ACTION;
- action, \* we use this variable to see which action was taken
- \* @type: PROCESS -> Set(PROPMESSAGE);
- receivedTimelyProposal, \* used to keep track when a process receives a timely PROPOSAL message
- \* @type: <<ROUND,PROCESS>> -> TIME;
- inspectedProposal \* used to keep track when a process tries to receive a message
-
- \* Action is excluded from the tuple, because it always changes
- bookkeepingVars ==
- <<msgsPropose, msgsPrevote, msgsPrecommit,
- evidence, (*action,*) receivedTimelyProposal,
- inspectedProposal>>
-
- \* Invariant support
- VARIABLES
- \* @type: ROUND -> TIME;
- beginRound, \* the minimum of the local clocks at the time any process entered a new round
- \* @type: PROCESS -> TIME;
- endConsensus, \* the local time when a decision is made
- \* @type: ROUND -> TIME;
- lastBeginRound, \* the maximum of the local clocks in each round
- \* @type: ROUND -> TIME;
- proposalTime, \* the real time when a proposer proposes in a round
- \* @type: ROUND -> TIME;
- proposalReceivedTime \* the real time when a correct process first receives a proposal message in a round
-
- invariantVars ==
- <<beginRound, endConsensus, lastBeginRound,
- proposalTime, proposalReceivedTime>>
-
- (* to see a type invariant, check TendermintAccInv3 *)
-
- (********************* PROTOCOL INITIALIZATION ******************************)
- \* @type: (ROUND) => Set(PROPMESSAGE);
- FaultyProposals(r) ==
- [
- type : {"PROPOSAL"},
- src : Faulty,
- round : {r},
- proposal : Proposals,
- validRound: RoundsOrNil
- ]
-
- \* @type: Set(PROPMESSAGE);
- AllFaultyProposals ==
- [
- type : {"PROPOSAL"},
- src : Faulty,
- round : Rounds,
- proposal : Proposals,
- validRound: RoundsOrNil
- ]
-
- \* @type: (ROUND) => Set(PREMESSAGE);
- FaultyPrevotes(r) ==
- [
- type : {"PREVOTE"},
- src : Faulty,
- round: {r},
- id : Proposals
- ]
-
- \* @type: Set(PREMESSAGE);
- AllFaultyPrevotes ==
- [
- type : {"PREVOTE"},
- src : Faulty,
- round: Rounds,
- id : Proposals
- ]
-
- \* @type: (ROUND) => Set(PREMESSAGE);
- FaultyPrecommits(r) ==
- [
- type : {"PRECOMMIT"},
- src : Faulty,
- round: {r},
- id : Proposals
- ]
-
- \* @type: Set(PREMESSAGE);
- AllFaultyPrecommits ==
- [
- type : {"PRECOMMIT"},
- src : Faulty,
- round: Rounds,
- id : Proposals
- ]
-
- \* @type: Set(PROPMESSAGE);
- AllProposals ==
- [
- type : {"PROPOSAL"},
- src : AllProcs,
- round : Rounds,
- proposal : Proposals,
- validRound: RoundsOrNil
- ]
-
- \* @type: (ROUND) => Set(PROPMESSAGE);
- RoundProposals(r) ==
- [
- type : {"PROPOSAL"},
- src : AllProcs,
- round : {r},
- proposal : Proposals,
- validRound: RoundsOrNil
- ]
-
- \* @type: (ROUND -> Set(MESSAGE)) => Bool;
- BenignRoundsInMessages(msgfun) ==
- \* the message function never contains a message for a wrong round
- \A r \in Rounds:
- \A m \in msgfun[r]:
- r = m.round
-
- \* The initial states of the protocol. Some faults can be in the system already.
- Init ==
- /\ round = [p \in Corr |-> 0]
- /\ localClock \in [Corr -> MinTimestamp..(MinTimestamp + Precision)]
- /\ realTime = 0
- /\ step = [p \in Corr |-> "PROPOSE"]
- /\ decision = [p \in Corr |-> NilDecision]
- /\ lockedValue = [p \in Corr |-> NilValue]
- /\ lockedRound = [p \in Corr |-> NilRound]
- /\ validValue = [p \in Corr |-> NilProposal]
- /\ validRound = [p \in Corr |-> NilRound]
- /\ msgsPropose \in [Rounds -> SUBSET AllFaultyProposals]
- /\ msgsPrevote \in [Rounds -> SUBSET AllFaultyPrevotes]
- /\ msgsPrecommit \in [Rounds -> SUBSET AllFaultyPrecommits]
- /\ receivedTimelyProposal = [p \in Corr |-> {}]
- /\ inspectedProposal = [r \in Rounds, p \in Corr |-> NilTimestamp]
- /\ BenignRoundsInMessages(msgsPropose)
- /\ BenignRoundsInMessages(msgsPrevote)
- /\ BenignRoundsInMessages(msgsPrecommit)
- /\ evidence = {}
- /\ action' = "Init"
- /\ beginRound =
- [r \in Rounds |->
- IF r = 0
- THEN Min({localClock[p] : p \in Corr})
- ELSE MaxTimestamp
- ]
- /\ endConsensus = [p \in Corr |-> NilTimestamp]
- /\ lastBeginRound =
- [r \in Rounds |->
- IF r = 0
- THEN Max({localClock[p] : p \in Corr})
- ELSE NilTimestamp
- ]
- /\ proposalTime = [r \in Rounds |-> NilTimestamp]
- /\ proposalReceivedTime = [r \in Rounds |-> NilTimestamp]
-
- (************************ MESSAGE PASSING ********************************)
- \* @type: (PROCESS, ROUND, PROPOSAL, ROUND) => Bool;
- BroadcastProposal(pSrc, pRound, pProposal, pValidRound) ==
- LET
- \* @type: PROPMESSAGE;
- newMsg ==
- [
- type |-> "PROPOSAL",
- src |-> pSrc,
- round |-> pRound,
- proposal |-> pProposal,
- validRound |-> pValidRound
- ]
- IN
- /\ msgsPropose' = [msgsPropose EXCEPT ![pRound] = msgsPropose[pRound] \union {newMsg}]
-
- \* @type: (PROCESS, ROUND, PROPOSAL) => Bool;
- BroadcastPrevote(pSrc, pRound, pId) ==
- LET
- \* @type: PREMESSAGE;
- newMsg ==
- [
- type |-> "PREVOTE",
- src |-> pSrc,
- round |-> pRound,
- id |-> pId
- ]
- IN
- /\ msgsPrevote' = [msgsPrevote EXCEPT ![pRound] = msgsPrevote[pRound] \union {newMsg}]
-
- \* @type: (PROCESS, ROUND, PROPOSAL) => Bool;
- BroadcastPrecommit(pSrc, pRound, pId) ==
- LET
- \* @type: PREMESSAGE;
- newMsg ==
- [
- type |-> "PRECOMMIT",
- src |-> pSrc,
- round |-> pRound,
- id |-> pId
- ]
- IN
- /\ msgsPrecommit' = [msgsPrecommit EXCEPT ![pRound] = msgsPrecommit[pRound] \union {newMsg}]
-
- (***************************** TIME **************************************)
-
- \* [PBTS-CLOCK-PRECISION.0]
- \* @type: Bool;
- SynchronizedLocalClocks ==
- \A p \in Corr : \A q \in Corr :
- p /= q =>
- \/ /\ localClock[p] >= localClock[q]
- /\ localClock[p] - localClock[q] < Precision
- \/ /\ localClock[p] < localClock[q]
- /\ localClock[q] - localClock[p] < Precision
-
- \* [PBTS-PROPOSE.0]
- \* @type: (VALUE, TIME, ROUND) => PROPOSAL;
- Proposal(v, t, r) ==
- <<v, t, r>>
-
- \* [PBTS-DECISION-ROUND.0]
- \* @type: (PROPOSAL, ROUND) => DECISION;
- Decision(p, r) ==
- <<p, r>>
-
- (**************** MESSAGE PROCESSING TRANSITIONS *************************)
- \* lines 12-13
- \* @type: (PROCESS, ROUND) => Bool;
- StartRound(p, r) ==
- /\ step[p] /= "DECIDED" \* a decided process does not participate in consensus
- /\ round' = [round EXCEPT ![p] = r]
- /\ step' = [step EXCEPT ![p] = "PROPOSE"]
- \* We only need to update (last)beginRound[r] once a process enters round `r`
- /\ beginRound' = [beginRound EXCEPT ![r] = Min2(@, localClock[p])]
- /\ lastBeginRound' = [lastBeginRound EXCEPT ![r] = Max2(@, localClock[p])]
-
- \* lines 14-19, a proposal may be sent later
- \* @type: (PROCESS) => Bool;
- InsertProposal(p) ==
- LET r == round[p] IN
- /\ p = Proposer[r]
- /\ step[p] = "PROPOSE"
- \* if the proposer is sending a proposal, then there are no other proposals
- \* by the correct processes for the same round
- /\ \A m \in msgsPropose[r]: m.src /= p
- \* /\ localClock[p] >
- /\ \E v \in ValidValues:
- LET proposal ==
- IF validValue[p] /= NilProposal
- THEN validValue[p]
- ELSE Proposal(v, localClock[p], r)
- IN
- /\ BroadcastProposal(p, r, proposal, validRound[p])
- /\ proposalTime' = [proposalTime EXCEPT ![r] = realTime]
- /\ UNCHANGED <<temporalVars, coreVars>>
- /\ UNCHANGED
- <<(*msgsPropose,*) msgsPrevote, msgsPrecommit,
- evidence, receivedTimelyProposal, inspectedProposal>>
- /\ UNCHANGED
- <<beginRound, endConsensus, lastBeginRound,
- (*proposalTime,*) proposalReceivedTime>>
- /\ action' = "InsertProposal"
-
- \* a new action used to filter messages that are not on time
- \* [PBTS-RECEPTION-STEP.0]
- \* @type: (PROCESS) => Bool;
- ReceiveProposal(p) ==
- \E v \in Values, t \in Timestamps:
- /\ LET r == round[p] IN
- LET
- \* @type: PROPMESSAGE;
- msg ==
- [
- type |-> "PROPOSAL",
- src |-> Proposer[round[p]],
- round |-> round[p],
- proposal |-> Proposal(v, t, r),
- validRound |-> NilRound
- ]
- IN
- /\ msg \in msgsPropose[round[p]]
- /\ inspectedProposal[r,p] = NilTimestamp
- /\ msg \notin receivedTimelyProposal[p]
- /\ inspectedProposal' = [inspectedProposal EXCEPT ![r,p] = localClock[p]]
- /\ LET
- isTimely == IsTimely(localClock[p], t)
- IN
- \/ /\ isTimely
- /\ receivedTimelyProposal' = [receivedTimelyProposal EXCEPT ![p] = @ \union {msg}]
- /\ LET
- isNilTimestamp == proposalReceivedTime[r] = NilTimestamp
- IN
- \/ /\ isNilTimestamp
- /\ proposalReceivedTime' = [proposalReceivedTime EXCEPT ![r] = realTime]
- \/ /\ ~isNilTimestamp
- /\ UNCHANGED proposalReceivedTime
- \/ /\ ~isTimely
- /\ UNCHANGED <<receivedTimelyProposal, proposalReceivedTime>>
- /\ UNCHANGED <<temporalVars, coreVars>>
- /\ UNCHANGED
- <<msgsPropose, msgsPrevote, msgsPrecommit,
- evidence(*, receivedTimelyProposal, inspectedProposal*)>>
- /\ UNCHANGED
- <<beginRound, endConsensus, lastBeginRound,
- proposalTime(*, proposalReceivedTime*)>>
- /\ action' = "ReceiveProposal"
-
- \* lines 22-27
- \* @type: (PROCESS) => Bool;
- UponProposalInPropose(p) ==
- \E v \in Values, t \in Timestamps:
- LET
- r == round[p]
- IN LET
- \* @type: PROPOSAL;
- prop == Proposal(v,t,r)
- IN
- /\ step[p] = "PROPOSE" (* line 22 *)
- /\ LET
- \* @type: PROPMESSAGE;
- msg ==
- [
- type |-> "PROPOSAL",
- src |-> Proposer[r],
- round |-> r,
- proposal |-> prop,
- validRound |-> NilRound
- ]
- IN
- /\ msg \in receivedTimelyProposal[p] \* updated line 22
- /\ evidence' = {msg} \union evidence
- /\ LET mid == (* line 23 *)
- IF IsValid(prop) /\ (lockedRound[p] = NilRound \/ lockedValue[p] = v)
- THEN Id(prop)
- ELSE NilProposal
- IN
- BroadcastPrevote(p, r, mid) \* lines 24-26
- /\ step' = [step EXCEPT ![p] = "PREVOTE"]
- /\ UNCHANGED <<temporalVars, invariantVars>>
- /\ UNCHANGED
- <<round, (*step,*) decision, lockedValue,
- lockedRound, validValue, validRound>>
- /\ UNCHANGED
- <<msgsPropose, (*msgsPrevote,*) msgsPrecommit,
- (*evidence,*) receivedTimelyProposal, inspectedProposal>>
- /\ action' = "UponProposalInPropose"
-
- \* lines 28-33
- \* [PBTS-ALG-OLD-PREVOTE.0]
- \* @type: (PROCESS) => Bool;
- UponProposalInProposeAndPrevote(p) ==
- \E v \in Values, t \in Timestamps, vr \in Rounds, pr \in Rounds:
- LET
- r == round[p]
- IN LET
- \* @type: PROPOSAL;
- prop == Proposal(v,t,pr)
- IN
- /\ step[p] = "PROPOSE" /\ 0 <= vr /\ vr < r \* line 28, the while part
- /\ pr <= vr
- /\ LET
- \* @type: PROPMESSAGE;
- msg ==
- [
- type |-> "PROPOSAL",
- src |-> Proposer[r],
- round |-> r,
- proposal |-> prop,
- validRound |-> vr
- ]
- IN
- \* Changed from 001: no need to re-check timeliness
- /\ msg \in msgsPropose[r] \* line 28
- /\ LET PV == { m \in msgsPrevote[vr]: m.id = Id(prop) } IN
- /\ Cardinality(PV) >= THRESHOLD2 \* line 28
- /\ evidence' = PV \union {msg} \union evidence
- /\ LET mid == (* line 29 *)
- IF IsValid(prop) /\ (lockedRound[p] <= vr \/ lockedValue[p] = v)
- THEN Id(prop)
- ELSE NilProposal
- IN
- BroadcastPrevote(p, r, mid) \* lines 24-26
- /\ step' = [step EXCEPT ![p] = "PREVOTE"]
- /\ UNCHANGED <<temporalVars, invariantVars>>
- /\ UNCHANGED
- <<round, (*step,*) decision, lockedValue,
- lockedRound, validValue, validRound>>
- /\ UNCHANGED
- <<msgsPropose, (*msgsPrevote,*) msgsPrecommit,
- (*evidence,*) receivedTimelyProposal, inspectedProposal>>
- /\ action' = "UponProposalInProposeAndPrevote"
-
- \* lines 34-35 + lines 61-64 (onTimeoutPrevote)
- \* @type: (PROCESS) => Bool;
- UponQuorumOfPrevotesAny(p) ==
- /\ step[p] = "PREVOTE" \* line 34 and 61
- /\ \E MyEvidence \in SUBSET msgsPrevote[round[p]]:
- \* find the unique voters in the evidence
- LET Voters == { m.src: m \in MyEvidence } IN
- \* compare the number of the unique voters against the threshold
- /\ Cardinality(Voters) >= THRESHOLD2 \* line 34
- /\ evidence' = MyEvidence \union evidence
- /\ BroadcastPrecommit(p, round[p], NilProposal)
- /\ step' = [step EXCEPT ![p] = "PRECOMMIT"]
- /\ UNCHANGED <<temporalVars, invariantVars>>
- /\ UNCHANGED
- <<round, (*step,*) decision, lockedValue,
- lockedRound, validValue, validRound>>
- /\ UNCHANGED
- <<msgsPropose, msgsPrevote, (*msgsPrecommit, *)
- (*evidence,*) receivedTimelyProposal, inspectedProposal>>
- /\ action' = "UponQuorumOfPrevotesAny"
-
- \* lines 36-46
- \* [PBTS-ALG-NEW-PREVOTE.0]
- \* @type: (PROCESS) => Bool;
- UponProposalInPrevoteOrCommitAndPrevote(p) ==
- \E v \in ValidValues, t \in Timestamps, vr \in RoundsOrNil:
- LET
- r == round[p]
- IN LET
- \* @type: PROPOSAL;
- prop == Proposal(v,t,r)
- IN
- /\ step[p] \in {"PREVOTE", "PRECOMMIT"} \* line 36
- /\ LET
- \* @type: PROPMESSAGE;
- msg ==
- [
- type |-> "PROPOSAL",
- src |-> Proposer[r],
- round |-> r,
- proposal |-> prop,
- validRound |-> vr
- ]
- IN
- \* Changed from 001: no need to re-check timeliness
- /\ msg \in msgsPropose[r] \* line 36
- /\ LET PV == { m \in msgsPrevote[r]: m.id = Id(prop) } IN
- /\ Cardinality(PV) >= THRESHOLD2 \* line 36
- /\ evidence' = PV \union {msg} \union evidence
- /\ IF step[p] = "PREVOTE"
- THEN \* lines 38-41:
- /\ lockedValue' = [lockedValue EXCEPT ![p] = v]
- /\ lockedRound' = [lockedRound EXCEPT ![p] = r]
- /\ BroadcastPrecommit(p, r, Id(prop))
- /\ step' = [step EXCEPT ![p] = "PRECOMMIT"]
- ELSE
- UNCHANGED <<lockedValue, lockedRound, msgsPrecommit, step>>
- \* lines 42-43
- /\ validValue' = [validValue EXCEPT ![p] = prop]
- /\ validRound' = [validRound EXCEPT ![p] = r]
- /\ UNCHANGED <<temporalVars, invariantVars>>
- /\ UNCHANGED
- <<round, (*step,*) decision(*, lockedValue,
- lockedRound, validValue, validRound*)>>
- /\ UNCHANGED
- <<msgsPropose, msgsPrevote, (*msgsPrecommit, *)
- (*evidence,*) receivedTimelyProposal, inspectedProposal>>
- /\ action' = "UponProposalInPrevoteOrCommitAndPrevote"
-
- \* lines 47-48 + 65-67 (onTimeoutPrecommit)
- \* @type: (PROCESS) => Bool;
- UponQuorumOfPrecommitsAny(p) ==
- /\ \E MyEvidence \in SUBSET msgsPrecommit[round[p]]:
- \* find the unique committers in the evidence
- LET Committers == { m.src: m \in MyEvidence } IN
- \* compare the number of the unique committers against the threshold
- /\ Cardinality(Committers) >= THRESHOLD2 \* line 47
- /\ evidence' = MyEvidence \union evidence
- /\ round[p] + 1 \in Rounds
- /\ StartRound(p, round[p] + 1)
- /\ UNCHANGED temporalVars
- /\ UNCHANGED
- <<(*beginRound,*) endConsensus, (*lastBeginRound,*)
- proposalTime, proposalReceivedTime>>
- /\ UNCHANGED
- <<(*round, step,*) decision, lockedValue,
- lockedRound, validValue, validRound>>
- /\ UNCHANGED
- <<msgsPropose, msgsPrevote, msgsPrecommit,
- (*evidence,*) receivedTimelyProposal, inspectedProposal>>
- /\ action' = "UponQuorumOfPrecommitsAny"
-
- \* lines 49-54
- \* [PBTS-ALG-DECIDE.0]
- \* @type: (PROCESS) => Bool;
- UponProposalInPrecommitNoDecision(p) ==
- /\ decision[p] = NilDecision \* line 49
- /\ \E v \in ValidValues, t \in Timestamps (* line 50*) , r \in Rounds, pr \in Rounds, vr \in RoundsOrNil:
- LET
- \* @type: PROPOSAL;
- prop == Proposal(v,t,pr)
- IN
- /\ LET
- \* @type: PROPMESSAGE;
- msg ==
- [
- type |-> "PROPOSAL",
- src |-> Proposer[r],
- round |-> r,
- proposal |-> prop,
- validRound |-> vr
- ]
- IN
- /\ msg \in msgsPropose[r] \* line 49
- /\ inspectedProposal[r,p] /= NilTimestamp \* Keep?
- /\ LET PV == { m \in msgsPrecommit[r]: m.id = Id(prop) } IN
- /\ Cardinality(PV) >= THRESHOLD2 \* line 49
- /\ evidence' = PV \union {msg} \union evidence
- /\ decision' = [decision EXCEPT ![p] = Decision(prop, r)] \* update the decision, line 51
- \* The original algorithm does not have 'DECIDED', but it increments the height.
- \* We introduced 'DECIDED' here to prevent the process from changing its decision.
- /\ endConsensus' = [endConsensus EXCEPT ![p] = localClock[p]]
- /\ step' = [step EXCEPT ![p] = "DECIDED"]
- /\ UNCHANGED temporalVars
- /\ UNCHANGED
- <<round, (*step, decision,*) lockedValue,
- lockedRound, validValue, validRound>>
- /\ UNCHANGED
- <<msgsPropose, msgsPrevote, msgsPrecommit,
- (*evidence,*) receivedTimelyProposal, inspectedProposal>>
- /\ UNCHANGED
- <<beginRound, (*endConsensus,*) lastBeginRound,
- proposalTime, proposalReceivedTime>>
- /\ action' = "UponProposalInPrecommitNoDecision"
-
- \* the actions below are not essential for safety, but added for completeness
-
- \* lines 20-21 + 57-60
- \* @type: (PROCESS) => Bool;
- OnTimeoutPropose(p) ==
- /\ step[p] = "PROPOSE"
- /\ p /= Proposer[round[p]]
- /\ BroadcastPrevote(p, round[p], NilProposal)
- /\ step' = [step EXCEPT ![p] = "PREVOTE"]
- /\ UNCHANGED <<temporalVars, invariantVars>>
- /\ UNCHANGED
- <<round, (*step,*) decision, lockedValue,
- lockedRound, validValue, validRound>>
- /\ UNCHANGED
- <<msgsPropose, (*msgsPrevote,*) msgsPrecommit,
- evidence, receivedTimelyProposal, inspectedProposal>>
- /\ action' = "OnTimeoutPropose"
-
- \* lines 44-46
- \* @type: (PROCESS) => Bool;
- OnQuorumOfNilPrevotes(p) ==
- /\ step[p] = "PREVOTE"
- /\ LET PV == { m \in msgsPrevote[round[p]]: m.id = Id(NilProposal) } IN
- /\ Cardinality(PV) >= THRESHOLD2 \* line 36
- /\ evidence' = PV \union evidence
- /\ BroadcastPrecommit(p, round[p], Id(NilProposal))
- /\ step' = [step EXCEPT ![p] = "PRECOMMIT"]
- /\ UNCHANGED <<temporalVars, invariantVars>>
- /\ UNCHANGED
- <<round, (*step,*) decision, lockedValue,
- lockedRound, validValue, validRound>>
- /\ UNCHANGED
- <<msgsPropose, msgsPrevote, (*msgsPrecommit,*)
- (*evidence,*) receivedTimelyProposal, inspectedProposal>>
- /\ action' = "OnQuorumOfNilPrevotes"
-
- \* lines 55-56
- \* @type: (PROCESS) => Bool;
- OnRoundCatchup(p) ==
- \E r \in {rr \in Rounds: rr > round[p]}:
- LET RoundMsgs == msgsPropose[r] \union msgsPrevote[r] \union msgsPrecommit[r] IN
- \E MyEvidence \in SUBSET RoundMsgs:
- LET Faster == { m.src: m \in MyEvidence } IN
- /\ Cardinality(Faster) >= THRESHOLD1
- /\ evidence' = MyEvidence \union evidence
- /\ StartRound(p, r)
- /\ UNCHANGED temporalVars
- /\ UNCHANGED
- <<(*beginRound,*) endConsensus, (*lastBeginRound,*)
- proposalTime, proposalReceivedTime>>
- /\ UNCHANGED
- <<(*round, step,*) decision, lockedValue,
- lockedRound, validValue, validRound>>
- /\ UNCHANGED
- <<msgsPropose, msgsPrevote, msgsPrecommit,
- (*evidence,*) receivedTimelyProposal, inspectedProposal>>
- /\ action' = "OnRoundCatchup"
-
-
- (********************* PROTOCOL TRANSITIONS ******************************)
- \* advance the global clock
- \* @type: Bool;
- AdvanceRealTime ==
- /\ ValidTime(realTime)
- /\ \E t \in Timestamps:
- /\ t > realTime
- /\ realTime' = t
- /\ localClock' = [p \in Corr |-> localClock[p] + (t - realTime)]
- /\ UNCHANGED <<coreVars, bookkeepingVars, invariantVars>>
- /\ action' = "AdvanceRealTime"
-
- \* advance the local clock of node p to some larger time t, not necessarily by 1
- \* #type: (PROCESS) => Bool;
- \* AdvanceLocalClock(p) ==
- \* /\ ValidTime(localClock[p])
- \* /\ \E t \in Timestamps:
- \* /\ t > localClock[p]
- \* /\ localClock' = [localClock EXCEPT ![p] = t]
- \* /\ UNCHANGED <<coreVars, bookkeepingVars, invariantVars>>
- \* /\ UNCHANGED realTime
- \* /\ action' = "AdvanceLocalClock"
-
- \* process timely messages
- \* @type: (PROCESS) => Bool;
- MessageProcessing(p) ==
- \* start round
- \/ InsertProposal(p)
- \* reception step
- \/ ReceiveProposal(p)
- \* processing step
- \/ UponProposalInPropose(p)
- \/ UponProposalInProposeAndPrevote(p)
- \/ UponQuorumOfPrevotesAny(p)
- \/ UponProposalInPrevoteOrCommitAndPrevote(p)
- \/ UponQuorumOfPrecommitsAny(p)
- \/ UponProposalInPrecommitNoDecision(p)
- \* the actions below are not essential for safety, but added for completeness
- \/ OnTimeoutPropose(p)
- \/ OnQuorumOfNilPrevotes(p)
- \/ OnRoundCatchup(p)
-
- (*
- * A system transition. In this specificatiom, the system may eventually deadlock,
- * e.g., when all processes decide. This is expected behavior, as we focus on safety.
- *)
- Next ==
- \/ AdvanceRealTime
- \/ /\ SynchronizedLocalClocks
- /\ \E p \in Corr: MessageProcessing(p)
-
- -----------------------------------------------------------------------------
-
- (*************************** INVARIANTS *************************************)
-
- \* [PBTS-INV-AGREEMENT.0]
- AgreementOnValue ==
- \A p, q \in Corr:
- /\ decision[p] /= NilDecision
- /\ decision[q] /= NilDecision
- => \E v \in ValidValues, t \in Timestamps, pr \in Rounds, r1 \in Rounds, r2 \in Rounds :
- LET prop == Proposal(v,t,pr)
- IN
- /\ decision[p] = Decision(prop, r1)
- /\ decision[q] = Decision(prop, r2)
-
- \* [PBTS-CONSENSUS-TIME-VALID.0]
- ConsensusTimeValid ==
- \A p \in Corr:
- \* if a process decides on v and t
- \E v \in ValidValues, t \in Timestamps, pr \in Rounds, dr \in Rounds :
- decision[p] = Decision(Proposal(v,t,pr), dr)
- \* then
- \* TODO: consider tighter bound where beginRound[pr] is replaced
- \* w/ MedianOfRound[pr]
- => (/\ beginRound[pr] - Precision - Delay <= t
- /\ t <= endConsensus[p] + Precision)
-
- \* [PBTS-CONSENSUS-SAFE-VALID-CORR-PROP.0]
- ConsensusSafeValidCorrProp ==
- \A v \in ValidValues:
- \* and there exists a process that decided on v, t
- /\ \E p \in Corr, t \in Timestamps, pr \in Rounds, dr \in Rounds :
- \* if the proposer in the round is correct
- (/\ Proposer[pr] \in Corr
- /\ decision[p] = Decision(Proposal(v,t,pr), dr))
- \* then t is between the minimal and maximal initial local time
- => /\ beginRound[pr] <= t
- /\ t <= lastBeginRound[pr]
-
- \* [PBTS-CONSENSUS-REALTIME-VALID-CORR.0]
- ConsensusRealTimeValidCorr ==
- \A r \in Rounds :
- \E p \in Corr, v \in ValidValues, t \in Timestamps, pr \in Rounds:
- (/\ decision[p] = Decision(Proposal(v,t,pr), r)
- /\ proposalTime[r] /= NilTimestamp)
- => (/\ proposalTime[r] - Precision <= t
- /\ t <= proposalTime[r] + Precision)
-
- \* [PBTS-CONSENSUS-REALTIME-VALID.0]
- ConsensusRealTimeValid ==
- \A t \in Timestamps, r \in Rounds :
- (\E p \in Corr, v \in ValidValues, pr \in Rounds :
- decision[p] = Decision(Proposal(v,t,pr), r))
- => /\ proposalReceivedTime[r] - Precision < t
- /\ t < proposalReceivedTime[r] + Precision + Delay
-
- DecideAfterMin == TRUE
- \* if decide => time > min
-
- \* [PBTS-MSG-FAIR.0]
- BoundedDelay ==
- \A r \in Rounds :
- (/\ proposalTime[r] /= NilTimestamp
- /\ proposalTime[r] + Delay < realTime)
- => \A p \in Corr: inspectedProposal[r,p] /= NilTimestamp
-
- \* [PBTS-CONSENSUS-TIME-LIVE.0]
- ConsensusTimeLive ==
- \A r \in Rounds, p \in Corr :
- (/\ proposalTime[r] /= NilTimestamp
- /\ proposalTime[r] + Delay < realTime
- /\ Proposer[r] \in Corr
- /\ round[p] <= r)
- => \E msg \in RoundProposals(r) : msg \in receivedTimelyProposal[p]
-
- \* a conjunction of all invariants
- Inv ==
- /\ AgreementOnValue
- /\ ConsensusTimeValid
- /\ ConsensusSafeValidCorrProp
- \* /\ ConsensusRealTimeValid
- \* /\ ConsensusRealTimeValidCorr
- \* /\ BoundedDelay
-
- \* Liveness ==
- \* ConsensusTimeLive
-
- =============================================================================
|