-------------------- MODULE TendermintAcc_004_draft ---------------------------
|
|
(*
|
|
A TLA+ specification of a simplified Tendermint consensus, tuned for
|
|
fork accountability. The simplifications are as follows:
|
|
|
|
- the protocol runs for one height, that is, it is one-shot consensus
|
|
|
|
- this specification focuses on safety, so timeouts are modelled
|
|
with non-determinism
|
|
|
|
- the proposer function is non-determinstic, no fairness is assumed
|
|
|
|
- the messages by the faulty processes are injected right in the initial states
|
|
|
|
- every process has the voting power of 1
|
|
|
|
- hashes are modelled as identity
|
|
|
|
Having the above assumptions in mind, the specification follows the pseudo-code
|
|
of the Tendermint paper: https://arxiv.org/abs/1807.04938
|
|
|
|
Byzantine processes can demonstrate arbitrary behavior, including
|
|
no communication. We show that if agreement is violated, then the Byzantine
|
|
processes demonstrate one of the two behaviours:
|
|
|
|
- Equivocation: a Byzantine process may send two different values
|
|
in the same round.
|
|
|
|
- Amnesia: a Byzantine process may lock a value without unlocking
|
|
the previous value that it has locked in the past.
|
|
|
|
* Version 4. Remove defective processes, fix bugs, collect global evidence.
|
|
* Version 3. Modular and parameterized definitions.
|
|
* Version 2. Bugfixes in the spec and an inductive invariant.
|
|
* Version 1. A preliminary specification.
|
|
|
|
Zarko Milosevic, Igor Konnov, Informal Systems, 2019-2020.
|
|
*)
|
|
|
|
EXTENDS Integers, FiniteSets, typedefs
|
|
|
|
(********************* 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
|
|
|
|
ASSUME(N = Cardinality(Corr \union Faulty))
|
|
|
|
(*************************** DEFINITIONS ************************************)
|
|
AllProcs == Corr \union Faulty \* the set of all processes
|
|
\* @type: Set(ROUND);
|
|
Rounds == 0..MaxRound \* the set of potential rounds
|
|
\* @type: ROUND;
|
|
NilRound == -1 \* a special value to denote a nil round, outside of Rounds
|
|
RoundsOrNil == Rounds \union {NilRound}
|
|
Values == ValidValues \union InvalidValues \* the set of all values
|
|
\* @type: VALUE;
|
|
NilValue == "None" \* a special value for a nil round, outside of Values
|
|
ValuesOrNil == Values \union {NilValue}
|
|
|
|
\* a value hash is modeled as identity
|
|
\* @type: (t) => t;
|
|
Id(v) == v
|
|
|
|
\* The validity predicate
|
|
IsValid(v) == v \in ValidValues
|
|
|
|
\* the two thresholds that are used in the algorithm
|
|
THRESHOLD1 == T + 1 \* at least one process is not faulty
|
|
THRESHOLD2 == 2 * T + 1 \* a quorum when having N > 3 * T
|
|
|
|
(********************* TYPE ANNOTATIONS FOR APALACHE **************************)
|
|
|
|
\* An empty set of messages
|
|
\* @type: Set(MESSAGE);
|
|
EmptyMsgSet == {}
|
|
|
|
(********************* PROTOCOL STATE VARIABLES ******************************)
|
|
VARIABLES
|
|
\* @type: PROCESS -> ROUND;
|
|
round, \* a process round number: Corr -> Rounds
|
|
\* @type: PROCESS -> STEP;
|
|
step, \* a process step: Corr -> { "PROPOSE", "PREVOTE", "PRECOMMIT", "DECIDED" }
|
|
\* @type: PROCESS -> VALUE;
|
|
decision, \* process decision: Corr -> ValuesOrNil
|
|
\* @type: PROCESS -> VALUE;
|
|
lockedValue, \* a locked value: Corr -> ValuesOrNil
|
|
\* @type: PROCESS -> ROUND;
|
|
lockedRound, \* a locked round: Corr -> RoundsOrNil
|
|
\* @type: PROCESS -> VALUE;
|
|
validValue, \* a valid value: Corr -> ValuesOrNil
|
|
\* @type: PROCESS -> ROUND;
|
|
validRound \* a valid round: Corr -> RoundsOrNil
|
|
|
|
\* 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
|
|
|
|
(* to see a type invariant, check TendermintAccInv3 *)
|
|
|
|
\* a handy definition used in UNCHANGED
|
|
vars == <<round, step, decision, lockedValue, lockedRound,
|
|
validValue, validRound, evidence, msgsPropose, msgsPrevote, msgsPrecommit>>
|
|
|
|
(********************* PROTOCOL INITIALIZATION ******************************)
|
|
\* @type: (ROUND) => Set(PROPMESSAGE);
|
|
FaultyProposals(r) ==
|
|
[
|
|
type : {"PROPOSAL"},
|
|
src : Faulty,
|
|
round : {r},
|
|
proposal : Values,
|
|
validRound: RoundsOrNil
|
|
]
|
|
|
|
\* @type: Set(PROPMESSAGE);
|
|
AllFaultyProposals ==
|
|
[
|
|
type : {"PROPOSAL"},
|
|
src : Faulty,
|
|
round : Rounds,
|
|
proposal : Values,
|
|
validRound: RoundsOrNil
|
|
]
|
|
|
|
\* @type: (ROUND) => Set(PREMESSAGE);
|
|
FaultyPrevotes(r) ==
|
|
[
|
|
type : {"PREVOTE"},
|
|
src : Faulty,
|
|
round: {r},
|
|
id : Values
|
|
]
|
|
|
|
\* @type: Set(PREMESSAGE);
|
|
AllFaultyPrevotes ==
|
|
[
|
|
type : {"PREVOTE"},
|
|
src : Faulty,
|
|
round: Rounds,
|
|
id : Values
|
|
]
|
|
|
|
\* @type: (ROUND) => Set(PREMESSAGE);
|
|
FaultyPrecommits(r) ==
|
|
[
|
|
type : {"PRECOMMIT"},
|
|
src : Faulty,
|
|
round: {r},
|
|
id : Values
|
|
]
|
|
|
|
\* @type: Set(PREMESSAGE);
|
|
AllFaultyPrecommits ==
|
|
[
|
|
type : {"PRECOMMIT"},
|
|
src : Faulty,
|
|
round: Rounds,
|
|
id : Values
|
|
]
|
|
|
|
\* @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]
|
|
/\ step = [p \in Corr |-> "PROPOSE"]
|
|
/\ decision = [p \in Corr |-> NilValue]
|
|
/\ lockedValue = [p \in Corr |-> NilValue]
|
|
/\ lockedRound = [p \in Corr |-> NilRound]
|
|
/\ validValue = [p \in Corr |-> NilValue]
|
|
/\ validRound = [p \in Corr |-> NilRound]
|
|
/\ msgsPropose \in [Rounds -> SUBSET AllFaultyProposals]
|
|
/\ msgsPrevote \in [Rounds -> SUBSET AllFaultyPrevotes]
|
|
/\ msgsPrecommit \in [Rounds -> SUBSET AllFaultyPrecommits]
|
|
/\ BenignRoundsInMessages(msgsPropose)
|
|
/\ BenignRoundsInMessages(msgsPrevote)
|
|
/\ BenignRoundsInMessages(msgsPrecommit)
|
|
/\ evidence = EmptyMsgSet
|
|
/\ action = "Init"
|
|
|
|
(************************ MESSAGE PASSING ********************************)
|
|
\* @type: (PROCESS, ROUND, VALUE, 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, VALUE) => 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, VALUE) => 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}]
|
|
|
|
|
|
(********************* PROTOCOL TRANSITIONS ******************************)
|
|
\* lines 12-13
|
|
StartRound(p, r) ==
|
|
/\ step[p] /= "DECIDED" \* a decided process does not participate in consensus
|
|
/\ round' = [round EXCEPT ![p] = r]
|
|
/\ step' = [step EXCEPT ![p] = "PROPOSE"]
|
|
|
|
\* 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
|
|
/\ \E v \in ValidValues:
|
|
LET
|
|
\* @type: VALUE;
|
|
proposal ==
|
|
IF validValue[p] /= NilValue
|
|
THEN validValue[p]
|
|
ELSE v
|
|
IN BroadcastProposal(p, round[p], proposal, validRound[p])
|
|
/\ UNCHANGED <<evidence, round, decision, lockedValue, lockedRound,
|
|
validValue, step, validRound, msgsPrevote, msgsPrecommit>>
|
|
/\ action' = "InsertProposal"
|
|
|
|
\* lines 22-27
|
|
UponProposalInPropose(p) ==
|
|
\E v \in Values:
|
|
/\ step[p] = "PROPOSE" (* line 22 *)
|
|
/\ LET
|
|
\* @type: PROPMESSAGE;
|
|
msg ==
|
|
[
|
|
type |-> "PROPOSAL",
|
|
src |-> Proposer[round[p]],
|
|
round |-> round[p],
|
|
proposal |-> v,
|
|
validRound |-> NilRound
|
|
]
|
|
IN
|
|
/\ msg \in msgsPropose[round[p]] \* line 22
|
|
/\ evidence' = {msg} \union evidence
|
|
/\ LET mid == (* line 23 *)
|
|
IF IsValid(v) /\ (lockedRound[p] = NilRound \/ lockedValue[p] = v)
|
|
THEN Id(v)
|
|
ELSE NilValue
|
|
IN
|
|
BroadcastPrevote(p, round[p], mid) \* lines 24-26
|
|
/\ step' = [step EXCEPT ![p] = "PREVOTE"]
|
|
/\ UNCHANGED <<round, decision, lockedValue, lockedRound,
|
|
validValue, validRound, msgsPropose, msgsPrecommit>>
|
|
/\ action' = "UponProposalInPropose"
|
|
|
|
\* lines 28-33
|
|
UponProposalInProposeAndPrevote(p) ==
|
|
\E v \in Values, vr \in Rounds:
|
|
/\ step[p] = "PROPOSE" /\ 0 <= vr /\ vr < round[p] \* line 28, the while part
|
|
/\ LET
|
|
\* @type: PROPMESSAGE;
|
|
msg ==
|
|
[
|
|
type |-> "PROPOSAL",
|
|
src |-> Proposer[round[p]],
|
|
round |-> round[p],
|
|
proposal |-> v,
|
|
validRound |-> vr
|
|
]
|
|
IN
|
|
/\ msg \in msgsPropose[round[p]] \* line 28
|
|
/\ LET PV == { m \in msgsPrevote[vr]: m.id = Id(v) } IN
|
|
/\ Cardinality(PV) >= THRESHOLD2 \* line 28
|
|
/\ evidence' = PV \union {msg} \union evidence
|
|
/\ LET mid == (* line 29 *)
|
|
IF IsValid(v) /\ (lockedRound[p] <= vr \/ lockedValue[p] = v)
|
|
THEN Id(v)
|
|
ELSE NilValue
|
|
IN
|
|
BroadcastPrevote(p, round[p], mid) \* lines 24-26
|
|
/\ step' = [step EXCEPT ![p] = "PREVOTE"]
|
|
/\ UNCHANGED <<round, decision, lockedValue, lockedRound,
|
|
validValue, validRound, msgsPropose, msgsPrecommit>>
|
|
/\ action' = "UponProposalInProposeAndPrevote"
|
|
|
|
\* lines 34-35 + lines 61-64 (onTimeoutPrevote)
|
|
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], NilValue)
|
|
/\ step' = [step EXCEPT ![p] = "PRECOMMIT"]
|
|
/\ UNCHANGED <<round, decision, lockedValue, lockedRound,
|
|
validValue, validRound, msgsPropose, msgsPrevote>>
|
|
/\ action' = "UponQuorumOfPrevotesAny"
|
|
|
|
\* lines 36-46
|
|
UponProposalInPrevoteOrCommitAndPrevote(p) ==
|
|
\E v \in ValidValues, vr \in RoundsOrNil:
|
|
/\ step[p] \in {"PREVOTE", "PRECOMMIT"} \* line 36
|
|
/\ LET
|
|
\* @type: PROPMESSAGE;
|
|
msg ==
|
|
[
|
|
type |-> "PROPOSAL",
|
|
src |-> Proposer[round[p]],
|
|
round |-> round[p],
|
|
proposal |-> v,
|
|
validRound |-> vr
|
|
]
|
|
IN
|
|
/\ msg \in msgsPropose[round[p]] \* line 36
|
|
/\ LET PV == { m \in msgsPrevote[round[p]]: m.id = Id(v) } 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] = round[p]]
|
|
/\ BroadcastPrecommit(p, round[p], Id(v))
|
|
/\ step' = [step EXCEPT ![p] = "PRECOMMIT"]
|
|
ELSE
|
|
UNCHANGED <<lockedValue, lockedRound, msgsPrecommit, step>>
|
|
\* lines 42-43
|
|
/\ validValue' = [validValue EXCEPT ![p] = v]
|
|
/\ validRound' = [validRound EXCEPT ![p] = round[p]]
|
|
/\ UNCHANGED <<round, decision, msgsPropose, msgsPrevote>>
|
|
/\ action' = "UponProposalInPrevoteOrCommitAndPrevote"
|
|
|
|
\* lines 47-48 + 65-67 (onTimeoutPrecommit)
|
|
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 <<decision, lockedValue, lockedRound, validValue,
|
|
validRound, msgsPropose, msgsPrevote, msgsPrecommit>>
|
|
/\ action' = "UponQuorumOfPrecommitsAny"
|
|
|
|
\* lines 49-54
|
|
UponProposalInPrecommitNoDecision(p) ==
|
|
/\ decision[p] = NilValue \* line 49
|
|
/\ \E v \in ValidValues (* line 50*) , r \in Rounds, vr \in RoundsOrNil:
|
|
/\ LET
|
|
\* @type: PROPMESSAGE;
|
|
msg ==
|
|
[
|
|
type |-> "PROPOSAL",
|
|
src |-> Proposer[r],
|
|
round |-> r,
|
|
proposal |-> v,
|
|
validRound |-> vr
|
|
]
|
|
IN
|
|
/\ msg \in msgsPropose[r] \* line 49
|
|
/\ LET PV == { m \in msgsPrecommit[r]: m.id = Id(v) } IN
|
|
/\ Cardinality(PV) >= THRESHOLD2 \* line 49
|
|
/\ evidence' = PV \union {msg} \union evidence
|
|
/\ decision' = [decision EXCEPT ![p] = v] \* 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.
|
|
/\ step' = [step EXCEPT ![p] = "DECIDED"]
|
|
/\ UNCHANGED <<round, lockedValue, lockedRound, validValue,
|
|
validRound, msgsPropose, msgsPrevote, msgsPrecommit>>
|
|
/\ action' = "UponProposalInPrecommitNoDecision"
|
|
|
|
\* the actions below are not essential for safety, but added for completeness
|
|
|
|
\* lines 20-21 + 57-60
|
|
OnTimeoutPropose(p) ==
|
|
/\ step[p] = "PROPOSE"
|
|
/\ p /= Proposer[round[p]]
|
|
/\ BroadcastPrevote(p, round[p], NilValue)
|
|
/\ step' = [step EXCEPT ![p] = "PREVOTE"]
|
|
/\ UNCHANGED <<round, lockedValue, lockedRound, validValue,
|
|
validRound, decision, evidence, msgsPropose, msgsPrecommit>>
|
|
/\ action' = "OnTimeoutPropose"
|
|
|
|
\* lines 44-46
|
|
OnQuorumOfNilPrevotes(p) ==
|
|
/\ step[p] = "PREVOTE"
|
|
/\ LET PV == { m \in msgsPrevote[round[p]]: m.id = Id(NilValue) } IN
|
|
/\ Cardinality(PV) >= THRESHOLD2 \* line 36
|
|
/\ evidence' = PV \union evidence
|
|
/\ BroadcastPrecommit(p, round[p], Id(NilValue))
|
|
/\ step' = [step EXCEPT ![p] = "PRECOMMIT"]
|
|
/\ UNCHANGED <<round, lockedValue, lockedRound, validValue,
|
|
validRound, decision, msgsPropose, msgsPrevote>>
|
|
/\ action' = "OnQuorumOfNilPrevotes"
|
|
|
|
\* lines 55-56
|
|
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 <<decision, lockedValue, lockedRound, validValue,
|
|
validRound, msgsPropose, msgsPrevote, msgsPrecommit>>
|
|
/\ action' = "OnRoundCatchup"
|
|
|
|
(*
|
|
* 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 ==
|
|
\E p \in Corr:
|
|
\/ InsertProposal(p)
|
|
\/ 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)
|
|
|
|
|
|
(**************************** FORK SCENARIOS ***************************)
|
|
|
|
\* equivocation by a process p
|
|
EquivocationBy(p) ==
|
|
\E m1, m2 \in evidence:
|
|
/\ m1 /= m2
|
|
/\ m1.src = p
|
|
/\ m2.src = p
|
|
/\ m1.round = m2.round
|
|
/\ m1.type = m2.type
|
|
|
|
\* amnesic behavior by a process p
|
|
AmnesiaBy(p) ==
|
|
\E r1, r2 \in Rounds:
|
|
/\ r1 < r2
|
|
/\ \E v1, v2 \in ValidValues:
|
|
/\ v1 /= v2
|
|
/\ [
|
|
type |-> "PRECOMMIT",
|
|
src |-> p,
|
|
round |-> r1,
|
|
id |-> Id(v1)
|
|
] \in evidence
|
|
/\ [
|
|
type |-> "PREVOTE",
|
|
src |-> p,
|
|
round |-> r2,
|
|
id |-> Id(v2)
|
|
] \in evidence
|
|
/\ \A r \in { rnd \in Rounds: r1 <= rnd /\ rnd < r2 }:
|
|
LET prevotes ==
|
|
{ m \in evidence:
|
|
m.type = "PREVOTE" /\ m.round = r /\ m.id = Id(v2) }
|
|
IN
|
|
Cardinality(prevotes) < THRESHOLD2
|
|
|
|
(******************************** PROPERTIES ***************************************)
|
|
|
|
\* the safety property -- agreement
|
|
Agreement ==
|
|
\A p, q \in Corr:
|
|
\/ decision[p] = NilValue
|
|
\/ decision[q] = NilValue
|
|
\/ decision[p] = decision[q]
|
|
|
|
\* the protocol validity
|
|
Validity ==
|
|
\A p \in Corr: decision[p] \in ValidValues \union {NilValue}
|
|
|
|
(*
|
|
The protocol safety. Two cases are possible:
|
|
1. There is no fork, that is, Agreement holds true.
|
|
2. A subset of faulty processes demonstrates equivocation or amnesia.
|
|
*)
|
|
Accountability ==
|
|
\/ Agreement
|
|
\/ \E Detectable \in SUBSET Faulty:
|
|
/\ Cardinality(Detectable) >= THRESHOLD1
|
|
/\ \A p \in Detectable:
|
|
EquivocationBy(p) \/ AmnesiaBy(p)
|
|
|
|
(****************** FALSE INVARIANTS TO PRODUCE EXAMPLES ***********************)
|
|
|
|
\* This property is violated. You can check it to see how amnesic behavior
|
|
\* appears in the evidence variable.
|
|
NoAmnesia ==
|
|
\A p \in Faulty: ~AmnesiaBy(p)
|
|
|
|
\* This property is violated. You can check it to see an example of equivocation.
|
|
NoEquivocation ==
|
|
\A p \in Faulty: ~EquivocationBy(p)
|
|
|
|
\* This property is violated. You can check it to see an example of agreement.
|
|
\* It is not exactly ~Agreement, as we do not want to see the states where
|
|
\* decision[p] = NilValue
|
|
NoAgreement ==
|
|
\A p, q \in Corr:
|
|
(p /= q /\ decision[p] /= NilValue /\ decision[q] /= NilValue)
|
|
=> decision[p] /= decision[q]
|
|
|
|
\* Either agreement holds, or the faulty processes indeed demonstrate amnesia.
|
|
\* This property is violated. A counterexample should demonstrate equivocation.
|
|
AgreementOrAmnesia ==
|
|
Agreement \/ (\A p \in Faulty: AmnesiaBy(p))
|
|
|
|
\* We expect this property to be violated. It shows us a protocol run,
|
|
\* where one faulty process demonstrates amnesia without equivocation.
|
|
\* However, the absence of amnesia
|
|
\* is a tough constraint for Apalache. It has not reported a counterexample
|
|
\* for n=4,f=2, length <= 5.
|
|
ShowMeAmnesiaWithoutEquivocation ==
|
|
(~Agreement /\ \E p \in Faulty: ~EquivocationBy(p))
|
|
=> \A p \in Faulty: ~AmnesiaBy(p)
|
|
|
|
\* This property is violated on n=4,f=2, length=4 in less than 10 min.
|
|
\* Two faulty processes may demonstrate amnesia without equivocation.
|
|
AmnesiaImpliesEquivocation ==
|
|
(\E p \in Faulty: AmnesiaBy(p)) => (\E q \in Faulty: EquivocationBy(q))
|
|
|
|
(*
|
|
This property is violated. You can check it to see that all correct processes
|
|
may reach MaxRound without making a decision.
|
|
*)
|
|
NeverUndecidedInMaxRound ==
|
|
LET AllInMax == \A p \in Corr: round[p] = MaxRound
|
|
AllDecided == \A p \in Corr: decision[p] /= NilValue
|
|
IN
|
|
AllInMax => AllDecided
|
|
|
|
=============================================================================
|
|
|