├── .envrc ├── .github └── workflows │ └── test.yml ├── .gitignore ├── Apalache.tla ├── LICENSE ├── LogPaxos ├── LogPaxos.tla ├── MC.cfg ├── MC.tla └── README.md ├── Makefile ├── Paxos ├── MC.cfg ├── MC.tla ├── Paxos.tla └── README.md ├── README.md ├── conspire ├── Abstract.tla ├── AbstractLog.tla ├── PNoC.cfg ├── PNoC.tla └── models │ ├── AbstractLogMC.cfg │ ├── AbstractLogMC.tla │ ├── AbstractMC.cfg │ └── AbstractMC.tla ├── flake.lock ├── flake.nix ├── msfp ├── Nezha.tla ├── PrimedValue.tla ├── SimpleValue.cfg └── SimpleValue.tla └── u2pc ├── Apalache.tla ├── U2PC.tla ├── U2PC_MC.cfg ├── U2PC_MC.tla └── latex ├── U2PC.pdf ├── U2PC.tex ├── U2PC_MC.pdf └── U2PC_MC.tex /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "Model check" 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | main 7 | 8 | jobs: 9 | tests: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: cachix/install-nix-action@v13 14 | with: 15 | nix_path: nixpkgs=channel:nixos-unstable 16 | - uses: workflow/nix-shell-action@v1 17 | with: 18 | packages: tlaplus 19 | script: | 20 | make test 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **.toolbox 2 | **/states 3 | **/_apalache-out 4 | .direnv 5 | 6 | -------------------------------------------------------------------------------- /Apalache.tla: -------------------------------------------------------------------------------- 1 | --------------------------- MODULE Apalache ----------------------------------- 2 | (* 3 | * This is a standard module for use with the Apalache model checker. 4 | * The meaning of the operators is explained in the comments. 5 | * Many of the operators serve as additional annotations of their arguments. 6 | * As we like to preserve compatibility with TLC and TLAPS, we define the 7 | * operator bodies by erasure. The actual interpretation of the operators is 8 | * encoded inside Apalache. For the moment, these operators are mirrored in 9 | * the class at.forsyte.apalache.tla.lir.oper.ApalacheOper. 10 | * 11 | * Igor Konnov, Jure Kukovec, Informal Systems 2020-2022 12 | *) 13 | 14 | (** 15 | * An assignment of an expression e to a state variable x. Typically, one 16 | * uses the non-primed version of x in the initializing predicate Init and 17 | * the primed version of x (that is, x') in the transition predicate Next. 18 | * Although TLA+ does not have a concept of a variable assignment, we find 19 | * this concept extremely useful for symbolic model checking. In pure TLA+, 20 | * one would simply write x = e, or x \in {e}. 21 | * 22 | * Apalache automatically converts some expressions of the form 23 | * x = e or x \in {e} into assignments. However, if you like to annotate 24 | * assignments by hand, you can use this operator. 25 | * 26 | * For a further discussion on that matter, see: 27 | * https://github.com/informalsystems/apalache/blob/main/docs/src/idiomatic/001assignments.md 28 | *) 29 | __x := __e == __x = __e 30 | 31 | (** 32 | * A generator of a data structure. Given a positive integer `bound`, and 33 | * assuming that the type of the operator application is known, we 34 | * recursively generate a TLA+ data structure as a tree, whose width is 35 | * bound by the number `bound`. 36 | * 37 | * The body of this operator is redefined by Apalache. 38 | *) 39 | Gen(__size) == {} 40 | 41 | (** 42 | * Non-deterministically pick a value out of the set `S`, if `S` is non-empty. 43 | * If `S` is empty, return some value of the proper type. This can be 44 | * understood as a non-deterministic version of CHOOSE x \in S: TRUE. 45 | * 46 | * @type: Set(a) => a; 47 | *) 48 | Guess(__S) == 49 | \* Since this is not supported by TLC, 50 | \* we fall back to the deterministic version for TLC. 51 | \* Apalache redefines the operator `Guess` as explained above. 52 | CHOOSE __x \in __S: TRUE 53 | 54 | (** 55 | * Convert a set of pairs S to a function F. Note that if S contains at least 56 | * two pairs <> and <> such that x = u and y /= v, 57 | * then F is not uniquely defined. We use CHOOSE to resolve this ambiguity. 58 | * Apalache implements a more efficient encoding of this operator 59 | * than the default one. 60 | * 61 | * @type: Set(<>) => (a -> b); 62 | *) 63 | SetAsFun(__S) == 64 | LET __Dom == { __x: <<__x, __y>> \in __S } 65 | __Rng == { __y: <<__x, __y>> \in __S } 66 | IN 67 | [ __x \in __Dom |-> CHOOSE __y \in __Rng: <<__x, __y>> \in __S ] 68 | 69 | (** 70 | * A sequence constructor that avoids using a function constructor. 71 | * Since Apalache is typed, this operator is more efficient than 72 | * FunAsSeq([ i \in 1..N |-> F(i) ]). Apalache requires N to be 73 | * a constant expression. 74 | * 75 | * @type: (Int, (Int -> a)) => Seq(a); 76 | *) 77 | LOCAL INSTANCE Integers 78 | MkSeq(__N, __F(_)) == 79 | \* This is the TLC implementation. Apalache does it differently. 80 | [ __i \in (1..__N) |-> __F(__i) ] 81 | 82 | \* required by our default definition of FoldSeq and FunAsSeq 83 | LOCAL INSTANCE Sequences 84 | 85 | (** 86 | * As TLA+ is untyped, one can use function- and sequence-specific operators 87 | * interchangeably. However, to maintain correctness w.r.t. our type-system, 88 | * an explicit cast is needed when using functions as sequences. 89 | * FunAsSeq reinterprets a function over integers as a sequence. 90 | * 91 | * The parameters have the following meaning: 92 | * 93 | * - fn is the function from 1..len that should be interpreted as a sequence. 94 | * - len is the length of the sequence, len = Cardinality(DOMAIN fn), 95 | * len may be a variable, a computable expression, etc. 96 | * - capacity is a static upper bound on the length, that is, len <= capacity. 97 | * 98 | * @type: ((Int -> a), Int, Int) => Seq(a); 99 | *) 100 | FunAsSeq(__fn, __len, __capacity) == 101 | LET __FunAsSeq_elem_ctor(__i) == __fn[__i] IN 102 | SubSeq(MkSeq(__capacity, __FunAsSeq_elem_ctor), 1, __len) 103 | 104 | (** 105 | * Annotating an expression \E x \in S: P as Skolemizable. That is, it can 106 | * be replaced with an expression c \in S /\ P(c) for a fresh constant c. 107 | * Not every exisential can be replaced with a constant, this should be done 108 | * with care. Apalache detects Skolemizable expressions by static analysis. 109 | *) 110 | Skolem(__e) == __e 111 | 112 | (** 113 | * A hint to the model checker to expand a set S, instead of dealing 114 | * with it symbolically. Apalache finds out which sets have to be expanded 115 | * by static analysis. 116 | *) 117 | Expand(__S) == __S 118 | 119 | (** 120 | * A hint to the model checker to replace its argument Cardinality(S) >= k 121 | * with a series of existential quantifiers for a constant k. 122 | * Similar to Skolem, this has to be done carefully. Apalache automatically 123 | * places this hint by static analysis. 124 | *) 125 | ConstCardinality(__cardExpr) == __cardExpr 126 | 127 | (** 128 | * The folding operator, used to implement computation over a set. 129 | * Apalache implements a more efficient encoding than the one below. 130 | * (from the community modules). 131 | * 132 | * @type: ((a, b) => a, a, Set(b)) => a; 133 | *) 134 | RECURSIVE ApaFoldSet(_, _, _) 135 | ApaFoldSet(__Op(_,_), __v, __S) == 136 | IF __S = {} 137 | THEN __v 138 | ELSE LET __w == CHOOSE __x \in __S: TRUE IN 139 | LET __T == __S \ {__w} IN 140 | ApaFoldSet(__Op, __Op(__v,__w), __T) 141 | 142 | (** 143 | * The folding operator, used to implement computation over a sequence. 144 | * Apalache implements a more efficient encoding than the one below. 145 | * (from the community modules). 146 | * 147 | * @type: ((a, b) => a, a, Seq(b)) => a; 148 | *) 149 | RECURSIVE ApaFoldSeqLeft(_, _, _) 150 | ApaFoldSeqLeft(__Op(_,_), __v, __seq) == 151 | IF __seq = <<>> 152 | THEN __v 153 | ELSE ApaFoldSeqLeft(__Op, __Op(__v, Head(__seq)), Tail(__seq)) 154 | 155 | =============================================================================== 156 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Chris Jensen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LogPaxos/LogPaxos.tla: -------------------------------------------------------------------------------- 1 | ----------------------------- MODULE LogPaxos ----------------------------- 2 | 3 | EXTENDS TLC, Integers, Sequences, FiniteSets 4 | 5 | CONSTANTS BallotNumbers, Acceptors, Values, Quorums 6 | 7 | VARIABLES msgs, 8 | acc, 9 | prop 10 | 11 | (* One log is a prefix of another if every item in the prefix is in the other. 12 | * ie Prefix([1,2,3], [1,2,3,4]) 13 | * Prefix([1,2,3], [1,2,3]) 14 | * ~Prefix([1,2,3], [1,2,4]) 15 | *) 16 | Prefix(a,b) == 17 | /\ Len(a) =< Len(b) 18 | /\ \A i \in DOMAIN a: a[i] = b[i] 19 | 20 | (* The set of all elements in the list. *) 21 | Range(s) == {s[i] : i \in DOMAIN s} 22 | 23 | Max(Leq(_,_), s) == CHOOSE v \in s: \A v1 \in s: Leq(v1, v) 24 | Min(Leq(_,_), s) == CHOOSE v \in s: \A v1 \in s: Leq(v, v1) 25 | 26 | (* The comparison function used by acceptors to update acc[a].maxBal 27 | * It is also used by the ValueSelect function to choose the 'maximum' ballot 28 | * And by the proposer to commit the 'minimum' of the quorum responses. *) 29 | BallotLeq(a, b) == 30 | \/ a.bal < b.bal 31 | \/ a.bal = b.bal /\ Prefix(a.val, b.val) 32 | 33 | ----------------------------------------------------------------------------- 34 | 35 | (* Initial state of the the system. *) 36 | Init == /\ msgs = {} 37 | /\ acc = [a \in Acceptors |-> [maxBalNum |-> -1, maxBal |-> [bal |-> -1, val |-> <<>>]]] 38 | /\ prop = [b \in BallotNumbers |-> [val |-> << >>, valSelect |-> FALSE, committed |-> <<>>, hasCommitted |-> FALSE]] 39 | 40 | Send(m) == msgs' = msgs \cup {m} 41 | 42 | (* Phase 1a: A proposer for ballot number b sends a 1a message. *) 43 | Phase1a(b) == 44 | /\ ~\E m \in msgs: m.type = "1a" /\ m.balNum = b 45 | /\ Send ([type |-> "1a", balNum |-> b]) 46 | /\ UNCHANGED << acc, prop >> 47 | 48 | (* Phase 1b: When an acceptor receives a 1a message, if that message is from 49 | * a higher ballot number than the highest one heard of, then update the 50 | * highest ballot number and respond with the stored highest ballot. *) 51 | Phase1b(a) == 52 | \E m \in msgs : 53 | /\ m.type = "1a" 54 | /\ m.balNum > acc[a].maxBalNum 55 | /\ acc' = [acc EXCEPT ![a] = [acc[a] EXCEPT !.maxBalNum = m.balNum]] 56 | /\ Send([type |-> "1b", balNum |-> m.balNum, acc |-> a, maxBal |-> acc[a].maxBal]) 57 | /\ UNCHANGED << prop >> 58 | 59 | (* ValueSelect: When a proposer receives a quorum of phase 1b responses, it sets 60 | * valSelect and its stored value to the value of the maximum ballot within 61 | * those responses. This enables Phase2a. *) 62 | ValueSelect(b) == 63 | /\ ~ prop[b].valSelect 64 | /\ \E Q \in Quorums, S \in SUBSET {m \in msgs: (m.type = "1b") /\ (m.balNum = b)}: 65 | /\ \A a \in Q: \E m \in S: m.acc = a 66 | /\ LET maxBal == Max(BallotLeq, {m.maxBal: m \in S}) 67 | IN /\ prop' = [prop EXCEPT ![b] = 68 | [prop[b] EXCEPT !.val = maxBal.val, !.valSelect = TRUE]] 69 | /\ UNCHANGED << acc, msgs >> 70 | 71 | (* Phase2a: A proposer appends a value to its stored log and sends out a 72 | * phase2a message containing the new log. This ensures that all proposed logs 73 | * extend previous ones. *) 74 | Phase2a(b) == 75 | /\ prop[b].valSelect 76 | /\ \E v \in {<<>>} \cup {<> : v \in Values \ Range(prop[b].val)}: 77 | LET bal == 78 | [bal |-> b, 79 | val |-> prop[b].val \o v] 80 | IN /\ Send([type |-> "2a", bal |-> bal]) 81 | /\ prop' = [prop EXCEPT ![b] = [prop[b] EXCEPT !.val = bal.val]] 82 | /\ UNCHANGED << acc >> 83 | 84 | (* Phase2b: An acceptor upon receiving a phase 2a message from ballot greater 85 | * than or equal to its own, and which has a greater than or equal ballot than 86 | * its stored one updates its stored and responds with a vote for the new ballot. *) 87 | Phase2b(a) == 88 | /\ \E m \in msgs : 89 | /\ m.type = "2a" 90 | /\ m.bal.bal >= acc[a].maxBalNum 91 | /\ BallotLeq(acc[a].maxBal, m.bal) 92 | /\ Send([type |-> "2b", acc |-> a, bal |-> m.bal]) 93 | /\ acc' = [acc EXCEPT ![a] = [maxBalNum |-> m.bal.bal, maxBal |-> m.bal]] 94 | /\ UNCHANGED << prop >> 95 | 96 | (* Commit: When a proposer receives a quorum of votes for logs which extend 97 | * its previously committed one, it commits the shortest log from those. *) 98 | Commit(b) == 99 | \E Q \in Quorums: 100 | \E S \in SUBSET {m \in msgs: /\ m.type = "2b" 101 | /\ m.bal.bal = b 102 | /\ m.acc \in Q}: 103 | /\ \A a \in Q: \E m \in S: m.acc = a 104 | /\ LET val == (Min(BallotLeq, {m.bal: m \in S})).val 105 | IN /\ Prefix(prop[b].committed, val) 106 | /\ \A m \in S: \A m1 \in S \ {m}: m.acc /= m1.acc 107 | /\ prop' = [prop EXCEPT ![b] = [prop[b] EXCEPT !.committed = val, !.hasCommitted = TRUE]] 108 | /\ UNCHANGED << msgs, acc >> 109 | 110 | (* Next: a disjunction of all possible actions. Since each action asserts that 111 | * other states are unchanged only one is true at a time. *) 112 | Next == \/ \E p \in BallotNumbers : Phase1a(p) \/ ValueSelect(p) \/ Phase2a(p) \/ Commit(p) 113 | \/ \E a \in Acceptors : Phase1b(a) \/ Phase2b(a) 114 | 115 | ----------------------------------------------------------------------------- 116 | (* Type checking invariant *) 117 | 118 | (* All possible sequences from a set of items. *) 119 | AllSeqFromSet(S) == 120 | LET unique(f) == \A i,j \in DOMAIN f: i /= j => f[i] /= f[j] 121 | subseq(c) == {seq \in [1..c -> S]: unique(seq)} 122 | IN 123 | UNION {subseq(c): c \in 0..Cardinality(S)} 124 | 125 | PossibleValues == AllSeqFromSet(Values) 126 | 127 | PossibleBallots == [bal : BallotNumbers \cup {-1}, val : PossibleValues] 128 | 129 | Messages == [type : {"1a"}, balNum : BallotNumbers] 130 | \cup [type : {"1b"}, acc : Acceptors, balNum : BallotNumbers, maxBal : PossibleBallots] 131 | \cup [type : {"2a"}, bal : PossibleBallots] 132 | \cup [type : {"2b"}, acc : Acceptors, bal : PossibleBallots] 133 | 134 | ProposerState == [val : PossibleValues, 135 | valSelect : {TRUE, FALSE}, 136 | committed : PossibleValues, 137 | hasCommitted : {TRUE,FALSE}] 138 | 139 | AcceptorState == [maxBalNum : BallotNumbers \cup {-1}, maxBal : PossibleBallots] 140 | 141 | TypeInvariant == /\ msgs \in SUBSET Messages 142 | /\ acc \in [Acceptors -> AcceptorState] 143 | /\ prop \in [BallotNumbers -> ProposerState] 144 | 145 | vars == <> 146 | 147 | ----------------------------------------------------------------------------- 148 | 149 | Spec == Init /\ [][Next]_vars 150 | 151 | ----------------------------------------------------------------------------- 152 | (* The system is consistent if once a log is committed, that any logs 153 | * committed in greater ballots must extend it. *) 154 | Consistency == 155 | \A b1, b2 \in BallotNumbers: 156 | LET v1 == prop[b1].committed 157 | v2 == prop[b2].committed 158 | IN (b1 < b2 /\ prop[b1].hasCommitted /\ prop[b2].hasCommitted) => Prefix(v1, v2) 159 | 160 | (* A proposer should only ever extend its committed log. This should allow 161 | * reads to be served by the proposer. *) 162 | ProposerConsistency == 163 | \A b \in BallotNumbers: 164 | prop[b].hasCommitted => /\ prop'[b].hasCommitted 165 | /\ Prefix(prop[b].committed, prop'[b].committed) 166 | 167 | ============================================================================= 168 | -------------------------------------------------------------------------------- /LogPaxos/MC.cfg: -------------------------------------------------------------------------------- 1 | \* MV CONSTANT declarations 2 | CONSTANTS 3 | v1 = v1 4 | v2 = v2 5 | \* MV CONSTANT declarations 6 | CONSTANTS 7 | a1 = a1 8 | a2 = a2 9 | a3 = a3 10 | \* MV CONSTANT definitions 11 | CONSTANT 12 | Values <- const_1631037570427200000 13 | \* MV CONSTANT definitions 14 | CONSTANT 15 | Acceptors <- const_1631037570427201000 16 | \* SYMMETRY definition 17 | SYMMETRY symm_1631037570427202000 18 | \* CONSTANT definitions 19 | CONSTANT 20 | Quorums <- const_1631037570427203000 21 | \* CONSTANT definitions 22 | CONSTANT 23 | BallotNumbers <- const_1631037570427204000 24 | \* SPECIFICATION definition 25 | SPECIFICATION 26 | Spec 27 | \* INVARIANT definition 28 | INVARIANT 29 | Consistency 30 | \* PROPERTY definition 31 | PROPERTY 32 | prop_1631037570427206000 33 | \* Generated on Tue Sep 07 18:59:30 BST 2021 34 | -------------------------------------------------------------------------------- /LogPaxos/MC.tla: -------------------------------------------------------------------------------- 1 | ---- MODULE MC ---- 2 | EXTENDS LogPaxos, TLC 3 | 4 | \* MV CONSTANT declarations@modelParameterConstants 5 | CONSTANTS 6 | v1, v2 7 | ---- 8 | 9 | \* MV CONSTANT declarations@modelParameterConstants 10 | CONSTANTS 11 | a1, a2, a3 12 | ---- 13 | 14 | \* MV CONSTANT definitions Values 15 | const_1631037570427200000 == 16 | {v1, v2} 17 | ---- 18 | 19 | \* MV CONSTANT definitions Acceptors 20 | const_1631037570427201000 == 21 | {a1, a2, a3} 22 | ---- 23 | 24 | \* SYMMETRY definition 25 | symm_1631037570427202000 == 26 | Permutations(const_1631037570427200000) \union Permutations(const_1631037570427201000) 27 | ---- 28 | 29 | \* CONSTANT definitions @modelParameterConstants:0Quorums 30 | const_1631037570427203000 == 31 | {{a1,a2},{a2,a3},{a3,a1}} 32 | ---- 33 | 34 | \* CONSTANT definitions @modelParameterConstants:3BallotNumbers 35 | const_1631037570427204000 == 36 | {1,2} 37 | ---- 38 | 39 | \* PROPERTY definition @modelCorrectnessProperties:0 40 | prop_1631037570427206000 == 41 | [][ProposerConsistency]_prop 42 | ---- 43 | ============================================================================= 44 | \* Modification History 45 | \* Created Tue Sep 07 18:59:30 BST 2021 by cjjen 46 | -------------------------------------------------------------------------------- /LogPaxos/README.md: -------------------------------------------------------------------------------- 1 | # LogPaxos 2 | This is a TLA+ specification of the LogPaxos protocol described on [Decentralised Thoughts](TODO). 3 | 4 | It is adapted from the Paxos specification in this repository. 5 | 6 | ## Model Checking 7 | To model check it, either open the specification in the TLA+ Toolbox and add a new model, or run `tlc2 MC.tla`. This checks a three node cluster, over two ballot numbers and which decides one of two values. 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: test 3 | test: test-logpaxos test-paxos 4 | 5 | .PHONY: test-logpaxos 6 | test-logpaxos: 7 | tlc2 LogPaxos/MC.tla 8 | 9 | .PHONY: test-paxos 10 | test-paxos: 11 | tlc2 Paxos/MC.tla 12 | 13 | -------------------------------------------------------------------------------- /Paxos/MC.cfg: -------------------------------------------------------------------------------- 1 | \* MV CONSTANT declarations 2 | CONSTANTS 3 | v1 = v1 4 | v2 = v2 5 | \* MV CONSTANT declarations 6 | CONSTANTS 7 | a1 = a1 8 | a2 = a2 9 | a3 = a3 10 | \* MV CONSTANT definitions 11 | CONSTANT 12 | Values <- const_163118455825621000 13 | \* MV CONSTANT definitions 14 | CONSTANT 15 | Acceptors <- const_163118455825622000 16 | \* SYMMETRY definition 17 | SYMMETRY symm_163118455825623000 18 | \* CONSTANT definitions 19 | CONSTANT 20 | Quorums <- const_163118455825624000 21 | \* CONSTANT definitions 22 | CONSTANT 23 | BallotNumbers <- const_163118455825625000 24 | \* CONSTANT definition 25 | CONSTANT 26 | None = None 27 | \* SPECIFICATION definition 28 | SPECIFICATION 29 | Spec 30 | \* Generated on Thu Sep 09 11:49:18 BST 2021 -------------------------------------------------------------------------------- /Paxos/MC.tla: -------------------------------------------------------------------------------- 1 | ---- MODULE MC ---- 2 | EXTENDS Paxos, TLC 3 | 4 | \* MV CONSTANT declarations@modelParameterConstants 5 | CONSTANTS 6 | v1, v2 7 | ---- 8 | 9 | \* MV CONSTANT declarations@modelParameterConstants 10 | CONSTANTS 11 | a1, a2, a3 12 | ---- 13 | 14 | \* MV CONSTANT definitions Values 15 | const_163118455825621000 == 16 | {v1, v2} 17 | ---- 18 | 19 | \* MV CONSTANT definitions Acceptors 20 | const_163118455825622000 == 21 | {a1, a2, a3} 22 | ---- 23 | 24 | \* SYMMETRY definition 25 | symm_163118455825623000 == 26 | Permutations(const_163118455825621000) \union Permutations(const_163118455825622000) 27 | ---- 28 | 29 | \* CONSTANT definitions @modelParameterConstants:0Quorums 30 | const_163118455825624000 == 31 | {{a1,a2}, {a2,a3}, {a3,a1}} 32 | ---- 33 | 34 | \* CONSTANT definitions @modelParameterConstants:1BallotNumbers 35 | const_163118455825625000 == 36 | {1,2} 37 | ---- 38 | 39 | ============================================================================= 40 | \* Modification History 41 | \* Created Thu Sep 09 11:49:18 BST 2021 by cjen1 42 | -------------------------------------------------------------------------------- /Paxos/Paxos.tla: -------------------------------------------------------------------------------- 1 | ----------------------------- MODULE Paxos ----------------------------- 2 | 3 | (* Adapated from the excellent Paxos example at 4 | * https://github.com/tlaplus/Examples/blob/master/specifications/Paxos/Paxos.tla 5 | *) 6 | 7 | EXTENDS TLC, Integers, Sequences, FiniteSets 8 | 9 | CONSTANTS BallotNumbers, Acceptors, Values, Quorums 10 | 11 | VARIABLES msgs, 12 | acc, 13 | prop 14 | 15 | Max(Leq(_,_), s) == CHOOSE v \in s: \A v1 \in s: Leq(v1, v) 16 | Min(Leq(_,_), s) == CHOOSE v \in s: \A v1 \in s: Leq(v, v1) 17 | 18 | None == CHOOSE v : v \notin Values 19 | 20 | (* The comparison function used by acceptors to update acc[a].maxBal 21 | * It is also used by the ValueSelect function to choose the 'maximum' ballot 22 | * And by the proposer to commit the 'minimum' of the quorum responses. *) 23 | BallotLeq(a, b) == 24 | \/ a.bal < b.bal 25 | \/ a.bal = b.bal /\ a.val = b.val 26 | 27 | ----------------------------------------------------------------------------- 28 | 29 | (* Initial state of the the system. *) 30 | Init == /\ msgs = {} 31 | /\ acc = [a \in Acceptors |-> [maxBalNum |-> -1, maxBal |-> [bal |-> -1, val |-> None]]] 32 | /\ prop = [b \in BallotNumbers |-> [val |-> None, valSelect |-> FALSE, committed |-> None, hasCommitted |-> FALSE]] 33 | 34 | Send(m) == msgs' = msgs \cup {m} 35 | 36 | (* Phase 1a: A proposer for ballot number b sends a 1a message. *) 37 | Phase1a(b) == 38 | /\ ~\E m \in msgs: m.type = "1a" /\ m.balNum = b 39 | /\ Send ([type |-> "1a", balNum |-> b]) 40 | /\ UNCHANGED << acc, prop >> 41 | 42 | (* Phase 1b: When an acceptor receives a 1a message, if that message is from 43 | * a higher ballot number than the highest one heard of, then update the 44 | * highest ballot number and respond with the stored highest ballot. *) 45 | Phase1b(a) == 46 | \E m \in msgs : 47 | /\ m.type = "1a" 48 | /\ m.balNum > acc[a].maxBalNum 49 | /\ acc' = [acc EXCEPT ![a] = [acc[a] EXCEPT !.maxBalNum = m.balNum]] 50 | /\ Send([type |-> "1b", balNum |-> m.balNum, acc |-> a, maxBal |-> acc[a].maxBal]) 51 | /\ UNCHANGED << prop >> 52 | 53 | (* ValueSelect: When a proposer receives a quorum of phase 1b responses, it sets 54 | * valSelect and its stored value to the value of the maximum ballot within 55 | * those responses. This enables Phase2a. *) 56 | ValueSelect(b) == 57 | /\ ~ prop[b].valSelect 58 | /\ \E Q \in Quorums, S \in SUBSET {m \in msgs: (m.type = "1b") /\ (m.balNum = b)}: 59 | /\ \A a \in Q: \E m \in S: m.acc = a 60 | /\ LET maxBal == Max(BallotLeq, {m.maxBal: m \in S}) 61 | IN /\ prop' = [prop EXCEPT ![b] = 62 | [prop[b] EXCEPT !.val = maxBal.val, !.valSelect = TRUE]] 63 | /\ UNCHANGED << acc, msgs >> 64 | 65 | (* Phase2a: A proposer proposes a value. If its stored val is none, it chooses 66 | * a new value. It updates its stored value and sends the ballot. This ensures 67 | * that it will only propose one value. *) 68 | Phase2a(b) == 69 | /\ prop[b].valSelect 70 | /\ \E v \in IF prop[b].val = None THEN Values ELSE {prop[b].val}: 71 | LET bal == [bal |-> b, val |-> v] 72 | IN /\ Send([type |-> "2a", bal |-> bal]) 73 | /\ prop' = [prop EXCEPT ![b] = [prop[b] EXCEPT !.val = bal.val]] 74 | /\ UNCHANGED << acc >> 75 | 76 | (* Phase2b: An acceptor upon receiving a phase 2a message from ballot greater 77 | * than or equal to its own, and which has a greater than or equal ballot than 78 | * its stored one updates its stored and responds with a vote for the new ballot. *) 79 | Phase2b(a) == 80 | /\ \E m \in msgs : 81 | /\ m.type = "2a" 82 | /\ m.bal.bal >= acc[a].maxBalNum 83 | /\ BallotLeq(acc[a].maxBal, m.bal) 84 | /\ Send([type |-> "2b", acc |-> a, bal |-> m.bal]) 85 | /\ acc' = [acc EXCEPT ![a] = [maxBalNum |-> m.bal.bal, maxBal |-> m.bal]] 86 | /\ UNCHANGED << prop >> 87 | 88 | (* Commit: When a proposer receives a quorum of votes for a value it commits 89 | * that value. Since all the ballots are equal (Min(BallotLeq,...)).val selects 90 | * the value. *) 91 | Commit(b) == 92 | \E Q \in Quorums: 93 | \E S \in SUBSET {m \in msgs: /\ m.type = "2b" 94 | /\ m.bal.bal = b 95 | /\ m.acc \in Q}: 96 | /\ \A a \in Q: \E m \in S: m.acc = a 97 | /\ LET val == (Min(BallotLeq, {m.bal: m \in S})).val 98 | IN /\ \A m \in S: \A m1 \in S \ {m}: m.acc /= m1.acc 99 | /\ prop' = [prop EXCEPT ![b] = [prop[b] EXCEPT !.committed = val, !.hasCommitted = TRUE]] 100 | /\ UNCHANGED << msgs, acc >> 101 | 102 | (* Next: a disjunction of all possible actions. Since each action asserts that 103 | * other states are unchanged only one is true at a time. *) 104 | Next == \/ \E p \in BallotNumbers : Phase1a(p) \/ ValueSelect(p) \/ Phase2a(p) \/ Commit(p) 105 | \/ \E a \in Acceptors : Phase1b(a) \/ Phase2b(a) 106 | 107 | ----------------------------------------------------------------------------- 108 | (* Type checking invariant *) 109 | 110 | PossibleValues == Values \cup {None} 111 | 112 | PossibleBallots == [bal : BallotNumbers \cup {-1}, val : PossibleValues] 113 | 114 | Messages == [type : {"1a"}, balNum : BallotNumbers] 115 | \cup [type : {"1b"}, acc : Acceptors, balNum : BallotNumbers, maxBal : PossibleBallots] 116 | \cup [type : {"2a"}, bal : PossibleBallots] 117 | \cup [type : {"2b"}, acc : Acceptors, bal : PossibleBallots] 118 | 119 | ProposerState == [val : PossibleValues, 120 | valSelect : {TRUE, FALSE}, 121 | committed : PossibleValues, 122 | hasCommitted : {TRUE,FALSE}] 123 | 124 | AcceptorState == [maxBalNum : BallotNumbers \cup {-1}, maxBal : PossibleBallots] 125 | 126 | TypeInvariant == /\ msgs \in SUBSET Messages 127 | /\ acc \in [Acceptors -> AcceptorState] 128 | /\ prop \in [BallotNumbers -> ProposerState] 129 | 130 | vars == <> 131 | 132 | ----------------------------------------------------------------------------- 133 | 134 | Spec == Init /\ [][Next]_vars 135 | 136 | ----------------------------------------------------------------------------- 137 | (* The system is consistent if once a value is committed, any values committed 138 | * in greater ballots equal it. *) 139 | Consistency == 140 | \A b1, b2 \in BallotNumbers: 141 | LET v1 == prop[b1].committed 142 | v2 == prop[b2].committed 143 | IN (b1 < b2 /\ prop[b1].hasCommitted /\ prop[b2].hasCommitted) => v1 = v2 144 | 145 | (* Once the proposer commits a value it cannot commit any other values *) 146 | ProposerConsistency == 147 | \A b \in BallotNumbers: 148 | prop[b].hasCommitted => /\ prop'[b].hasCommitted 149 | /\ prop[b].committed = prop'[b].committed 150 | ============================================================================= 151 | -------------------------------------------------------------------------------- /Paxos/README.md: -------------------------------------------------------------------------------- 1 | # Paxos 2 | This is a Paxos specification adapted and abstracted from the [TLA+ example](https://github.com/tlaplus/Examples/blob/master/specifications/Paxos/Paxos.tla) by Lamport et. al. 3 | 4 | It abstracts away certain parts of the protocol in order to highlight similarities with LogPaxos. 5 | 6 | ## Model Checking 7 | To model check it, either open the specification in the TLA+ Toolbox and add a new model, or run `tlc2 MC.tla`. This checks a three node cluster, over two ballot numbers and which decides one of two values. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TLA+ Consensus Specifications 2 | ![example workflow](https://github.com/cjen1/tlaplusstuff/actions/workflows/test.yml/badge.svg) 3 | 4 | A repository with TLA+ specifications for various consensus protocols. 5 | 6 | # Current Projects 7 | - [x] Adapt Paxos to produce LogPaxos 8 | -------------------------------------------------------------------------------- /conspire/Abstract.tla: -------------------------------------------------------------------------------- 1 | ---- MODULE Abstract ---- 2 | 3 | EXTENDS FiniteSets, Integers 4 | 5 | \* @typeAlias: nid = Str; 6 | \* @typeAlias: value = Str; 7 | \* @typeAlias: term = Int; 8 | \* @typeAlias: reqMsg = {term : $term, val : $value}; 9 | \* @typeAlias: repMsg = {src : $nid, state : $state}; 10 | \* @typeAlias: state = {term : $term, vterm : $term, vval : $value}; 11 | CONSPIRE_BASE_ALIAS == TRUE 12 | 13 | CONSTANTS 14 | \* @type: Set($nid); 15 | Nodes, 16 | \* @type: Set($value); 17 | Values, 18 | \* @type: $value; 19 | None 20 | 21 | VARIABLES 22 | \* @type: $nid -> $state; 23 | local_states, 24 | \* @type: Set($reqMsg); 25 | proposals, 26 | \* @type: Set($repMsg); 27 | states 28 | 29 | ASSUME ~None \in Values 30 | 31 | Quorums == {Q \in SUBSET Nodes: Cardinality(Q) = (2 * Cardinality(Nodes)) \div 3 + 1} 32 | 33 | \* @type: Set($term); 34 | UsedTerms == 35 | UNION ({{local_states[n].term, local_states[n].vterm}: n \in DOMAIN local_states} \union 36 | {{m.term}: m \in proposals} \union 37 | {{m.state.term, m.state.vterm}: m \in states}) 38 | 39 | \*==================== 40 | \* Main functions 41 | \*==================== 42 | 43 | Init == 44 | /\ local_states = [n \in Nodes |-> [term |-> 0, vterm |-> -1, vval |-> None]] 45 | /\ proposals = {[term |-> 0, val |-> v] : v \in Values} 46 | /\ states = {} 47 | 48 | Vote(n) == 49 | \E m \in proposals: 50 | /\ m.term >= local_states[n].term 51 | /\ \/ m.term > local_states[n].vterm 52 | \/ m.term = local_states[n].vterm /\ local_states[n].vval = None 53 | /\ local_states' = [local_states EXCEPT ![n] = 54 | [term |-> m.term, vterm |-> m.term, vval |-> m.val]] 55 | 56 | IncrTerm(n) == local_states' = [local_states EXCEPT ![n] = 57 | [local_states[n] EXCEPT !.term = local_states[n].term + 1]] 58 | 59 | BroadcastState(n) == 60 | LET msg == [src |-> n, state |-> local_states[n]] IN 61 | /\ ~ msg \in states 62 | /\ states' = states \union {msg} 63 | 64 | Propose == 65 | \E t \in UsedTerms: 66 | \E Qr \in Quorums: 67 | \* Valid votes 68 | \E S \in SUBSET {m \in states: /\ m.state.term = t 69 | /\ m.src \in Qr}: 70 | LET max_vterm == (CHOOSE m \in S: \A m1 \in S: m1.state.vterm <= m.state.vterm).state.vterm 71 | max_vterm_msgs == {m \in S: m.state.vterm = max_vterm} 72 | \* @type: ($value) => Bool; 73 | PossiblyCommittable(v) == 74 | \E Qw \in Quorums: 75 | \A n \in Qw: 76 | \* Voted for v 77 | \/ \E m \in max_vterm_msgs: 78 | /\ m.src = n 79 | /\ m.state.vval = v 80 | \* Did not vote 81 | \/ ~\E m \in S: m.src = n 82 | max_vterm_vals == {m.state.vval : m \in max_vterm_msgs} 83 | ChoosableVals == {v \in max_vterm_vals: PossiblyCommittable(v)} 84 | IN 85 | /\ \A n \in Qr: \E m \in S: m.src = n 86 | /\ \E v \in Values: 87 | LET msg == [term |-> t, val |-> v] IN 88 | \* Inductive base case 89 | /\ \A lb \in ChoosableVals: lb = None \/ lb = v 90 | \* Inductive case 91 | /\ \E lb \in max_vterm_vals: lb = None \/ lb = v 92 | /\ ~ msg \in proposals 93 | /\ proposals' = proposals \union {msg} 94 | 95 | Next == 96 | \/ \E n \in Nodes: \/ /\ \/ Vote(n) 97 | \/ IncrTerm(n) 98 | /\ UNCHANGED << states, proposals >> 99 | \/ /\ BroadcastState(n) 100 | /\ UNCHANGED << local_states, proposals>> 101 | \/ Propose /\ UNCHANGED << local_states, states >> 102 | 103 | Spec == Init /\ [][Next]_<> 104 | 105 | \*==================== 106 | \* Invariants 107 | \*==================== 108 | 109 | \* @type: ($term, $value) => Bool; 110 | Committable(t,v) == 111 | \E Q \in Quorums: 112 | \A n \in Q: 113 | \E m \in states: 114 | /\ m.src = n 115 | /\ m.state.vterm = t 116 | /\ m.state.vval = v 117 | 118 | Safety == 119 | LET CanCommit == {v \in Values: \E t \in UsedTerms: Committable(t,v)} IN 120 | \A v1, v2 \in CanCommit: v1 = v2 121 | 122 | ==== 123 | -------------------------------------------------------------------------------- /conspire/AbstractLog.tla: -------------------------------------------------------------------------------- 1 | ---- MODULE AbstractLog ---- 2 | 3 | EXTENDS FiniteSets, Integers, Sequences 4 | 5 | \* @typeAlias: nid = Str; 6 | \* @typeAlias: value = Str; 7 | \* @typeAlias: term = Int; 8 | \* @typeAlias: reqMsg = {term : $term, val : Seq($value)}; 9 | \* @typeAlias: repMsg = {src : $nid, state : $state}; 10 | \* @typeAlias: state = {term : $term, vterm : $term, vval : Seq($value)}; 11 | CONSPIRE_LOG_ALIAS == TRUE 12 | 13 | CONSTANTS 14 | \* @type: Set($nid); 15 | Nodes, 16 | \* @type: Set($value); 17 | Values 18 | 19 | VARIABLES 20 | \* @type: $nid -> $state; 21 | local_states, 22 | \* @type: Set($reqMsg); 23 | req_msgs, 24 | \* @type: Set($repMsg); 25 | rep_msgs 26 | 27 | Quorums == {Q \in SUBSET Nodes: Cardinality(Q) = (2 * Cardinality(Nodes)) \div 3 + 1} 28 | 29 | \* @type: ($nid, $state) => Bool; 30 | Send(src, s) == 31 | LET msg == [src |-> src, state |-> s] IN 32 | /\ ~ msg \in rep_msgs 33 | /\ rep_msgs' = rep_msgs \union {msg} 34 | 35 | \* @type: Set($term); 36 | UsedTerms == 37 | UNION ({{local_states[n].term, local_states[n].vterm}: n \in DOMAIN local_states} \union 38 | {{m.term}: m \in req_msgs} \union 39 | {{m.state.term, m.state.vterm}: m \in rep_msgs}) 40 | 41 | \* @type: (Seq(x), Seq(x)) => Bool; 42 | Leq(a,b) == 43 | /\ Len(a) <= Len(b) 44 | /\ \A k \in DOMAIN a: a[k] = b[k] 45 | 46 | \* @type: Seq(x) => Set(Seq(x)); 47 | Prefixes(S) == 48 | {SubSeq(S, 1, l): l \in 0..Len(S)} 49 | 50 | \*==================== 51 | \* Main functions 52 | \*==================== 53 | 54 | Init == 55 | /\ local_states = [n \in Nodes |-> [term |-> 0, vterm |-> -1, vval |-> <<>>]] 56 | /\ req_msgs = {[term |-> 0, val |-> <>] : v \in Values} 57 | /\ rep_msgs = {} 58 | 59 | Reply(n) == 60 | \E m \in req_msgs: 61 | /\ m.term >= local_states[n].term 62 | /\ \/ m.term > local_states[n].vterm 63 | \/ m.term = local_states[n].vterm /\ Leq(local_states[n].vval, m.val) 64 | /\ LET new_state == [term |-> m.term, vterm |-> m.term, vval |-> m.val] IN 65 | /\ local_states' = [local_states EXCEPT ![n] = new_state] 66 | /\ Send(n, new_state) 67 | /\ UNCHANGED req_msgs 68 | 69 | IncrTerm(n) == 70 | LET new_state == [local_states[n] EXCEPT !.term = local_states[n].term + 1] IN 71 | /\ new_state.term < 10 72 | /\ local_states' = [local_states EXCEPT ![n] = new_state] 73 | /\ Send(n, new_state) 74 | /\ UNCHANGED req_msgs 75 | 76 | Propose == 77 | \E t \in UsedTerms: 78 | \E Qr \in Quorums: 79 | \* Valid votes 80 | \E S \in SUBSET {m \in rep_msgs: /\ m.state.term = t 81 | /\ m.src \in Qr}: 82 | LET max_vterm == (CHOOSE m \in S: \A m1 \in S: m1.state.vterm <= m.state.vterm).state.vterm 83 | max_vterm_msgs == {m \in S: m.state.vterm = max_vterm} 84 | \* @type: (Seq($value)) => Bool; 85 | PossiblyCommittable(v) == 86 | \E Qw \in Quorums: 87 | \A n \in Qw: 88 | \* Voted for v 89 | \/ \E m \in max_vterm_msgs: 90 | /\ m.src = n 91 | /\ Leq(v, m.state.vval) 92 | \* Did not vote 93 | \/ ~\E m \in S: m.src = n 94 | max_vterm_vals == {m.state.vval : m \in max_vterm_msgs} 95 | ChoosableVals == {}\*{v \in UNION {Prefixes(v) : v \in max_vterm_vals}: PossiblyCommittable(v)} 96 | IN 97 | /\ \A n \in Qr: \E m \in S: m.src = n 98 | /\ \E lb \in max_vterm_vals: 99 | \* Inductive base case 100 | /\ \A olb \in ChoosableVals: Leq(olb, lb) 101 | \* Inductive case 102 | /\ \E olb \in max_vterm_vals: Leq(olb, lb) 103 | /\ \E prop_v \in Values: 104 | LET v == lb \o <> 105 | msg == [term |-> t, val |-> v] 106 | IN 107 | /\ ~ msg \in req_msgs 108 | /\ req_msgs' = req_msgs \union {msg} 109 | /\ UNCHANGED << local_states, rep_msgs >> 110 | 111 | Next == 112 | \/ Propose 113 | \/ \E n \in Nodes: Reply(n) \/ IncrTerm(n) 114 | 115 | Spec == Init /\ [][Next]_<> 116 | 117 | \*==================== 118 | \* Invariants 119 | \*==================== 120 | 121 | \* @type: ($term, Seq($value)) => Bool; 122 | Committable(t,v) == 123 | \E Q \in Quorums: 124 | \A n \in Q: 125 | \E m \in rep_msgs: 126 | /\ m.src = n 127 | /\ m.state.vterm = t 128 | /\ Leq(v, m.state.vval) 129 | 130 | \* @type: Set(Seq($value)); 131 | UsedValues == {m.val: m \in req_msgs} \union {m.state.vval: m \in rep_msgs} \union {local_states[n].vval : n \in DOMAIN local_states} 132 | 133 | 134 | Safety == 135 | LET CanCommit == {v \in UsedValues: \E t \in UsedTerms: Committable(t,v)} IN 136 | \A v1, v2 \in CanCommit: Leq(v1, v2) \/ Leq(v2, v1) 137 | 138 | ==== 139 | -------------------------------------------------------------------------------- /conspire/PNoC.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION 2 | Spec 3 | 4 | CONSTANTS 5 | RIDs = {r1, r2, r3, r4, r5} 6 | CliVals = {v1, v2, v3, v4} 7 | 8 | INVARIANTS 9 | InvSafety 10 | InvOneVotePerTerm 11 | InvTermLimit 12 | 13 | SYMMETRY 14 | Symmetry 15 | -------------------------------------------------------------------------------- /conspire/PNoC.tla: -------------------------------------------------------------------------------- 1 | ---- MODULE PNoC ---- 2 | 3 | EXTENDS Apalache, TLC, FiniteSets, Integers 4 | 5 | (* 6 | @typeAlias: value = Str; 7 | @typeAlias: vvalue = Set($value); 8 | @typeAlias: rid = Str; 9 | @typeAlias: vote = {term : Int, value: $vvalue}; 10 | *) 11 | PNoCAliases == TRUE 12 | 13 | CONSTANTS 14 | \* @type: Set($rid); 15 | RIDs, 16 | \* @type: Set($value); 17 | CliVals 18 | 19 | VARIABLES 20 | \* @type: $rid -> $vote; 21 | Replicas, 22 | \* @type: Set({src: $rid, vote: $vote}); 23 | Votes, 24 | \* @type: Set($vote); 25 | Proposals 26 | 27 | vars == << Proposals, Replicas, Votes >> 28 | 29 | MaxCommitQuorums == {s \in SUBSET RIDs: Cardinality(s) >= (Cardinality(RIDs) * 3) \div 4} 30 | CommitQuorums == {q \in MaxCommitQuorums: ~\E s \in MaxCommitQuorums: s \subseteq q /\ s /= q} 31 | ProposalQuorums == {s \in SUBSET RIDs: /\ \A q \in CommitQuorums: s \intersect q /= {} 32 | /\ Cardinality(s) >= (Cardinality(RIDs) \div 2) + 1} 33 | 34 | Init == 35 | /\ Proposals := {[value |-> {v}, term |-> 0] : v \in CliVals} 36 | /\ Votes := {} 37 | /\ Replicas := [r \in RIDs |-> [value |-> {}, term |-> -1]] 38 | 39 | Vote(r) == 40 | \E p \in Proposals: 41 | /\ p.term > Replicas[r].term 42 | /\ Replicas' = [Replicas EXCEPT ![r] = p] 43 | /\ Votes' = Votes \union {[src |-> r, vote |-> p]} 44 | /\ UNCHANGED << Proposals >> 45 | 46 | Terms == {p.term: p \in Proposals} 47 | Values == { p.value: p \in Proposals} 48 | 49 | ProposeVals == 50 | \E t \in Terms: 51 | \* large number of possible subsets result in the same proposal => reduce explicit states here 52 | LET term_votes == {v \in Votes: v.vote.term = t} 53 | vote_rel == [r \in {v.src : v \in term_votes} |-> CHOOSE v \in term_votes: v.src = r] 54 | usable_qs == {q \in ProposalQuorums: q \ DOMAIN vote_rel = {}} 55 | AllPNoC(q) == 56 | LET qvotes == {v \in Votes: v.src \in q} IN 57 | \A v \in {v.vote.value: v \in qvotes}: 58 | \* Not committable 59 | \A cq \in CommitQuorums: 60 | \E r \in cq: r \in DOMAIN vote_rel /\ vote_rel[r].vote.value /= v 61 | all_pnoc_qs == {q \in usable_qs: AllPNoC(q)} 62 | propable_vs == {UNION {vote_rel[r].vote.value : r \in q}: q \in all_pnoc_qs} 63 | IN \E v \in propable_vs: 64 | LET new_prop == [term |-> t + 1, value |-> v] IN 65 | /\ new_prop \notin Proposals 66 | /\ Proposals' = Proposals \union {new_prop} 67 | /\ UNCHANGED << Replicas, Votes >> 68 | 69 | Next == 70 | \/ ProposeVals 71 | \/ \E r \in RIDs: Vote(r) 72 | 73 | Spec == Init /\ [][Next]_vars 74 | 75 | Symmetry == Permutations(RIDs) \union Permutations(CliVals) 76 | 77 | \* There is at most one vote per term for each replica 78 | InvOneVotePerTerm == 79 | \A t \in Terms: 80 | LET term_votes == {v \in Votes: v.vote.term = t} IN 81 | Cardinality(term_votes) = Cardinality({v.src : v \in term_votes}) 82 | 83 | IsCommittable(v) == 84 | \E t \in Terms: 85 | \E q \in CommitQuorums: 86 | {[src |-> r, vote |-> [term |-> t, value |-> v]] : r \in q} \subseteq Votes 87 | 88 | Committable == {v \in Values: IsCommittable(v)} 89 | 90 | InvSafety == Cardinality(Committable) <= 1 91 | 92 | InvCommit == \A v \in Committable: Cardinality(v) = 3 93 | InvTermLimit == \A p \in Proposals: p.term < Cardinality(Values) + 1 94 | 95 | ==== 96 | -------------------------------------------------------------------------------- /conspire/models/AbstractLogMC.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION 2 | Spec 3 | 4 | CONSTANTS 5 | Nodes = {a1, a2, a3, a4} 6 | Values = {x, y, z, a} 7 | None = None 8 | 9 | INVARIANTS 10 | Safety 11 | -------------------------------------------------------------------------------- /conspire/models/AbstractLogMC.tla: -------------------------------------------------------------------------------- 1 | ---- MODULE AbstractLogMC ---- 2 | 3 | EXTENDS AbstractLog 4 | 5 | CInit == 6 | /\ Nodes = {"a1", "a2", "a3", "a4"} 7 | /\ Values = {"x", "y", "z"} 8 | 9 | Inv == 10 | /\ Safety 11 | 12 | ==== 13 | -------------------------------------------------------------------------------- /conspire/models/AbstractMC.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION 2 | Spec 3 | 4 | CONSTANTS 5 | Nodes = {a1, a2, a3, a4} 6 | Values = {x, y, z, a} 7 | None = None 8 | 9 | INVARIANTS 10 | Safety 11 | -------------------------------------------------------------------------------- /conspire/models/AbstractMC.tla: -------------------------------------------------------------------------------- 1 | ---- MODULE AbstractMC ---- 2 | 3 | EXTENDS Apalache, Abstract 4 | 5 | CInit == 6 | /\ Nodes := {"a1", "a2", "a3", "a4"} 7 | /\ Values := {"x", "y", "z"} 8 | /\ None := "None" 9 | 10 | Inv == 11 | /\ Safety 12 | 13 | ==== 14 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "apalache-release": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1723211064, 7 | "narHash": "sha256-DSRQsUQfGYoW/sy8Trjd3vM+7ZR8wf/QHtx98hBmNOA=", 8 | "type": "tarball", 9 | "url": "https://github.com/informalsystems/apalache/releases/download/v0.44.11/apalache.zip" 10 | }, 11 | "original": { 12 | "type": "tarball", 13 | "url": "https://github.com/informalsystems/apalache/releases/download/v0.44.11/apalache.zip" 14 | } 15 | }, 16 | "flake-utils": { 17 | "inputs": { 18 | "systems": "systems" 19 | }, 20 | "locked": { 21 | "lastModified": 1692799911, 22 | "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=", 23 | "owner": "numtide", 24 | "repo": "flake-utils", 25 | "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44", 26 | "type": "github" 27 | }, 28 | "original": { 29 | "owner": "numtide", 30 | "repo": "flake-utils", 31 | "type": "github" 32 | } 33 | }, 34 | "nixpkgs": { 35 | "locked": { 36 | "lastModified": 1693401179, 37 | "narHash": "sha256-iMRX9+zL6XpZV/1HMt3DZCqcezrW79arCeW1RujlJ60=", 38 | "owner": "nixos", 39 | "repo": "nixpkgs", 40 | "rev": "d1eaf1acfce382f14d26d20e0a9342884f3127b0", 41 | "type": "github" 42 | }, 43 | "original": { 44 | "owner": "nixos", 45 | "repo": "nixpkgs", 46 | "type": "github" 47 | } 48 | }, 49 | "root": { 50 | "inputs": { 51 | "apalache-release": "apalache-release", 52 | "flake-utils": "flake-utils", 53 | "nixpkgs": "nixpkgs" 54 | } 55 | }, 56 | "systems": { 57 | "locked": { 58 | "lastModified": 1681028828, 59 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 60 | "owner": "nix-systems", 61 | "repo": "default", 62 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 63 | "type": "github" 64 | }, 65 | "original": { 66 | "owner": "nix-systems", 67 | "repo": "default", 68 | "type": "github" 69 | } 70 | } 71 | }, 72 | "root": "root", 73 | "version": 7 74 | } 75 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs"; 4 | flake-utils.url = "github:numtide/flake-utils"; 5 | 6 | apalache-release = { 7 | url = "https://github.com/informalsystems/apalache/releases/download/v0.44.11/apalache.zip"; 8 | flake = false; 9 | type = "tarball"; 10 | }; 11 | }; 12 | 13 | outputs = {self, nixpkgs, flake-utils, ... }@inputs: 14 | flake-utils.lib.eachDefaultSystem (system: 15 | let 16 | pkgs = import nixpkgs { 17 | inherit system; 18 | }; 19 | apalache = pkgs.stdenv.mkDerivation { 20 | name = "apalache"; 21 | src = inputs.apalache-release; 22 | 23 | buildInputs = [pkgs.makeWrapper]; 24 | 25 | installPhase = '' 26 | mkdir -p $out 27 | cp -r lib $out/lib 28 | 29 | mkdir -p $out/bin 30 | cat > $out/bin/apalache-mc <<- EOM 31 | #!${pkgs.bash}/bin/bash 32 | exec ${pkgs.jre}/bin/java -Xmx100G -jar "$out/lib/apalache.jar" "\$@" 33 | EOM 34 | chmod +x $out/bin/apalache-mc 35 | ''; 36 | 37 | postFixup = '' 38 | wrapProgram $out/bin/apalache-mc \ 39 | --set PATH ${pkgs.lib.makeBinPath [ 40 | pkgs.gcc12 41 | pkgs.z3 42 | ]} 43 | ''; 44 | }; 45 | in { 46 | devShell = pkgs.mkShell { 47 | buildInputs = [ 48 | pkgs.tlaplus 49 | apalache 50 | ]; 51 | }; 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /msfp/Nezha.tla: -------------------------------------------------------------------------------- 1 | ---- MODULE Nezha ---- 2 | 3 | EXTENDS Integers, FiniteSets, Sequences 4 | 5 | CONSTANTS 6 | RIDs, 7 | BaseValues 8 | 9 | VARIABLE quorums 10 | 11 | InitQuorum == 12 | quorums \in 13 | {Q \in SUBSET SUBSET RIDs: 14 | /\ Cardinality(Q) > 0 15 | /\ \A q \in Q: Cardinality(q) > Cardinality(RIDs) \div 2 16 | /\ \A q1, q2 \in Q: q1 \cap q2 /= {} 17 | } 18 | 19 | VARIABLE coordinator_id 20 | 21 | InitCoordinatorID == coordinator_id \in RIDs 22 | 23 | VARIABLES 24 | replica_states, 25 | proposals, 26 | state_updates 27 | 28 | a \sqsubseteq b == 29 | /\ a.primed <= b.primed 30 | /\ Len(a.v) <= Len(b.v) 31 | \* primed sections match 32 | /\ \A i \in DOMAIN a.v: 33 | i <= a.primed => a[i] = b[i] 34 | \* unprimed sections match 35 | /\ \A i \in DOMAIN b.v: 36 | i > b.primed => \/ i <= Len(a.v) 37 | \/ a[i] = b[i] 38 | 39 | Propose(rid, s) == 40 | /\ replica_states[rid] \sqsubseteq s 41 | /\ replica_states[rid] /= s 42 | /\ replica_states' = [replica_states EXCEPT ![rid] = s] 43 | /\ state_updates' = state_updates \cup {<>} 44 | 45 | CoordinatorPropose(rid, v) == 46 | LET new_state == [v |-> replica_states[rid].v \o << v >>, primed |-> replica_states[rid].primed + 1] IN 47 | /\ rid = coordinator_id 48 | /\ Propose(rid, new_state) 49 | 50 | ReplicaPropose(rid, v) == 51 | LET new_state == [v |-> replica_states[rid].v \o << v >>, primed |-> replica_states[rid].primed] IN 52 | /\ rid /= coordinator_id 53 | /\ Propose(rid, new_state) 54 | 55 | Merge(rid) == 56 | \E <> \in state_updates: 57 | LET merged_log == 58 | [ i \in DOMAIN u.v \cup DOMAIN replica_states[rid].v |-> 59 | IF i <= u.primed 60 | THEN u[i] 61 | ELSE replica_states[rid].v] 62 | IN 63 | \* Required to more precisely follow Nezha 64 | /\ r = coordinator_id 65 | /\ replica_states[rid].primed < u.primed 66 | /\ Propose(rid, [primed |-> u.primed, v |-> merged_log]) 67 | 68 | Committable(v) == 69 | LET votes == {<> \in state_updates: v \sqsubseteq s} 70 | voting_rids == {r : <> \in votes} 71 | IN \E q \in quorums: q \subseteq voting_rids 72 | 73 | 74 | ==== 75 | -------------------------------------------------------------------------------- /msfp/PrimedValue.tla: -------------------------------------------------------------------------------- 1 | ---- MODULE PrimedValue ---- 2 | 3 | EXTENDS FiniteSets, Integers 4 | 5 | CONSTANTS 6 | BaseValues 7 | 8 | BaseLeq(a,b) == a < b 9 | 10 | 11 | 12 | ==== 13 | -------------------------------------------------------------------------------- /msfp/SimpleValue.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | CONSTANTS 3 | Null = Null 4 | RIDs = {r1,r2,r3,r4} 5 | BaseValues = {x,y,z} 6 | INVARIANT InvCommit 7 | 8 | -------------------------------------------------------------------------------- /msfp/SimpleValue.tla: -------------------------------------------------------------------------------- 1 | ---- MODULE SimpleValue ---- 2 | 3 | EXTENDS Integers, FiniteSets, TLC 4 | 5 | CONSTANTS 6 | RIDs, 7 | BaseValues 8 | 9 | VARIABLE 10 | quorums 11 | 12 | InitQuorum == 13 | quorums \in 14 | {Q \in SUBSET SUBSET RIDs: 15 | /\ Cardinality(Q) > 0 16 | /\ \A q \in Q: Cardinality(q) > Cardinality(RIDs) \div 2 17 | /\ \A q1, q2 \in Q: q1 \cap q2 /= {} 18 | } 19 | 20 | VARIABLES 21 | replica_states, 22 | proposals, 23 | state_updates 24 | 25 | Null == CHOOSE v : v \notin BaseValues 26 | a \sqsubseteq b == a = Null \/ a = b 27 | 28 | Values == BaseValues \cup {Null} 29 | 30 | InitReplica == 31 | replica_states = [r \in RIDs |-> Null] 32 | 33 | Propose(v) == 34 | /\ v \notin proposals 35 | /\ proposals' = proposals \cup {v} 36 | 37 | ReplicaVote(rid) == 38 | \E p \in proposals: 39 | /\ replica_states[rid] \sqsubseteq p /\ replica_states[rid] /= p 40 | /\ replica_states' = [replica_states EXCEPT ![rid] = p] 41 | /\ state_updates' = state_updates \cup {<< rid, p >>} 42 | 43 | Next == 44 | /\ UNCHANGED << quorums >> 45 | /\ \/ \E v \in BaseValues: Propose(v) /\ UNCHANGED <> 46 | \/ \E r \in RIDs: ReplicaVote(r) /\ UNCHANGED <> 47 | 48 | Init == 49 | /\ InitQuorum 50 | /\ InitReplica 51 | /\ proposals = {} 52 | /\ state_updates = {} 53 | 54 | Spec == Init /\ [][Next]_<> 55 | 56 | Committable(v) == 57 | LET votes == {<> \in state_updates: v \sqsubseteq s} 58 | voting_rids == {r : <> \in votes} 59 | IN \E q \in quorums: q \subseteq voting_rids 60 | 61 | InvCommit == 62 | Cardinality({v \in BaseValues : Committable(v)}) <= 1 63 | 64 | ==== 65 | -------------------------------------------------------------------------------- /u2pc/Apalache.tla: -------------------------------------------------------------------------------- 1 | --------------------------- MODULE Apalache ----------------------------------- 2 | (* 3 | * This is a standard module for use with the Apalache model checker. 4 | * The meaning of the operators is explained in the comments. 5 | * Many of the operators serve as additional annotations of their arguments. 6 | * As we like to preserve compatibility with TLC and TLAPS, we define the 7 | * operator bodies by erasure. The actual interpretation of the operators is 8 | * encoded inside Apalache. For the moment, these operators are mirrored in 9 | * the class at.forsyte.apalache.tla.lir.oper.ApalacheOper. 10 | * 11 | * Igor Konnov, Jure Kukovec, Informal Systems 2020-2022 12 | *) 13 | 14 | (** 15 | * An assignment of an expression e to a state variable x. Typically, one 16 | * uses the non-primed version of x in the initializing predicate Init and 17 | * the primed version of x (that is, x') in the transition predicate Next. 18 | * Although TLA+ does not have a concept of a variable assignment, we find 19 | * this concept extremely useful for symbolic model checking. In pure TLA+, 20 | * one would simply write x = e, or x \in {e}. 21 | * 22 | * Apalache automatically converts some expressions of the form 23 | * x = e or x \in {e} into assignments. However, if you like to annotate 24 | * assignments by hand, you can use this operator. 25 | * 26 | * For a further discussion on that matter, see: 27 | * https://github.com/informalsystems/apalache/blob/main/docs/src/idiomatic/001assignments.md 28 | *) 29 | __x := __e == __x = __e 30 | 31 | (** 32 | * A generator of a data structure. Given a positive integer `bound`, and 33 | * assuming that the type of the operator application is known, we 34 | * recursively generate a TLA+ data structure as a tree, whose width is 35 | * bound by the number `bound`. 36 | * 37 | * The body of this operator is redefined by Apalache. 38 | *) 39 | Gen(__size) == {} 40 | 41 | (** 42 | * Non-deterministically pick a value out of the set `S`, if `S` is non-empty. 43 | * If `S` is empty, return some value of the proper type. This can be 44 | * understood as a non-deterministic version of CHOOSE x \in S: TRUE. 45 | * 46 | * @type: Set(a) => a; 47 | *) 48 | Guess(__S) == 49 | \* Since this is not supported by TLC, 50 | \* we fall back to the deterministic version for TLC. 51 | \* Apalache redefines the operator `Guess` as explained above. 52 | CHOOSE __x \in __S: TRUE 53 | 54 | (** 55 | * Convert a set of pairs S to a function F. Note that if S contains at least 56 | * two pairs <> and <> such that x = u and y /= v, 57 | * then F is not uniquely defined. We use CHOOSE to resolve this ambiguity. 58 | * Apalache implements a more efficient encoding of this operator 59 | * than the default one. 60 | * 61 | * @type: Set(<>) => (a -> b); 62 | *) 63 | SetAsFun(__S) == 64 | LET __Dom == { __x: <<__x, __y>> \in __S } 65 | __Rng == { __y: <<__x, __y>> \in __S } 66 | IN 67 | [ __x \in __Dom |-> CHOOSE __y \in __Rng: <<__x, __y>> \in __S ] 68 | 69 | (** 70 | * A sequence constructor that avoids using a function constructor. 71 | * Since Apalache is typed, this operator is more efficient than 72 | * FunAsSeq([ i \in 1..N |-> F(i) ]). Apalache requires N to be 73 | * a constant expression. 74 | * 75 | * @type: (Int, (Int -> a)) => Seq(a); 76 | *) 77 | LOCAL INSTANCE Integers 78 | MkSeq(__N, __F(_)) == 79 | \* This is the TLC implementation. Apalache does it differently. 80 | [ __i \in (1..__N) |-> __F(__i) ] 81 | 82 | \* required by our default definition of FoldSeq and FunAsSeq 83 | LOCAL INSTANCE Sequences 84 | 85 | (** 86 | * As TLA+ is untyped, one can use function- and sequence-specific operators 87 | * interchangeably. However, to maintain correctness w.r.t. our type-system, 88 | * an explicit cast is needed when using functions as sequences. 89 | * FunAsSeq reinterprets a function over integers as a sequence. 90 | * 91 | * The parameters have the following meaning: 92 | * 93 | * - fn is the function from 1..len that should be interpreted as a sequence. 94 | * - len is the length of the sequence, len = Cardinality(DOMAIN fn), 95 | * len may be a variable, a computable expression, etc. 96 | * - capacity is a static upper bound on the length, that is, len <= capacity. 97 | * 98 | * @type: ((Int -> a), Int, Int) => Seq(a); 99 | *) 100 | FunAsSeq(__fn, __len, __capacity) == 101 | LET __FunAsSeq_elem_ctor(__i) == __fn[__i] IN 102 | SubSeq(MkSeq(__capacity, __FunAsSeq_elem_ctor), 1, __len) 103 | 104 | (** 105 | * Annotating an expression \E x \in S: P as Skolemizable. That is, it can 106 | * be replaced with an expression c \in S /\ P(c) for a fresh constant c. 107 | * Not every exisential can be replaced with a constant, this should be done 108 | * with care. Apalache detects Skolemizable expressions by static analysis. 109 | *) 110 | Skolem(__e) == __e 111 | 112 | (** 113 | * A hint to the model checker to expand a set S, instead of dealing 114 | * with it symbolically. Apalache finds out which sets have to be expanded 115 | * by static analysis. 116 | *) 117 | Expand(__S) == __S 118 | 119 | (** 120 | * A hint to the model checker to replace its argument Cardinality(S) >= k 121 | * with a series of existential quantifiers for a constant k. 122 | * Similar to Skolem, this has to be done carefully. Apalache automatically 123 | * places this hint by static analysis. 124 | *) 125 | ConstCardinality(__cardExpr) == __cardExpr 126 | 127 | (** 128 | * The folding operator, used to implement computation over a set. 129 | * Apalache implements a more efficient encoding than the one below. 130 | * (from the community modules). 131 | * 132 | * @type: ((a, b) => a, a, Set(b)) => a; 133 | *) 134 | RECURSIVE ApaFoldSet(_, _, _) 135 | ApaFoldSet(__Op(_,_), __v, __S) == 136 | IF __S = {} 137 | THEN __v 138 | ELSE LET __w == CHOOSE __x \in __S: TRUE IN 139 | LET __T == __S \ {__w} IN 140 | ApaFoldSet(__Op, __Op(__v,__w), __T) 141 | 142 | (** 143 | * The folding operator, used to implement computation over a sequence. 144 | * Apalache implements a more efficient encoding than the one below. 145 | * (from the community modules). 146 | * 147 | * @type: ((a, b) => a, a, Seq(b)) => a; 148 | *) 149 | RECURSIVE ApaFoldSeqLeft(_, _, _) 150 | ApaFoldSeqLeft(__Op(_,_), __v, __seq) == 151 | IF __seq = <<>> 152 | THEN __v 153 | ELSE ApaFoldSeqLeft(__Op, __Op(__v, Head(__seq)), Tail(__seq)) 154 | 155 | =============================================================================== 156 | -------------------------------------------------------------------------------- /u2pc/U2PC.tla: -------------------------------------------------------------------------------- 1 | ---- MODULE U2PC ---- 2 | 3 | \******************** 4 | \* This models a a deployment of U2PC with a reliable network (eventual and non-corrupt delivery). 5 | \* 6 | \* Crash faults are not explicitly modelled, instead relying on asynchrony (both in nodes and the network) 7 | \* to provide equivalent executions. 8 | \* Thus an execution where a replica crashes is equivalent to one where that replica takes no further action. 9 | \* 10 | \******************** 11 | 12 | EXTENDS FiniteSets, Integers, Apalache, TLC 13 | 14 | \* @typeAlias: key = Str; 15 | \* @typeAlias: rid = Str; 16 | \* @typeAlias: tid = Str; 17 | \* @typeAlias: version = $tid; 18 | \* @typeAlias: txn = Set($key); 19 | \* @typeAlias: txnstate = $key -> $version; 20 | U2PC_ALIAS == TRUE 21 | 22 | CONSTANTS 23 | \* @type: $key -> Set($rid); 24 | Shards, 25 | \* @type: $tid -> $txn; 26 | Txns 27 | 28 | ASSUME "Init" \notin DOMAIN Txns 29 | 30 | \******************** 31 | \* Replicas are unique across shards. 32 | \* 33 | \* In full implementations, if a server is a replica for multiple shards, 34 | \* it must have separate state for each shard. 35 | \******************** 36 | ASSUME \A k1, k2 \in DOMAIN Shards: k1 /= k2 => Shards[k1] \cap Shards[k2] = {} 37 | 38 | \* msg_read = Str; 39 | \* msg_read_resp = {key: $key, ver : $version}; 40 | \* msg_lock = {txn: $txn, key: $key, ver: $version}; 41 | \* msg_lock_resp = Bool; 42 | \* msg_unlock = Bool; 43 | \* msg_unlock_resp = Bool; 44 | 45 | VARIABLES 46 | \* @type: $rid -> {locked : Bool, version: $version, logged: $version}; 47 | Replicas, 48 | \* @type: $tid -> Str; 49 | Coordinator_state, 50 | \* @type: $tid -> $txnstate; 51 | Coordinator_txn_state, 52 | \* @type: Set({src : $tid, key : $key}); 53 | M_read, 54 | \* @type: Set({src : $rid, dst: $tid, ver: $version}); 55 | M_read_resp, 56 | \* @type: Set({tid : $tid, txn: $txn, state: $txnstate}); 57 | M_lock, 58 | \* @type: Set({src : $rid, dst : $tid, locked : Bool}); 59 | M_lock_resp, 60 | \* @type: Set({src : $tid, apply : Bool}); 61 | M_unlock, 62 | \* @type: Set({src : $rid, tid : $tid}); 63 | M_unlock_resp, 64 | \* The set of transactions committed before the given transaction started. 65 | \* NOTE: only used to check linearisability 66 | \* @type: $tid -> Set($tid); 67 | Linearisability_rt 68 | 69 | Vars == << Replicas, 70 | Coordinator_state, Coordinator_txn_state, 71 | M_read, M_read_resp, 72 | M_lock, M_lock_resp, 73 | M_unlock, M_unlock_resp, 74 | Linearisability_rt >> 75 | 76 | Var_M_read == <> 77 | Var_M_lock == <> 78 | Var_M_unlock == <> 79 | 80 | Var_Msgs == <> 81 | 82 | \* @type: (a -> b) => Set(b); 83 | Range(F) == {F[x] : x \in DOMAIN F} 84 | 85 | \* @type: Set($rid); 86 | RIDs == UNION Range(Shards) 87 | TIDs == DOMAIN Txns 88 | 89 | KeyLookup == [r \in RIDs |-> CHOOSE k \in DOMAIN Shards: r \in Shards[k]] 90 | 91 | Init == 92 | /\ Replicas = [r \in RIDs |-> 93 | [locked |-> FALSE, version |-> "Init", logged |-> "NULL"]] 94 | /\ Coordinator_state = [t \in TIDs |-> "Start"] 95 | /\ Coordinator_txn_state = [t \in TIDs |-> SetAsFun({})] 96 | /\ M_read = {} /\ M_read_resp = {} 97 | /\ M_lock = {} /\ M_lock_resp = {} 98 | /\ M_unlock = {} /\ M_unlock_resp = {} 99 | /\ Linearisability_rt = [t \in TIDs |-> {}] 100 | 101 | RelevantReplicas(t) == UNION {Shards[k]: k \in Txns[t]} 102 | 103 | CoordinatorStart(t) == 104 | /\ Coordinator_state[t] = "Start" 105 | /\ M_read' = M_read \cup {[src |-> t, key |-> k] : k \in Txns[t]} 106 | /\ UNCHANGED << M_read_resp, Var_M_lock, Var_M_unlock>> 107 | /\ Coordinator_state' = [Coordinator_state EXCEPT ![t] = "Read"] 108 | /\ UNCHANGED <> 109 | /\ Linearisability_rt' = [Linearisability_rt EXCEPT ![t] = 110 | {t1 \in TIDs : Coordinator_state[t1] = "Commit"}] 111 | 112 | ReplicaRead(r) == 113 | /\ Replicas[r].locked = FALSE 114 | /\ \E m \in M_read: 115 | /\ ~\E m1 \in M_read_resp: m1.src = r /\ m1.dst = m.src 116 | /\ M_read_resp' = M_read_resp \cup 117 | {[src |-> r, dst |-> m.src, ver |-> Replicas[r].version]} 118 | /\ UNCHANGED << M_read, Var_M_lock, Var_M_unlock>> 119 | /\ UNCHANGED << Replicas >> 120 | /\ UNCHANGED << Coordinator_state, Coordinator_txn_state, Linearisability_rt>> 121 | 122 | CoordinatorRead(t) == 123 | /\ Coordinator_state[t] = "Read" 124 | /\ \A k \in Txns[t]: \E m \in M_read_resp: KeyLookup[m.src] = k 125 | /\ \E F \in [Txns[t] -> RIDs]: 126 | /\ \A k \in Txns[t]: /\ k = KeyLookup[F[k]] 127 | /\ \E m \in M_read_resp: m.dst = t /\ m.src = F[k] 128 | /\ Coordinator_txn_state' = [Coordinator_txn_state EXCEPT ![t] = [ 129 | k \in Txns[t] |-> 130 | (CHOOSE m \in M_read_resp : m.dst = t /\ m.src = F[k]).ver 131 | ]] 132 | /\ Coordinator_state' = [Coordinator_state EXCEPT ![t] = "Lock"] 133 | /\ UNCHANGED << Replicas, Var_Msgs, Linearisability_rt >> 134 | 135 | CoordinatorLock(t) == 136 | /\ Coordinator_state[t] = "Lock" 137 | /\ M_lock' = M_lock \cup 138 | {[tid |-> t, txn |-> Txns[t], state |-> Coordinator_txn_state[t]]} 139 | /\ UNCHANGED << M_lock_resp, Var_M_read, Var_M_unlock >> 140 | /\ Coordinator_state' = [Coordinator_state EXCEPT ![t] = "Decide"] 141 | /\ UNCHANGED <> 142 | 143 | ReplicaLock(r) == 144 | /\ \E m \in M_lock: 145 | /\ KeyLookup[r] \in m.txn 146 | /\ ~\E m1 \in M_lock_resp: m1.src = r /\ m1.dst = m.tid 147 | /\ IF (~Replicas[r].locked) /\ Replicas[r].version = m.state[KeyLookup[r]] 148 | THEN 149 | /\ Replicas' = [Replicas EXCEPT ![r] = [ 150 | locked |-> TRUE, version |-> Replicas[r].version, logged |-> m.tid]] 151 | /\ M_lock_resp' = M_lock_resp \cup 152 | {[src |-> r, dst |-> m.tid, locked |-> TRUE]} 153 | ELSE 154 | /\ M_lock_resp' = M_lock_resp \cup 155 | {[src |-> r, dst |-> m.tid, locked |-> FALSE]} 156 | /\ UNCHANGED Replicas 157 | /\ UNCHANGED << M_lock, Var_M_read, Var_M_unlock >> 158 | /\ UNCHANGED << Coordinator_state, Coordinator_txn_state, Linearisability_rt>> 159 | 160 | CoordinatorCommit(t) == 161 | /\ Coordinator_state[t] = "Decide" 162 | /\ \A k \in Txns[t]: \A r \in Shards[k]: \E m \in M_lock_resp: 163 | /\ m.src = r /\ m.dst = t 164 | /\ m.locked 165 | /\ Coordinator_state' = [Coordinator_state EXCEPT ![t] = "Commit"] 166 | /\ M_unlock' = M_unlock \cup {[src |-> t, apply |-> TRUE]} 167 | /\ UNCHANGED << Var_M_read, Var_M_lock, M_unlock_resp >> 168 | /\ UNCHANGED << Replicas, Coordinator_txn_state, Linearisability_rt>> 169 | 170 | CoordinatorStartAbort(t) == 171 | /\ Coordinator_state[t] = "Decide" 172 | /\ \E k \in Txns[t]: \E r \in Shards[k]: \E m \in M_lock_resp: 173 | /\ m.src = r /\ m.dst = t 174 | /\ ~m.locked 175 | /\ Coordinator_state' = [Coordinator_state EXCEPT ![t] = "TryAbort"] 176 | /\ M_unlock' = M_unlock \cup {[src |-> t, apply |-> FALSE]} 177 | /\ UNCHANGED << Var_M_read, Var_M_lock, M_unlock_resp >> 178 | /\ UNCHANGED << Replicas, Coordinator_txn_state, Linearisability_rt>> 179 | 180 | ReplicaUnlock(r) == 181 | \E m \in M_unlock: 182 | /\ Replicas[r].locked 183 | /\ m.src = Replicas[r].logged 184 | /\ IF m.apply 185 | THEN Replicas' = [Replicas EXCEPT ![r] = [ 186 | locked |-> FALSE, version |-> Replicas[r].logged, logged |-> "NULL"]] 187 | ELSE Replicas' = [Replicas EXCEPT ![r] = [ 188 | locked |-> FALSE, version |-> Replicas[r].version, logged |-> "NULL"]] 189 | /\ M_unlock_resp' = M_unlock_resp \cup {[src |-> r, tid |-> Replicas[r].logged]} 190 | /\ UNCHANGED << Var_M_read, Var_M_lock, M_unlock >> 191 | /\ UNCHANGED << Coordinator_state, Coordinator_txn_state, Linearisability_rt>> 192 | 193 | CoordinatorAbort(t) == 194 | /\ Coordinator_state[t] = "TryAbort" 195 | /\ \E k \in Txns[t]: \A r \in Shards[k]: 196 | \/ \E m \in M_unlock_resp: m.src = r /\ m.tid = t 197 | \/ \E m \in M_lock_resp: m.src = r /\ m.dst = t /\ ~m.locked 198 | /\ Coordinator_state' = [Coordinator_state EXCEPT ![t] = "Abort"] 199 | /\ UNCHANGED << Replicas, Var_Msgs, Coordinator_txn_state, Linearisability_rt>> 200 | 201 | 202 | Next == 203 | \/ \E r \in RIDs: \/ ReplicaRead(r) 204 | \/ ReplicaLock(r) 205 | \/ ReplicaUnlock(r) 206 | \/ \E t \in TIDs: \/ CoordinatorStart(t) 207 | \/ CoordinatorRead(t) 208 | \/ CoordinatorLock(t) 209 | \/ CoordinatorCommit(t) 210 | \/ CoordinatorStartAbort(t) 211 | \/ CoordinatorAbort(t) 212 | 213 | Spec == Init /\ [][Next]_Vars 214 | 215 | Linearisability(C) == 216 | \/ Cardinality(C) < 2 217 | \/ \E R \in SUBSET (C \X C): 218 | \* Irreflexive 219 | /\ \A t1 \in C: <> \notin R 220 | \* Transitive 221 | /\ \A t1, t2, t3 \in C: (<> \in R /\ <> \in R) => <> \in R 222 | \* Above 2 ensure there are no cycles 223 | \* R respects observed order 224 | /\ \A t1, t2 \in C: 225 | (\E k \in Txns[t2] : Coordinator_txn_state[t2][k] = t1) => <> \in R 226 | \* If two transactions interfere, there is an order 227 | /\ \A t1, t2 \in C: 228 | (t1 /= t2 /\ Txns[t1] \cap Txns[t2] /= {}) => <> \in R \/ <> \in R 229 | \* Strict serialisability / Linearisability check 230 | /\ \A t1, t2 \in C: 231 | (t1 \in Linearisability_rt[t2]) => <> \in R 232 | 233 | CommittedTIDs == {t \in TIDs: Coordinator_state[t] = "Commit"} 234 | AbortedTIDs == {t \in TIDs: Coordinator_state[t] = "Abort"} 235 | 236 | Safety_non_recovery == Linearisability(CommittedTIDs) 237 | ==== 238 | -------------------------------------------------------------------------------- /u2pc/U2PC_MC.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION 2 | Spec 3 | 4 | CONSTANTS 5 | Txns <- T3 6 | Shards <- S3 7 | 8 | INVARIANTS 9 | Invs 10 | -------------------------------------------------------------------------------- /u2pc/U2PC_MC.tla: -------------------------------------------------------------------------------- 1 | ---- MODULE U2PC_MC ---- 2 | 3 | EXTENDS Apalache, U2PC, TLC 4 | 5 | \* @type: (a, b) => <>; 6 | Pair(A,B) == << A,B >> 7 | 8 | \******************** 9 | \* 1 shard, 1-2 transactions 10 | \* 11 | \* Checking simple commit, and conflict behaviours 12 | \******************** 13 | T1 == SetAsFun({Pair("T1", {"X"})}) 14 | T1_2 == SetAsFun({Pair("T1", {"X"}), Pair("T2", {"X"})}) 15 | S1 == SetAsFun({Pair("X", {"X1", "X2"})}) 16 | 17 | \******************** 18 | \* 3 shards, 3 transactions 19 | \* 20 | \* Checking indirect dependency loops 21 | \******************** 22 | T3 == SetAsFun({ 23 | Pair("T1", {"X", "Y"}), 24 | Pair("T2", {"Y","Z"}), 25 | Pair("T3", {"Z", "X"})}) 26 | S3 == SetAsFun({ 27 | Pair("X", {"X1", "X2"}), 28 | Pair("Y", {"Y1", "Y2"}), 29 | Pair("Z", {"Z1", "Z2"})}) 30 | 31 | \******************** 32 | \* Initial state for Apalache testing 33 | \******************** 34 | CInit == 35 | /\ Txns := T3 36 | /\ Shards := S3 37 | 38 | \******************** 39 | \* Credit to https://github.com/tlaplus/examples 40 | \******************** 41 | TransitiveClosure(R) == 42 | LET S == {r[1] : r \in R} \cup {r[2] : r \in R} 43 | RECURSIVE TCR(_) 44 | TCR(T) == IF T = {} 45 | THEN R 46 | ELSE LET r == CHOOSE s \in T : TRUE 47 | RR == TCR(T \ {r}) 48 | IN RR \cup {<> \in S \X S : 49 | <> \in RR /\ <> \in RR} 50 | IN TCR(S) 51 | 52 | TransactionOrdering == LET 53 | F(acc, tid) == acc \union (Range(Coordinator_txn_state[tid]) \X {tid}) 54 | Base == ApaFoldSet(F, {}, TIDs) 55 | IN TransitiveClosure(Base) 56 | 57 | RecoveryCommitted(S) == 58 | {t \in TIDs: 59 | \A r \in S: 60 | KeyLookup[r] \in Txns[t] 61 | => \/ Replicas[r].locked /\ Replicas[r].logged = t 62 | \/ Replicas[r].version = t 63 | \/ <> \in TransactionOrdering 64 | } 65 | 66 | \******************** 67 | \* Every transaction committed during recovery preserves linearisability 68 | \******************** 69 | Safety_recovery == 70 | \A S \in SUBSET RIDs: 71 | \* Valid recovery 72 | (\A k \in DOMAIN Shards: \E r \in S: r \in Shards[k]) 73 | => Linearisability(CommittedTIDs \cup RecoveryCommitted(S)) 74 | 75 | RecoveryAborted(S) == 76 | {t \in TIDs: 77 | \E r \in S: 78 | /\ KeyLookup[r] \in Txns[t] 79 | /\ \/ ~Replicas[r].locked 80 | \/ Replicas[r].locked /\ Replicas[r].logged /= t} 81 | 82 | \******************** 83 | \* Every committed or aborted transaction results in the same recovery decision 84 | \******************** 85 | Durability == 86 | \A S \in SUBSET RIDs: 87 | \* Valid recovery 88 | (\A k \in DOMAIN Shards: \E r \in S: r \in Shards[k]) 89 | => 90 | \A t \in TIDs: 91 | /\ t \in CommittedTIDs => t \in RecoveryCommitted(S) 92 | /\ t \in AbortedTIDs => t \in RecoveryAborted(S) 93 | 94 | \******************** 95 | \* Since recovery stops every replica it uses, an explicit recovery check is unnecessary 96 | \* since that is equivalent to just checking that every possible recovery using the current 97 | \* state preserves the invariants. 98 | \******************** 99 | Invs == 100 | /\ Safety_recovery 101 | /\ Durability 102 | 103 | ==== 104 | -------------------------------------------------------------------------------- /u2pc/latex/U2PC.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cjen1/consensus-tlaplus/48d85802c39cb46c92d033d093d4095ea746fdd9/u2pc/latex/U2PC.pdf -------------------------------------------------------------------------------- /u2pc/latex/U2PC.tex: -------------------------------------------------------------------------------- 1 | \batchmode %% Suppresses most terminal output. 2 | \documentclass{article} 3 | \setlength{\textwidth}{360pt} 4 | \setlength{\textheight}{541pt} 5 | \usepackage{tlatex} 6 | \begin{document} 7 | \tlatex 8 | \@x{}\moduleLeftDash\@xx{ {\MODULE} U2PC}\moduleRightDash\@xx{}% 9 | \@pvspace{8.0pt}% 10 | \begin{lcom}{0}% 11 | \begin{cpar}{0}{F}{F}{0}{0}{}% 12 | This models a a deployment of \ensuremath{U2PC} with a reliable network 13 | (eventual and non-corrupt delivery). 14 | \end{cpar}% 15 | \vshade{5.0}% 16 | \begin{cpar}{0}{F}{F}{0}{0}{}% 17 | Crash faults are not explicitly modelled, instead relying on asynchrony (both 18 | in nodes and the network) 19 | to provide equivalent executions. 20 | Thus an execution where a replica crashes is equivalent to one where that 21 | replica takes no further action. 22 | \end{cpar}% 23 | \end{lcom}% 24 | \@pvspace{8.0pt}% 25 | \@x{ {\EXTENDS} FiniteSets ,\, Integers ,\, Apalache ,\, TLC}% 26 | \@pvspace{8.0pt}% 27 | \@x{}% 28 | \@y{\@s{0}% 29 | @\ensuremath{typeAlias}: \ensuremath{key \.{=} Str}; 30 | }% 31 | \@xx{}% 32 | \@x{}% 33 | \@y{\@s{0}% 34 | @\ensuremath{typeAlias}: rid \ensuremath{\.{=} Str}; 35 | }% 36 | \@xx{}% 37 | \@x{}% 38 | \@y{\@s{0}% 39 | @\ensuremath{typeAlias}: \ensuremath{tid \.{=} Str}; 40 | }% 41 | \@xx{}% 42 | \@x{}% 43 | \@y{\@s{0}% 44 | @\ensuremath{typeAlias}: \ensuremath{version \.{=}} \.{\$}\ensuremath{tid}; 45 | }% 46 | \@xx{}% 47 | \@x{}% 48 | \@y{\@s{0}% 49 | @\ensuremath{typeAlias}: \ensuremath{txn \.{=} Set(\.{\,\$\,}key)}; 50 | }% 51 | \@xx{}% 52 | \@x{}% 53 | \@y{\@s{0}% 54 | @\ensuremath{typeAlias}: \ensuremath{txnstate \.{=}} \.{\$}\ensuremath{key 55 | \.{\rightarrow}} \.{\$}version; 56 | }% 57 | \@xx{}% 58 | \@x{ U2PC\_ALIAS \.{\defeq} {\TRUE}}% 59 | \@pvspace{8.0pt}% 60 | \@x{ {\CONSTANTS}}% 61 | \@x{\@s{8.2}}% 62 | \@y{\@s{0}% 63 | @type: \.{\$}\ensuremath{key \.{\rightarrow} Set(\.{\,\$\,}rid)}; 64 | }% 65 | \@xx{}% 66 | \@x{\@s{8.2} Shards ,\,}% 67 | \@x{\@s{8.2}}% 68 | \@y{\@s{0}% 69 | @type: \.{\$}\ensuremath{tid \.{\rightarrow}} \.{\$}\ensuremath{txn}; 70 | }% 71 | \@xx{}% 72 | \@x{\@s{8.2} Txns}% 73 | \@pvspace{8.0pt}% 74 | \@x{ {\ASSUME}\@w{Init} \.{\notin} {\DOMAIN} Txns}% 75 | \@pvspace{8.0pt}% 76 | \begin{lcom}{0}% 77 | \begin{cpar}{0}{F}{F}{0}{0}{}% 78 | Replicas are unique across shards. 79 | \end{cpar}% 80 | \vshade{5.0}% 81 | \begin{cpar}{0}{F}{F}{0}{0}{}% 82 | In full implementations, if a server is a replica for multiple shards, 83 | it must have separate state for each shard. 84 | \end{cpar}% 85 | \end{lcom}% 86 | \@x{ {\ASSUME} \A\, k1 ,\, k2 \.{\in} {\DOMAIN} Shards \.{:} k1 \.{\neq} k2 87 | \.{\implies} Shards [ k1 ] \.{\cap} Shards [ k2 ] \.{=} \{ \}}% 88 | \@pvspace{8.0pt}% 89 | \@x{}% 90 | \@y{\@s{0}% 91 | \ensuremath{msg\_read \.{=} Str}; 92 | }% 93 | \@xx{}% 94 | \@x{}% 95 | \@y{\@s{0}% 96 | \ensuremath{msg\_read\_resp \.{=} \{key\.{:} \.{\,\$\,}key,\, ver \.{:} 97 | \.{\,\$\,}version\}}; 98 | }% 99 | \@xx{}% 100 | \@x{}% 101 | \@y{\@s{0}% 102 | \ensuremath{msg\_lock \.{=} \{txn\.{:} \.{\,\$\,}txn,\, key\.{:} 103 | \.{\,\$\,}key,\, ver\.{:} \.{\,\$\,}version\}}; 104 | }% 105 | \@xx{}% 106 | \@x{}% 107 | \@y{\@s{0}% 108 | \ensuremath{msg\_lock\_resp \.{=} Bool}; 109 | }% 110 | \@xx{}% 111 | \@x{}% 112 | \@y{\@s{0}% 113 | \ensuremath{msg\_unlock \.{=} Bool}; 114 | }% 115 | \@xx{}% 116 | \@x{}% 117 | \@y{\@s{0}% 118 | \ensuremath{msg\_unlock\_resp \.{=} Bool}; 119 | }% 120 | \@xx{}% 121 | \@pvspace{8.0pt}% 122 | \@x{ {\VARIABLES}}% 123 | \@x{\@s{8.2}}% 124 | \@y{\@s{0}% 125 | @type: \.{\$}rid \ensuremath{\.{\rightarrow} \{locked \.{:} Bool,\, 126 | version\.{:} \.{\,\$\,}version,\, logged\.{:} \.{\,\$\,}version\}}; 127 | }% 128 | \@xx{}% 129 | \@x{\@s{8.2} Replicas ,\,}% 130 | \@x{\@s{8.2}}% 131 | \@y{\@s{0}% 132 | @type: \.{\$}\ensuremath{tid \.{\rightarrow} Str}; 133 | }% 134 | \@xx{}% 135 | \@x{\@s{8.2} Coordinator\_state ,\,}% 136 | \@x{\@s{8.2}}% 137 | \@y{\@s{0}% 138 | @type: \.{\$}\ensuremath{tid \.{\rightarrow}} \.{\$}txnstate; 139 | }% 140 | \@xx{}% 141 | \@x{\@s{8.2} Coordinator\_txn\_state ,\,}% 142 | \@x{\@s{8.2}}% 143 | \@y{\@s{0}% 144 | @type: \ensuremath{Set(\{src \.{:} \.{\,\$\,}tid,\, key \.{:} 145 | \.{\,\$\,}key\})}; 146 | }% 147 | \@xx{}% 148 | \@x{\@s{8.2} M\_read ,\,}% 149 | \@x{\@s{8.2}}% 150 | \@y{\@s{0}% 151 | @type: \ensuremath{Set(\{src \.{:} \.{\,\$\,}rid,\, dst\.{:} 152 | \.{\,\$\,}tid,\, ver\.{:} \.{\,\$\,}version\})}; 153 | }% 154 | \@xx{}% 155 | \@x{\@s{8.2} M\_read\_resp ,\,}% 156 | \@x{\@s{8.2}}% 157 | \@y{\@s{0}% 158 | @type: \ensuremath{Set(\{tid \.{:} \.{\,\$\,}tid,\, txn\.{:} 159 | \.{\,\$\,}txn,\, state\.{:} \.{\,\$\,}txnstate\})}; 160 | }% 161 | \@xx{}% 162 | \@x{\@s{8.2} M\_lock ,\,}% 163 | \@x{\@s{8.2}}% 164 | \@y{\@s{0}% 165 | @type: \ensuremath{Set(\{src \.{:} \.{\,\$\,}rid,\, dst \.{:} 166 | \.{\,\$\,}tid,\, locked \.{:} Bool\})}; 167 | }% 168 | \@xx{}% 169 | \@x{\@s{8.2} M\_lock\_resp ,\,}% 170 | \@x{\@s{8.2}}% 171 | \@y{\@s{0}% 172 | @type: \ensuremath{Set(\{src \.{:} \.{\,\$\,}tid,\, apply \.{:} Bool\})}; 173 | }% 174 | \@xx{}% 175 | \@x{\@s{8.2} M\_unlock ,\,}% 176 | \@x{\@s{8.2}}% 177 | \@y{\@s{0}% 178 | @type: \ensuremath{Set(\{src \.{:} \.{\,\$\,}rid,\, tid \.{:} 179 | \.{\,\$\,}tid\})}; 180 | }% 181 | \@xx{}% 182 | \@x{\@s{8.2} M\_unlock\_resp ,\,}% 183 | \@x{\@s{8.2}}% 184 | \@y{\@s{0}% 185 | The set of transactions committed before the given transaction started. 186 | }% 187 | \@xx{}% 188 | \@x{\@s{8.2}}% 189 | \@y{\@s{0}% 190 | NOTE: only used to check linearisability 191 | }% 192 | \@xx{}% 193 | \@x{\@s{8.2}}% 194 | \@y{\@s{0}% 195 | @type: \.{\$}\ensuremath{tid \.{\rightarrow} Set(\.{\,\$\,}tid)}; 196 | }% 197 | \@xx{}% 198 | \@x{\@s{8.2} Linearisability\_rt}% 199 | \@pvspace{8.0pt}% 200 | \@x{ Vars \.{\defeq} {\langle} Replicas ,\,}% 201 | \@x{ Coordinator\_state ,\, Coordinator\_txn\_state ,\,}% 202 | \@x{ M\_read ,\, M\_read\_resp ,\,}% 203 | \@x{ M\_lock ,\, M\_lock\_resp ,\,}% 204 | \@x{ M\_unlock ,\, M\_unlock\_resp ,\,}% 205 | \@x{ Linearisability\_rt {\rangle}}% 206 | \@pvspace{8.0pt}% 207 | \@x{ Var\_M\_read \.{\defeq} {\langle} M\_read ,\, M\_read\_resp {\rangle}}% 208 | \@x{ Var\_M\_lock \.{\defeq} {\langle} M\_lock ,\, M\_lock\_resp {\rangle}}% 209 | \@x{ Var\_M\_unlock \.{\defeq} {\langle} M\_unlock ,\, M\_unlock\_resp 210 | {\rangle}}% 211 | \@pvspace{8.0pt}% 212 | \@x{ Var\_Msgs \.{\defeq} {\langle} Var\_M\_read ,\, Var\_M\_lock ,\, 213 | Var\_M\_unlock {\rangle}}% 214 | \@pvspace{8.0pt}% 215 | \@x{}% 216 | \@y{\@s{0}% 217 | @type: (a \ensuremath{\.{\rightarrow} b}) \ensuremath{\.{\implies} Set(b)}; 218 | }% 219 | \@xx{}% 220 | \@x{ Range ( F ) \.{\defeq} \{ F [ x ] \.{:} x \.{\in} {\DOMAIN} F \}}% 221 | \@pvspace{8.0pt}% 222 | \@x{}% 223 | \@y{\@s{0}% 224 | @type: \ensuremath{Set(\.{\,\$\,}rid)}; 225 | }% 226 | \@xx{}% 227 | \@x{ RIDs \.{\defeq} {\UNION} Range ( Shards )}% 228 | \@x{ TIDs \.{\defeq} {\DOMAIN} Txns}% 229 | \@pvspace{8.0pt}% 230 | \@x{ KeyLookup \.{\defeq} [ r \.{\in} RIDs \.{\mapsto} {\CHOOSE} k \.{\in} 231 | {\DOMAIN} Shards \.{:} r \.{\in} Shards [ k ] ]}% 232 | \@pvspace{8.0pt}% 233 | \@x{ Init \.{\defeq}}% 234 | \@x{\@s{8.2} \.{\land} Replicas \.{=} [ r \.{\in} RIDs \.{\mapsto}}% 235 | \@x{\@s{16.4} [ locked \.{\mapsto} {\FALSE} ,\, version \.{\mapsto}\@w{Init} 236 | ,\, logged \.{\mapsto}\@w{NULL} ] ]}% 237 | \@x{\@s{8.2} \.{\land} Coordinator\_state \.{=} [ t \.{\in} TIDs 238 | \.{\mapsto}\@w{Start} ]}% 239 | \@x{\@s{8.2} \.{\land} Coordinator\_txn\_state \.{=} [ t \.{\in} TIDs 240 | \.{\mapsto} SetAsFun ( \{ \} ) ]}% 241 | \@x{\@s{8.2} \.{\land} M\_read \.{=} \{ \} \.{\land} M\_read\_resp \.{=} \{ 242 | \}}% 243 | \@x{\@s{8.2} \.{\land} M\_lock \.{=} \{ \} \.{\land} M\_lock\_resp \.{=} \{ 244 | \}}% 245 | \@x{\@s{8.2} \.{\land} M\_unlock \.{=} \{ \} \.{\land} M\_unlock\_resp \.{=} 246 | \{ \}}% 247 | \@x{\@s{8.2} \.{\land} Linearisability\_rt \.{=} [ t \.{\in} TIDs \.{\mapsto} 248 | \{ \} ]}% 249 | \@pvspace{8.0pt}% 250 | \@x{ RelevantReplicas ( t ) \.{\defeq} {\UNION} \{ Shards [ k ] \.{:} k 251 | \.{\in} Txns [ t ] \}}% 252 | \@pvspace{8.0pt}% 253 | \@x{ CoordinatorStart ( t ) \.{\defeq}}% 254 | \@x{\@s{8.2} \.{\land} Coordinator\_state [ t ] \.{=}\@w{Start}}% 255 | \@x{\@s{8.2} \.{\land} M\_read \.{'} \.{=} M\_read \.{\cup} \{ [ src 256 | \.{\mapsto} t ,\, key \.{\mapsto} k ] \.{:} k \.{\in} Txns [ t ] \}}% 257 | \@x{\@s{8.2} \.{\land} {\UNCHANGED} {\langle} M\_read\_resp ,\, Var\_M\_lock 258 | ,\, Var\_M\_unlock {\rangle}}% 259 | \@x{\@s{8.2} \.{\land} Coordinator\_state \.{'} \.{=} [ Coordinator\_state 260 | {\EXCEPT} {\bang} [ t ] \.{=}\@w{Read} ]}% 261 | \@x{\@s{8.2} \.{\land} {\UNCHANGED} {\langle} Coordinator\_txn\_state ,\, 262 | Replicas {\rangle}}% 263 | \@x{\@s{8.2} \.{\land} Linearisability\_rt \.{'} \.{=} [ Linearisability\_rt 264 | {\EXCEPT} {\bang} [ t ] \.{=}}% 265 | \@x{\@s{12.29} \{ t1 \.{\in} TIDs \.{:} Coordinator\_state [ t1 ] 266 | \.{=}\@w{Commit} \} ]}% 267 | \@pvspace{8.0pt}% 268 | \@x{ ReplicaRead ( r ) \.{\defeq}}% 269 | \@x{\@s{8.2} \.{\land} Replicas [ r ] . locked \.{=} {\FALSE}}% 270 | \@x{\@s{8.2} \.{\land} \E\, m \.{\in} M\_read \.{:}}% 271 | \@x{\@s{8.2} \.{\land} {\lnot} \E\, m1 \.{\in} M\_read\_resp \.{:} m1 . src 272 | \.{=} r \.{\land} m1 . dst \.{=} m . src}% 273 | \@x{\@s{8.2} \.{\land} M\_read\_resp \.{'} \.{=} M\_read\_resp \.{\cup}}% 274 | \@x{\@s{12.29} \{ [ src \.{\mapsto} r ,\, dst \.{\mapsto} m . src ,\, ver 275 | \.{\mapsto} Replicas [ r ] . version ] \}}% 276 | \@x{\@s{8.2} \.{\land} {\UNCHANGED} {\langle} M\_read ,\, Var\_M\_lock ,\, 277 | Var\_M\_unlock {\rangle}}% 278 | \@x{\@s{8.2} \.{\land} {\UNCHANGED} {\langle} Replicas {\rangle}}% 279 | \@x{\@s{8.2} \.{\land} {\UNCHANGED} {\langle} Coordinator\_state ,\, 280 | Coordinator\_txn\_state ,\, Linearisability\_rt {\rangle}}% 281 | \@pvspace{8.0pt}% 282 | \@x{ CoordinatorRead ( t ) \.{\defeq}}% 283 | \@x{\@s{8.2} \.{\land} Coordinator\_state [ t ] \.{=}\@w{Read}}% 284 | \@x{\@s{8.2} \.{\land} \A\, k \.{\in} Txns [ t ] \.{:} \E\, m \.{\in} 285 | M\_read\_resp \.{:} KeyLookup [ m . src ] \.{=} k}% 286 | \@x{\@s{8.2} \.{\land} \E\, F \.{\in} [ Txns [ t ] \.{\rightarrow} RIDs ] 287 | \.{:}}% 288 | \@x{\@s{8.2} \.{\land} \A\, k \.{\in} Txns [ t ] \.{:} \.{\land} k \.{=} 289 | KeyLookup [ F [ k ] ]}% 290 | \@x{\@s{8.2} \.{\land} \E\, m \.{\in} M\_read\_resp \.{:} m . dst \.{=} t 291 | \.{\land} m . src \.{=} F [ k ]}% 292 | \@x{\@s{8.2} \.{\land} Coordinator\_txn\_state \.{'} \.{=} [ 293 | Coordinator\_txn\_state {\EXCEPT} {\bang} [ t ] \.{=} [}% 294 | \@x{\@s{12.29} k \.{\in} Txns [ t ] \.{\mapsto}}% 295 | \@x{\@s{12.29} ( {\CHOOSE} m \.{\in} M\_read\_resp \.{:} m . dst \.{=} t 296 | \.{\land} m . src \.{=} F [ k ] ) . ver}% 297 | \@x{\@s{12.29} ] ]}% 298 | \@x{\@s{8.2} \.{\land} Coordinator\_state \.{'} \.{=} [ Coordinator\_state 299 | {\EXCEPT} {\bang} [ t ] \.{=}\@w{Lock} ]}% 300 | \@x{\@s{8.2} \.{\land} {\UNCHANGED} {\langle} Replicas ,\, Var\_Msgs ,\, 301 | Linearisability\_rt {\rangle}}% 302 | \@pvspace{8.0pt}% 303 | \@x{ CoordinatorLock ( t ) \.{\defeq}}% 304 | \@x{\@s{8.2} \.{\land} Coordinator\_state [ t ] \.{=}\@w{Lock}}% 305 | \@x{\@s{8.2} \.{\land} M\_lock \.{'} \.{=} M\_lock \.{\cup}}% 306 | \@x{\@s{16.4} \{ [ tid \.{\mapsto} t ,\, txn \.{\mapsto} Txns [ t ] ,\, state 307 | \.{\mapsto} Coordinator\_txn\_state [ t ] ] \}}% 308 | \@x{\@s{8.2} \.{\land} {\UNCHANGED} {\langle} M\_lock\_resp ,\, Var\_M\_read 309 | ,\, Var\_M\_unlock {\rangle}}% 310 | \@x{\@s{8.2} \.{\land} Coordinator\_state \.{'} \.{=} [ Coordinator\_state 311 | {\EXCEPT} {\bang} [ t ] \.{=}\@w{Decide} ]}% 312 | \@x{\@s{8.2} \.{\land} {\UNCHANGED} {\langle} Coordinator\_txn\_state ,\, 313 | Replicas ,\, Linearisability\_rt {\rangle}}% 314 | \@pvspace{8.0pt}% 315 | \@x{ ReplicaLock ( r ) \.{\defeq}}% 316 | \@x{\@s{8.2} \.{\land} \E\, m \.{\in} M\_lock \.{:}}% 317 | \@x{\@s{8.2} \.{\land} KeyLookup [ r ] \.{\in} m . txn}% 318 | \@x{\@s{8.2} \.{\land} {\lnot} \E\, m1 \.{\in} M\_lock\_resp \.{:} m1 . src 319 | \.{=} r \.{\land} m1 . dst \.{=} m . tid}% 320 | \@x{\@s{8.2} \.{\land} {\IF} ( {\lnot} Replicas [ r ] . locked ) \.{\land} 321 | Replicas [ r ] . version \.{=} m . state [ KeyLookup [ r ] ]}% 322 | \@x{\@s{8.2} \.{\THEN}}% 323 | \@x{\@s{16.4} \.{\land} Replicas \.{'} \.{=} [ Replicas {\EXCEPT} {\bang} [ r 324 | ] \.{=} [}% 325 | \@x{\@s{20.5} locked \.{\mapsto} {\TRUE} ,\, version \.{\mapsto} Replicas [ r 326 | ] . version ,\, logged \.{\mapsto} m . tid ] ]}% 327 | \@x{\@s{16.4} \.{\land} M\_lock\_resp \.{'} \.{=} M\_lock\_resp \.{\cup}}% 328 | \@x{\@s{24.59} \{ [ src \.{\mapsto} r ,\, dst \.{\mapsto} m . tid ,\, locked 329 | \.{\mapsto} {\TRUE} ] \}}% 330 | \@x{\@s{8.2} \.{\ELSE}}% 331 | \@x{\@s{16.4} \.{\land} M\_lock\_resp \.{'} \.{=} M\_lock\_resp \.{\cup}}% 332 | \@x{\@s{24.59} \{ [ src \.{\mapsto} r ,\, dst \.{\mapsto} m . tid ,\, locked 333 | \.{\mapsto} {\FALSE} ] \}}% 334 | \@x{\@s{16.4} \.{\land} {\UNCHANGED} Replicas}% 335 | \@x{\@s{8.2} \.{\land} {\UNCHANGED} {\langle} M\_lock ,\, Var\_M\_read ,\, 336 | Var\_M\_unlock {\rangle}}% 337 | \@x{\@s{8.2} \.{\land} {\UNCHANGED} {\langle} Coordinator\_state ,\, 338 | Coordinator\_txn\_state ,\, Linearisability\_rt {\rangle}}% 339 | \@pvspace{8.0pt}% 340 | \@x{ CoordinatorCommit ( t ) \.{\defeq}}% 341 | \@x{\@s{8.2} \.{\land} Coordinator\_state [ t ] \.{=}\@w{Decide}}% 342 | \@x{\@s{8.2} \.{\land} \A\, k \.{\in} Txns [ t ] \.{:} \A\, r \.{\in} Shards 343 | [ k ] \.{:} \E\, m \.{\in} M\_lock\_resp \.{:}}% 344 | \@x{\@s{16.4} \.{\land} m . src \.{=} r \.{\land} m . dst \.{=} t}% 345 | \@x{\@s{16.4} \.{\land} m . locked}% 346 | \@x{\@s{8.2} \.{\land} Coordinator\_state \.{'} \.{=} [ Coordinator\_state 347 | {\EXCEPT} {\bang} [ t ] \.{=}\@w{Commit} ]}% 348 | \@x{\@s{8.2} \.{\land} M\_unlock \.{'} \.{=} M\_unlock \.{\cup} \{ [ src 349 | \.{\mapsto} t ,\, apply \.{\mapsto} {\TRUE} ] \}}% 350 | \@x{\@s{8.2} \.{\land} {\UNCHANGED} {\langle} Var\_M\_read ,\, Var\_M\_lock 351 | ,\, M\_unlock\_resp {\rangle}}% 352 | \@x{\@s{8.2} \.{\land} {\UNCHANGED} {\langle} Replicas ,\, 353 | Coordinator\_txn\_state ,\, Linearisability\_rt {\rangle}}% 354 | \@pvspace{8.0pt}% 355 | \@x{ CoordinatorStartAbort ( t ) \.{\defeq}}% 356 | \@x{\@s{8.2} \.{\land} Coordinator\_state [ t ] \.{=}\@w{Decide}}% 357 | \@x{\@s{8.2} \.{\land} \E\, k \.{\in} Txns [ t ] \.{:} \E\, r \.{\in} Shards 358 | [ k ] \.{:} \E\, m \.{\in} M\_lock\_resp \.{:}}% 359 | \@x{\@s{16.4} \.{\land} m . src \.{=} r \.{\land} m . dst \.{=} t}% 360 | \@x{\@s{16.4} \.{\land} {\lnot} m . locked}% 361 | \@x{\@s{8.2} \.{\land} Coordinator\_state \.{'} \.{=} [ Coordinator\_state 362 | {\EXCEPT} {\bang} [ t ] \.{=}\@w{TryAbort} ]}% 363 | \@x{\@s{8.2} \.{\land} M\_unlock \.{'} \.{=} M\_unlock \.{\cup} \{ [ src 364 | \.{\mapsto} t ,\, apply \.{\mapsto} {\FALSE} ] \}}% 365 | \@x{\@s{8.2} \.{\land} {\UNCHANGED} {\langle} Var\_M\_read ,\, Var\_M\_lock 366 | ,\, M\_unlock\_resp {\rangle}}% 367 | \@x{\@s{8.2} \.{\land} {\UNCHANGED} {\langle} Replicas ,\, 368 | Coordinator\_txn\_state ,\, Linearisability\_rt {\rangle}}% 369 | \@pvspace{8.0pt}% 370 | \@x{ ReplicaUnlock ( r ) \.{\defeq}}% 371 | \@x{\@s{8.2} \E\, m \.{\in} M\_unlock \.{:}}% 372 | \@x{\@s{8.2} \.{\land} Replicas [ r ] . locked}% 373 | \@x{\@s{8.2} \.{\land} m . src \.{=} Replicas [ r ] . logged}% 374 | \@x{\@s{8.2} \.{\land} {\IF} m . apply}% 375 | \@x{\@s{8.2} \.{\THEN} Replicas \.{'} \.{=}\@s{4.1} [ Replicas {\EXCEPT} 376 | {\bang} [ r ] \.{=} [}% 377 | \@x{\@s{12.29} locked \.{\mapsto} {\FALSE} ,\, version \.{\mapsto} Replicas [ 378 | r ] . logged ,\, logged \.{\mapsto}\@w{NULL} ] ]}% 379 | \@x{\@s{8.2} \.{\ELSE} Replicas \.{'} \.{=}\@s{4.1} [ Replicas {\EXCEPT} 380 | {\bang} [ r ] \.{=} [}% 381 | \@x{\@s{12.29} locked \.{\mapsto} {\FALSE} ,\, version \.{\mapsto} Replicas [ 382 | r ] . version ,\, logged \.{\mapsto}\@w{NULL} ] ]}% 383 | \@x{\@s{8.2} \.{\land} M\_unlock\_resp \.{'} \.{=} M\_unlock\_resp \.{\cup} 384 | \{ [ src \.{\mapsto} r ,\, tid \.{\mapsto} Replicas [ r ] . logged ] \}}% 385 | \@x{\@s{8.2} \.{\land} {\UNCHANGED} {\langle} Var\_M\_read ,\, Var\_M\_lock 386 | ,\, M\_unlock {\rangle}}% 387 | \@x{\@s{8.2} \.{\land} {\UNCHANGED} {\langle} Coordinator\_state ,\, 388 | Coordinator\_txn\_state ,\, Linearisability\_rt {\rangle}}% 389 | \@pvspace{8.0pt}% 390 | \@x{ CoordinatorAbort ( t ) \.{\defeq}}% 391 | \@x{\@s{8.2} \.{\land} Coordinator\_state [ t ] \.{=}\@w{TryAbort}}% 392 | \@x{\@s{8.2} \.{\land} \E\, k \.{\in} Txns [ t ] \.{:} \A\, r \.{\in} Shards 393 | [ k ] \.{:}}% 394 | \@x{\@s{8.2} \.{\lor} \E\, m \.{\in} M\_unlock\_resp \.{:} m . src \.{=} r 395 | \.{\land} m . tid \.{=} t}% 396 | \@x{\@s{8.2} \.{\lor} \E\, m \.{\in} M\_lock\_resp \.{:} m . src \.{=} r 397 | \.{\land} m . dst \.{=} t \.{\land} {\lnot} m . locked}% 398 | \@x{\@s{8.2} \.{\land} Coordinator\_state \.{'} \.{=} [ Coordinator\_state 399 | {\EXCEPT} {\bang} [ t ] \.{=}\@w{Abort} ]}% 400 | \@x{\@s{8.2} \.{\land} {\UNCHANGED} {\langle} Replicas ,\, Var\_Msgs ,\, 401 | Coordinator\_txn\_state ,\, Linearisability\_rt {\rangle}}% 402 | \@pvspace{16.0pt}% 403 | \@x{ Next \.{\defeq}}% 404 | \@x{\@s{8.2} \.{\lor} \E\, r \.{\in} RIDs \.{:} \.{\lor} ReplicaRead ( r )}% 405 | \@x{\@s{8.2} \.{\lor} ReplicaLock ( r )}% 406 | \@x{\@s{8.2} \.{\lor} ReplicaUnlock ( r )}% 407 | \@x{\@s{8.2} \.{\lor} \E\, t \.{\in} TIDs \.{:} \.{\lor} CoordinatorStart ( t 408 | )}% 409 | \@x{\@s{8.2} \.{\lor} CoordinatorRead ( t )}% 410 | \@x{\@s{8.2} \.{\lor} CoordinatorLock ( t )}% 411 | \@x{\@s{8.2} \.{\lor} CoordinatorCommit ( t )}% 412 | \@x{\@s{8.2} \.{\lor} CoordinatorStartAbort ( t )}% 413 | \@x{\@s{8.2} \.{\lor} CoordinatorAbort ( t )}% 414 | \@pvspace{8.0pt}% 415 | \@x{ Spec \.{\defeq} Init \.{\land} {\Box} [ Next ]_{ Vars}}% 416 | \@pvspace{8.0pt}% 417 | \@x{ Linearisability ( C ) \.{\defeq}}% 418 | \@x{\@s{8.2} \.{\lor} Cardinality ( C ) \.{<} 2}% 419 | \@x{\@s{8.2} \.{\lor} \E\, R \.{\in} {\SUBSET} ( C \.{\times} C ) \.{:}}% 420 | \@x{\@s{8.2}}% 421 | \@y{\@s{0}% 422 | Irreflexive 423 | }% 424 | \@xx{}% 425 | \@x{\@s{8.2} \.{\land} \A\, t1 \.{\in} C \.{:} {\langle} t1 ,\, t1 {\rangle} 426 | \.{\notin} R}% 427 | \@x{\@s{8.2}}% 428 | \@y{\@s{0}% 429 | Transitive 430 | }% 431 | \@xx{}% 432 | \@x{\@s{8.2} \.{\land} \A\, t1 ,\, t2 ,\, t3 \.{\in} C \.{:} ( {\langle} t1 433 | ,\, t2 {\rangle} \.{\in} R \.{\land} {\langle} t2 ,\, t3 {\rangle} \.{\in} R 434 | ) \.{\implies} {\langle} t1 ,\, t3 {\rangle} \.{\in} R}% 435 | \@x{\@s{8.2}}% 436 | \@y{\@s{0}% 437 | Above 2 ensure there are no cycles 438 | }% 439 | \@xx{}% 440 | \@x{\@s{8.2}}% 441 | \@y{\@s{0}% 442 | \ensuremath{R} respects observed order 443 | }% 444 | \@xx{}% 445 | \@x{\@s{8.2} \.{\land} \A\, t1 ,\, t2 \.{\in} C \.{:}}% 446 | \@x{\@s{8.2} ( \E\, k \.{\in} Txns [ t2 ] \.{:} Coordinator\_txn\_state [ t2 447 | ] [ k ] \.{=} t1 ) \.{\implies} {\langle} t1 ,\, t2 {\rangle} \.{\in} R}% 448 | \@x{\@s{8.2}}% 449 | \@y{\@s{0}% 450 | If two transactions interfere, there is an order 451 | }% 452 | \@xx{}% 453 | \@x{\@s{8.2} \.{\land} \A\, t1 ,\, t2 \.{\in} C \.{:}}% 454 | \@x{\@s{8.2} ( t1 \.{\neq} t2 \.{\land} Txns [ t1 ] \.{\cap} Txns [ t2 ] 455 | \.{\neq} \{ \} ) \.{\implies} {\langle} t1 ,\, t2 {\rangle} \.{\in} R 456 | \.{\lor} {\langle} t2 ,\, t1 {\rangle} \.{\in} R}% 457 | \@x{\@s{8.2}}% 458 | \@y{\@s{0}% 459 | Strict serialisability / \ensuremath{Linearisability} check 460 | }% 461 | \@xx{}% 462 | \@x{\@s{8.2} \.{\land} \A\, t1 ,\, t2 \.{\in} C \.{:}}% 463 | \@x{\@s{8.2} ( t1 \.{\in} Linearisability\_rt [ t2 ] ) \.{\implies} {\langle} 464 | t1 ,\, t2 {\rangle} \.{\in} R}% 465 | \@pvspace{8.0pt}% 466 | \@x{ CommittedTIDs \.{\defeq} \{ t \.{\in} TIDs \.{:} Coordinator\_state [ t 467 | ] \.{=}\@w{Commit} \}}% 468 | \@x{ AbortedTIDs \.{\defeq} \{ t \.{\in} TIDs \.{:} Coordinator\_state [ t ] 469 | \.{=}\@w{Abort} \}}% 470 | \@pvspace{8.0pt}% 471 | \@x{ Safety\_non\_recovery \.{\defeq} Linearisability ( CommittedTIDs )}% 472 | \@x{}\bottombar\@xx{}% 473 | \end{document} 474 | -------------------------------------------------------------------------------- /u2pc/latex/U2PC_MC.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cjen1/consensus-tlaplus/48d85802c39cb46c92d033d093d4095ea746fdd9/u2pc/latex/U2PC_MC.pdf -------------------------------------------------------------------------------- /u2pc/latex/U2PC_MC.tex: -------------------------------------------------------------------------------- 1 | \batchmode %% Suppresses most terminal output. 2 | \documentclass{article} 3 | \setlength{\textwidth}{360pt} 4 | \setlength{\textheight}{541pt} 5 | \usepackage{tlatex} 6 | \begin{document} 7 | \tlatex 8 | \@x{}\moduleLeftDash\@xx{ {\MODULE} U2PC\_MC}\moduleRightDash\@xx{}% 9 | \@pvspace{8.0pt}% 10 | \@x{ {\EXTENDS} Apalache ,\, U2PC ,\, TLC}% 11 | \@pvspace{8.0pt}% 12 | \@x{}% 13 | \@y{\@s{0}% 14 | @type: (a, \ensuremath{b}) \ensuremath{\.{\implies} {\langle}a,\, 15 | b{\rangle}}; 16 | }% 17 | \@xx{}% 18 | \@x{ Pair ( A ,\, B ) \.{\defeq} {\langle} A ,\, B {\rangle}}% 19 | \@pvspace{8.0pt}% 20 | \begin{lcom}{0}% 21 | \begin{cpar}{0}{F}{F}{0}{0}{}% 22 | 1 shard, 1-2 transactions 23 | \end{cpar}% 24 | \vshade{5.0}% 25 | \begin{cpar}{0}{F}{F}{0}{0}{}% 26 | Checking simple commit, and conflict behaviours 27 | \end{cpar}% 28 | \end{lcom}% 29 | \@x{ T1 \.{\defeq} SetAsFun ( \{ Pair (\@w{T1} ,\, \{\@w{X} \} ) \} )}% 30 | \@x{ T1\_2 \.{\defeq} SetAsFun ( \{ Pair (\@w{T1} ,\, \{\@w{X} \} ) ,\, Pair 31 | (\@w{T2} ,\, \{\@w{X} \} ) \} )}% 32 | \@x{ S1 \.{\defeq} SetAsFun ( \{ Pair (\@w{X} ,\, \{\@w{X1} ,\,\@w{X2} \} ) 33 | \} )}% 34 | \@pvspace{8.0pt}% 35 | \begin{lcom}{0}% 36 | \begin{cpar}{0}{F}{F}{0}{0}{}% 37 | 3 shards, 3 transactions 38 | \end{cpar}% 39 | \vshade{5.0}% 40 | \begin{cpar}{0}{F}{F}{0}{0}{}% 41 | Checking indirect dependency loops 42 | \end{cpar}% 43 | \end{lcom}% 44 | \@x{ T3 \.{\defeq} SetAsFun ( \{}% 45 | \@x{\@s{8.2} Pair (\@w{T1} ,\, \{\@w{X} ,\,\@w{Y} \} ) ,\,}% 46 | \@x{\@s{8.2} Pair (\@w{T2} ,\, \{\@w{Y} ,\,\@w{Z} \} ) ,\,}% 47 | \@x{\@s{8.2} Pair (\@w{T3} ,\, \{\@w{Z} ,\,\@w{X} \} ) \} )}% 48 | \@x{ S3 \.{\defeq} SetAsFun ( \{}% 49 | \@x{\@s{8.2} Pair (\@w{X} ,\, \{\@w{X1} ,\,\@w{X2} \} ) ,\,}% 50 | \@x{\@s{8.2} Pair (\@w{Y} ,\, \{\@w{Y1} ,\,\@w{Y2} \} ) ,\,}% 51 | \@x{\@s{8.2} Pair (\@w{Z} ,\, \{\@w{Z1} ,\,\@w{Z2} \} ) \} )}% 52 | \@pvspace{8.0pt}% 53 | \begin{lcom}{0}% 54 | \begin{cpar}{0}{F}{F}{0}{0}{}% 55 | Initial state for \ensuremath{Apalache} testing 56 | \end{cpar}% 57 | \end{lcom}% 58 | \@x{ CInit \.{\defeq}}% 59 | \@x{\@s{8.2} \.{\land} Txns \.{:=} T3}% 60 | \@x{\@s{8.2} \.{\land} Shards \.{:=} S3}% 61 | \@pvspace{8.0pt}% 62 | \begin{lcom}{0}% 63 | \begin{cpar}{0}{F}{F}{0}{0}{}% 64 | Credit to https:\ensuremath{\.{\slsl}github.com}/tlaplus/examples 65 | \end{cpar}% 66 | \end{lcom}% 67 | \@x{ TransitiveClosure ( R ) \.{\defeq}}% 68 | \@x{\@s{8.2} \.{\LET} S \.{\defeq} \{ r [ 1 ] \.{:} r \.{\in} R \} \.{\cup} 69 | \{ r [ 2 ] \.{:} r \.{\in} R \}}% 70 | \@x{\@s{8.2} {\RECURSIVE} TCR ( \_ )}% 71 | \@x{\@s{12.29} TCR ( T ) \.{\defeq} {\IF} T \.{=} \{ \}}% 72 | \@x{\@s{20.5} \.{\THEN} R}% 73 | \@x{\@s{20.5} \.{\ELSE} \.{\LET} r \.{\defeq} {\CHOOSE} s \.{\in} T \.{:} 74 | {\TRUE}}% 75 | \@x{\@s{24.6} RR \.{\defeq} TCR ( T \.{\,\backslash\,} \{ r \} )}% 76 | \@x{\@s{20.5} \.{\IN}\@s{4.10} RR \.{\cup} \{ {\langle} s ,\, t {\rangle} 77 | \.{\in} S \.{\times} S \.{:}}% 78 | \@x{\@s{24.6} {\langle} s ,\, r {\rangle} \.{\in} RR \.{\land} {\langle} r 79 | ,\, t {\rangle} \.{\in} RR \}}% 80 | \@x{\@s{8.2} \.{\IN}\@s{4.09} TCR ( S )}% 81 | \@pvspace{8.0pt}% 82 | \@x{ TransactionOrdering \.{\defeq} \.{\LET}}% 83 | \@x{\@s{8.2} F ( acc ,\, tid ) \.{\defeq} acc \.{\cup} ( Range ( 84 | Coordinator\_txn\_state [ tid ] ) \.{\times} \{ tid \} )}% 85 | \@x{\@s{8.2} Base \.{\defeq} ApaFoldSet ( F ,\, \{ \} ,\, TIDs )}% 86 | \@x{\@s{8.2} \.{\IN} TransitiveClosure ( Base )}% 87 | \@pvspace{8.0pt}% 88 | \@x{ RecoveryCommitted ( S ) \.{\defeq}}% 89 | \@x{\@s{8.2} \{ t \.{\in} TIDs \.{:}}% 90 | \@x{\@s{12.29} \A\, r \.{\in} S \.{:}}% 91 | \@x{\@s{12.29} KeyLookup [ r ] \.{\in} Txns [ t ]}% 92 | \@x{\@s{12.29} \.{\implies} \.{\lor} Replicas [ r ] . locked \.{\land} 93 | Replicas [ r ] . logged \.{=} t}% 94 | \@x{\@s{12.29} \.{\lor} Replicas [ r ] . version \.{=} t}% 95 | \@x{\@s{12.29} \.{\lor} {\langle} t ,\, Replicas [ r ] . version {\rangle} 96 | \.{\in} TransactionOrdering}% 97 | \@x{\@s{8.2} \}}% 98 | \@pvspace{8.0pt}% 99 | \begin{lcom}{0}% 100 | \begin{cpar}{0}{F}{F}{0}{0}{}% 101 | Every transaction committed during recovery preserves linearisability 102 | \end{cpar}% 103 | \end{lcom}% 104 | \@x{ Safety\_recovery \.{\defeq}}% 105 | \@x{\@s{8.2} \A\, S \.{\in} {\SUBSET} RIDs \.{:}}% 106 | \@x{\@s{8.2}}% 107 | \@y{\@s{0}% 108 | Valid recovery 109 | }% 110 | \@xx{}% 111 | \@x{\@s{8.2} ( \A\, k \.{\in} {\DOMAIN} Shards \.{:} \E\, r \.{\in} S \.{:} r 112 | \.{\in} Shards [ k ] )}% 113 | \@x{\@s{8.2} \.{\implies} Linearisability ( CommittedTIDs \.{\cup} 114 | RecoveryCommitted ( S ) )}% 115 | \@pvspace{8.0pt}% 116 | \@x{ RecoveryAborted ( S ) \.{\defeq}}% 117 | \@x{\@s{8.2} \{ t \.{\in} TIDs \.{:}}% 118 | \@x{\@s{12.29} \E\, r \.{\in} S \.{:}}% 119 | \@x{\@s{12.29} \.{\land} KeyLookup [ r ] \.{\in} Txns [ t ]}% 120 | \@x{\@s{12.29} \.{\land} \.{\lor} {\lnot} Replicas [ r ] . locked}% 121 | \@x{\@s{12.29} \.{\lor} Replicas [ r ] . locked \.{\land} Replicas [ r ] . 122 | logged \.{\neq} t \}}% 123 | \@pvspace{8.0pt}% 124 | \begin{lcom}{0}% 125 | \begin{cpar}{0}{F}{F}{0}{0}{}% 126 | Every committed or aborted transaction results in the same recovery decision 127 | \end{cpar}% 128 | \end{lcom}% 129 | \@x{ Durability \.{\defeq}}% 130 | \@x{\@s{8.2} \A\, S \.{\in} {\SUBSET} RIDs \.{:}}% 131 | \@x{\@s{8.2}}% 132 | \@y{\@s{0}% 133 | Valid recovery 134 | }% 135 | \@xx{}% 136 | \@x{\@s{8.2} ( \A\, k \.{\in} {\DOMAIN} Shards \.{:} \E\, r \.{\in} S \.{:} r 137 | \.{\in} Shards [ k ] )}% 138 | \@x{\@s{8.2} \.{\implies}}% 139 | \@x{\@s{8.2} \A\, t \.{\in} TIDs \.{:}}% 140 | \@x{\@s{8.2} \.{\land} t \.{\in} CommittedTIDs \.{\implies} t \.{\in} 141 | RecoveryCommitted ( S )}% 142 | \@x{\@s{8.2} \.{\land} t \.{\in} AbortedTIDs \.{\implies} t \.{\in} 143 | RecoveryAborted ( S )}% 144 | \@pvspace{8.0pt}% 145 | \begin{lcom}{0}% 146 | \begin{cpar}{0}{F}{F}{0}{0}{}% 147 | Since recovery stops every replica it uses, an explicit recovery check is 148 | unnecessary 149 | since that is equivalent to just checking that every possible recovery using 150 | the current 151 | state preserves the invariants. 152 | \end{cpar}% 153 | \end{lcom}% 154 | \@x{ Invs \.{\defeq}}% 155 | \@x{\@s{8.2} \.{\land} Safety\_recovery}% 156 | \@x{\@s{8.2} \.{\land} Durability}% 157 | \@pvspace{8.0pt}% 158 | \@x{}\bottombar\@xx{}% 159 | \end{document} 160 | --------------------------------------------------------------------------------