@ -0,0 +1,37 @@ | |||||
# we need python2 support, which was dropped after buster: | |||||
FROM debian:buster | |||||
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections | |||||
RUN apt-get update | |||||
RUN apt-get install -y apt-utils | |||||
# Install and configure locale `en_US.UTF-8` | |||||
RUN apt-get install -y locales && \ | |||||
sed -i -e "s/# $en_US.*/en_US.UTF-8 UTF-8/" /etc/locale.gen && \ | |||||
dpkg-reconfigure --frontend=noninteractive locales && \ | |||||
update-locale LANG=en_US.UTF-8 | |||||
ENV LANG=en_US.UTF-8 | |||||
RUN apt-get update | |||||
RUN apt-get install -y git python2 python-pip g++ cmake python-ply python-tk tix pkg-config libssl-dev python-setuptools | |||||
# create a user: | |||||
RUN useradd -ms /bin/bash user | |||||
USER user | |||||
WORKDIR /home/user | |||||
RUN git clone --recurse-submodules https://github.com/kenmcmil/ivy.git | |||||
WORKDIR /home/user/ivy/ | |||||
RUN git checkout 271ee38980699115508eb90a0dd01deeb750a94b | |||||
RUN python2.7 build_submodules.py | |||||
RUN mkdir -p "/home/user/python/lib/python2.7/site-packages" | |||||
ENV PYTHONPATH="/home/user/python/lib/python2.7/site-packages" | |||||
# need to install pyparsing manually because otherwise wrong version found | |||||
RUN pip install pyparsing | |||||
RUN python2.7 setup.py install --prefix="/home/user/python/" | |||||
ENV PATH=$PATH:"/home/user/python/bin/" | |||||
WORKDIR /home/user/tendermint-proof/ | |||||
ENTRYPOINT ["/home/user/tendermint-proof/check_proofs.sh"] | |||||
@ -0,0 +1,27 @@ | |||||
``` | |||||
Copyright (c) 2020 Galois, Inc. | |||||
SPDX-License-Identifier: Apache-2.0 | |||||
``` | |||||
This folder contains: | |||||
* `tendermint.ivy`, a specification of Tendermint algorithm as described in *The latest gossip on BFT consensus* by E. Buchman, J. Kwon, Z. Milosevic. | |||||
* `abstract_tendermint.ivy`, a more abstract specification of Tendermint that is more verification-friendly. | |||||
* `classic_safety.ivy`, a proof that Tendermint satisfies the classic safety property of BFT consensus: if every two quorums have a well-behaved node in common, then no two well-behaved nodes ever disagree. | |||||
* `accountable_safety_1.ivy`, a proof that, assuming every quorum contains at least one well-behaved node, if two well-behaved nodes disagree, then there is evidence demonstrating at least f+1 nodes misbehaved. | |||||
* `accountable_safety_2.ivy`, a proof that, regardless of any assumption about quorums, well-behaved nodes cannot be framed by malicious nodes. In other words, malicious nodes can never construct evidence that incriminates a well-behaved node. | |||||
* `network_shim.ivy`, the network model and a convenience `shim` object to interface with the Tendermint specification. | |||||
* `domain_model.ivy`, a specification of the domain model underlying the Tendermint specification, i.e. rounds, value, quorums, etc. | |||||
All specifications and proofs are written in [Ivy](https://github.com/kenmcmil/ivy). | |||||
The license above applies to all files in this folder. | |||||
# Building and running | |||||
The easiest way to check the proofs is to use [Docker](https://www.docker.com/). | |||||
1. Install [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/). | |||||
2. Build a Docker image: `docker-compose build` | |||||
3. Run the proofs inside the Docker container: `docker-compose run | |||||
tendermint-proof`. This will check all the proofs with the `ivy_check` | |||||
command and write the output of `ivy_check` to a subdirectory of `./output/' |
@ -0,0 +1,173 @@ | |||||
#lang ivy1.7 | |||||
# --- | |||||
# layout: page | |||||
# title: Abstract specification of Tendermint in Ivy | |||||
# --- | |||||
# Here we define an abstract version of the Tendermint specification. We use | |||||
# two main forms of abstraction: a) We abstract over how information is | |||||
# transmitted (there is no network). b) We abstract functions using relations. | |||||
# For example, we abstract over a node's current round, instead only tracking | |||||
# with a relation which rounds the node has left. We do something similar for | |||||
# the `lockedRound` variable. This is in order to avoid using a function from | |||||
# node to round, and it allows us to emit verification conditions that are | |||||
# efficiently solvable by Z3. | |||||
# This specification also defines the observations that are used to adjudicate | |||||
# misbehavior. Well-behaved nodes faithfully observe every message that they | |||||
# use to take a step, while Byzantine nodes can fake observations about | |||||
# themselves (including withholding observations). Misbehavior is defined using | |||||
# the collection of all observations made (in reality, those observations must | |||||
# be collected first, but we do not model this process). | |||||
include domain_model | |||||
module abstract_tendermint = { | |||||
# Protocol state | |||||
# ############## | |||||
relation left_round(N:node, R:round) | |||||
relation prevoted(N:node, R:round, V:value) | |||||
relation precommitted(N:node, R:round, V:value) | |||||
relation decided(N:node, R:round, V:value) | |||||
relation locked(N:node, R:round, V:value) | |||||
# Accountability relations | |||||
# ######################## | |||||
relation observed_prevoted(N:node, R:round, V:value) | |||||
relation observed_precommitted(N:node, R:round, V:value) | |||||
# relations that are defined in terms of the previous two: | |||||
relation observed_equivocation(N:node) | |||||
relation observed_unlawful_prevote(N:node) | |||||
relation agreement | |||||
object defs = { # we hide those definitions and use them only when needed | |||||
private { | |||||
definition [observed_equivocation_def] observed_equivocation(N) = exists V1,V2,R . | |||||
V1 ~= V2 & (observed_precommitted(N,R,V1) & observed_precommitted(N,R,V2) | observed_prevoted(N,R,V1) & observed_prevoted(N,R,V2)) | |||||
definition [observed_unlawful_prevote_def] observed_unlawful_prevote(N) = exists V1,V2,R1,R2 . | |||||
V1 ~= value.nil & V2 ~= value.nil & V1 ~= V2 & R1 < R2 & observed_precommitted(N,R1,V1) & observed_prevoted(N,R2,V2) | |||||
& forall Q,R . R1 <= R & R < R2 & nset.is_quorum(Q) -> exists N2 . nset.member(N2,Q) & ~observed_prevoted(N2,R,V2) | |||||
definition [agreement_def] agreement = forall N1,N2,R1,R2,V1,V2 . well_behaved(N1) & well_behaved(N2) & decided(N1,R1,V1) & decided(N2,R2,V2) -> V1 = V2 | |||||
} | |||||
} | |||||
# Protocol transitions | |||||
# #################### | |||||
after init { | |||||
left_round(N,R) := R < 0; | |||||
prevoted(N,R,V) := false; | |||||
precommitted(N,R,V) := false; | |||||
decided(N,R,V) := false; | |||||
locked(N,R,V) := false; | |||||
observed_prevoted(N,R,V) := false; | |||||
observed_precommitted(N,R,V) := false; | |||||
} | |||||
# Actions are named after the corresponding line numbers in the Tendermint | |||||
# arXiv paper. | |||||
action l_11(n:node, r:round) = { # start round r | |||||
require ~left_round(n,r); | |||||
left_round(n,R) := R < r; | |||||
} | |||||
action l_22(n:node, rp:round, v:value) = { | |||||
require ~left_round(n,rp); | |||||
require ~prevoted(n,rp,V) & ~precommitted(n,rp,V); | |||||
require (forall R,V . locked(n,R,V) -> V = v) | v = value.nil; | |||||
prevoted(n, rp, v) := true; | |||||
left_round(n, R) := R < rp; # leave all lower rounds. | |||||
observed_prevoted(n, rp, v) := observed_prevoted(n, rp, v) | well_behaved(n); # the node observes itself | |||||
} | |||||
action l_28(n:node, rp:round, v:value, vr:round, q:nset) = { | |||||
require ~left_round(n,rp) & ~prevoted(n,rp,V); | |||||
require ~prevoted(n,rp,V) & ~precommitted(n,rp,V); | |||||
require vr < rp; | |||||
require nset.is_quorum(q) & (forall N . nset.member(N,q) -> (prevoted(N,vr,v) | ~well_behaved(N))); | |||||
var proposal:value; | |||||
if value.valid(v) & ((forall R0,V0 . locked(n,R0,V0) -> R0 <= vr) | (forall R,V . locked(n,R,V) -> V = v)) { | |||||
proposal := v; | |||||
} | |||||
else { | |||||
proposal := value.nil; | |||||
}; | |||||
prevoted(n, rp, proposal) := true; | |||||
left_round(n, R) := R < rp; # leave all lower rounds | |||||
observed_prevoted(N, vr, v) := observed_prevoted(N, vr, v) | (well_behaved(n) & nset.member(N,q)); # the node observes the prevotes of quorum q | |||||
observed_prevoted(n, rp, proposal) := observed_prevoted(n, rp, proposal) | well_behaved(n); # the node observes itself | |||||
} | |||||
action l_36(n:node, rp:round, v:value, q:nset) = { | |||||
require v ~= value.nil; | |||||
require ~left_round(n,rp); | |||||
require exists V . prevoted(n,rp,V); | |||||
require ~precommitted(n,rp,V); | |||||
require nset.is_quorum(q) & (forall N . nset.member(N,q) -> (prevoted(N,rp,v) | ~well_behaved(N))); | |||||
precommitted(n, rp, v) := true; | |||||
left_round(n, R) := R < rp; # leave all lower rounds | |||||
locked(n,R,V) := R <= rp & V = v; | |||||
observed_prevoted(N, rp, v) := observed_prevoted(N, rp, v) | (well_behaved(n) & nset.member(N,q)); # the node observes the prevotes of quorum q | |||||
observed_precommitted(n, rp, v) := observed_precommitted(n, rp, v) | well_behaved(n); # the node observes itself | |||||
} | |||||
action l_44(n:node, rp:round, q:nset) = { | |||||
require ~left_round(n,rp); | |||||
require ~precommitted(n,rp,V); | |||||
require nset.is_quorum(q) & (forall N .nset.member(N,q) -> (prevoted(N,rp,value.nil) | ~well_behaved(N))); | |||||
precommitted(n, rp, value.nil) := true; | |||||
left_round(n, R) := R < rp; # leave all lower rounds | |||||
observed_prevoted(N, rp, value.nil) := observed_prevoted(N, rp, value.nil) | (well_behaved(n) & nset.member(N,q)); # the node observes the prevotes of quorum q | |||||
observed_precommitted(n, rp, value.nil) := observed_precommitted(n, rp, value.nil) | well_behaved(n); # the node observes itself | |||||
} | |||||
action l_57(n:node, rp:round) = { | |||||
require ~left_round(n,rp); | |||||
require ~prevoted(n,rp,V); | |||||
prevoted(n, rp, value.nil) := true; | |||||
left_round(n, R) := R < rp; # leave all lower rounds | |||||
observed_prevoted(n, rp, value.nil) := observed_prevoted(n, rp, value.nil) | well_behaved(n); # the node observes itself | |||||
} | |||||
action l_61(n:node, rp:round) = { | |||||
require ~left_round(n,rp); | |||||
require ~precommitted(n,rp,V); | |||||
precommitted(n, rp, value.nil) := true; | |||||
left_round(n, R) := R < rp; # leave all lower rounds | |||||
observed_precommitted(n, rp, value.nil) := observed_precommitted(n, rp, value.nil) | well_behaved(n); # the node observes itself | |||||
} | |||||
action decide(n:node, r:round, v:value, q:nset) = { | |||||
require v ~= value.nil; | |||||
require nset.is_quorum(q) & (forall N . nset.member(N, q) -> (precommitted(N, r, v) | ~well_behaved(N))); | |||||
decided(n, r, v) := true; | |||||
observed_precommitted(N, r, v) := observed_precommitted(N, r, v) | (well_behaved(n) & nset.member(N,q)); # the node observes the precommits of quorum q | |||||
} | |||||
action misbehave = { | |||||
# Byzantine nodes can claim they observed whatever they want about themselves, | |||||
# but they cannot remove observations. | |||||
observed_prevoted(N,R,V) := *; | |||||
assume (old observed_prevoted(N,R,V)) -> observed_prevoted(N,R,V); | |||||
assume well_behaved(N) -> old observed_prevoted(N,R,V) = observed_prevoted(N,R,V); | |||||
observed_precommitted(N,R,V) := *; | |||||
assume (old observed_precommitted(N,R,V)) -> observed_precommitted(N,R,V); | |||||
assume well_behaved(N) -> old observed_precommitted(N,R,V) = observed_precommitted(N,R,V); | |||||
} | |||||
} |
@ -0,0 +1,144 @@ | |||||
#lang ivy1.7 | |||||
# --- | |||||
# layout: page | |||||
# title: Proof of Classic Safety | |||||
# --- | |||||
include tendermint | |||||
include abstract_tendermint | |||||
# Here we prove the first accountability property: if two well-behaved nodes | |||||
# disagree, then there are two quorums Q1 and Q2 such that all members of the | |||||
# intersection of Q1 and Q2 have violated the accountability properties. | |||||
# The proof is done in two steps: first we prove the abstract specification | |||||
# satisfies the property, and then we show by refinement that this property | |||||
# also holds in the concrete specification. | |||||
# To see what is checked in the refinement proof, use `ivy_show isolate=accountable_safety_1 accountable_safety_1.ivy` | |||||
# To see what is checked in the abstract correctness proof, use `ivy_show isolate=abstract_accountable_safety_1 accountable_safety_1.ivy` | |||||
# To check the whole proof, use `ivy_check accountable_safety_1.ivy`. | |||||
# Proof of the accountability property in the abstract specification | |||||
# ================================================================== | |||||
# We prove with tactics (see `lemma_1` and `lemma_2`) that, if some basic | |||||
# invariants hold (see `invs` below), then the accountability property holds. | |||||
isolate abstract_accountable_safety = { | |||||
instantiate abstract_tendermint | |||||
# The main property | |||||
# ----------------- | |||||
# If there is disagreement, then there is evidence that a third of the nodes | |||||
# have violated the protocol: | |||||
invariant [accountability] agreement | exists Q1,Q2 . nset.is_quorum(Q1) & nset.is_quorum(Q2) & (forall N . nset.member(N,Q1) & nset.member(N,Q2) -> observed_equivocation(N) | observed_unlawful_prevote(N)) | |||||
proof { | |||||
apply lemma_2.thm # this reduces to goal to three subgoals: p1, p2, and p3 (see their definition below) | |||||
proof [p1] { | |||||
assume invs.inv1 | |||||
} | |||||
proof [p2] { | |||||
assume invs.inv2 | |||||
} | |||||
proof [p3] { | |||||
assume invs.inv3 | |||||
} | |||||
} | |||||
# The invariants | |||||
# -------------- | |||||
isolate invs = { | |||||
# well-behaved nodes observe their own actions faithfully: | |||||
invariant [inv1] well_behaved(N) -> (observed_precommitted(N,R,V) = precommitted(N,R,V)) | |||||
# if a value is precommitted by a well-behaved node, then a quorum is observed to prevote it: | |||||
invariant [inv2] (exists N . well_behaved(N) & precommitted(N,R,V)) & V ~= value.nil -> exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_prevoted(N2,R,V) | |||||
# if a value is decided by a well-behaved node, then a quorum is observed to precommit it: | |||||
invariant [inv3] (exists N . well_behaved(N) & decided(N,R,V)) -> 0 <= R & V ~= value.nil & exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_precommitted(N2,R,V) | |||||
private { | |||||
invariant (precommitted(N,R,V) | prevoted(N,R,V)) -> 0 <= R | |||||
invariant R < 0 -> left_round(N,R) | |||||
} | |||||
} with this, nset, round, accountable_bft.max_2f_byzantine | |||||
# The theorems proved with tactics | |||||
# -------------------------------- | |||||
# Using complete induction on rounds, we prove that, assuming that the | |||||
# invariants inv1, inv2, and inv3 hold, the accountability property holds. | |||||
# For technical reasons, we separate the proof in two steps | |||||
isolate lemma_1 = { | |||||
# complete induction is not built-in, so we introduce it with an axiom. Note that this only holds for a type where 0 is the smallest element | |||||
axiom [complete_induction] { | |||||
relation p(X:round) | |||||
{ # base case | |||||
property p(0) | |||||
} | |||||
{ # inductive step: show that if the property is true for all X lower or equal to x and y=x+1, then the property is true of y | |||||
individual a:round | |||||
individual b:round | |||||
property (forall X. 0 <= X & X <= a -> p(X)) & round.succ(a,b) -> p(b) | |||||
} | |||||
#-------------------------- | |||||
property forall X . 0 <= X -> p(X) | |||||
} | |||||
# the main lemma: if inv1 and inv2 below hold and a quorum is observed to | |||||
# precommit V1 at R1 and another quorum is observed to precommit V2~=V1 at | |||||
# R2>=R1, then the intersection of two quorums (i.e. f+1 nodes) is observed to | |||||
# violate the protocol | |||||
theorem [thm] { | |||||
property [p1] forall N,R,V . well_behaved(N) -> (observed_precommitted(N,R,V) = precommitted(N,R,V)) | |||||
property [p2] forall R,V . (exists N . well_behaved(N) & precommitted(N,R,V)) -> V = value.nil | exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_prevoted(N2,R,V) | |||||
#----------------------------------------------------------------------------------------------------------------------- | |||||
property forall R2. 0 <= R2 -> ((exists V2,Q1,R1,V1,Q1 . V1 ~= value.nil & V2 ~= value.nil & V1 ~= V2 & 0 <= R1 & R1 <= R2 & nset.is_quorum(Q1) & (forall N . nset.member(N,Q1) -> observed_precommitted(N,R1,V1)) & (exists Q2 . nset.is_quorum(Q2) & forall N . nset.member(N,Q2) -> observed_prevoted(N,R2,V2))) -> exists Q1,Q2 . nset.is_quorum(Q1) & nset.is_quorum(Q2) & forall N . nset.member(N,Q1) & nset.member(N,Q2) -> observed_equivocation(N) | observed_unlawful_prevote(N)) | |||||
} | |||||
proof { | |||||
apply complete_induction # the two subgoals (base case and inductive case) are then discharged automatically | |||||
} | |||||
} with this, round, nset, accountable_bft.max_2f_byzantine, defs.observed_equivocation_def, defs.observed_unlawful_prevote_def | |||||
# Now we put lemma_1 in a form that matches exactly the accountability property | |||||
# we want to prove. This is a bit cumbersome and could probably be improved. | |||||
isolate lemma_2 = { | |||||
theorem [thm] { | |||||
property [p1] forall N,R,V . well_behaved(N) -> (observed_precommitted(N,R,V) = precommitted(N,R,V)) | |||||
property [p2] forall R,V . (exists N . well_behaved(N) & precommitted(N,R,V)) & V ~= value.nil -> exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_prevoted(N2,R,V) | |||||
property [p3] forall R,V. (exists N . well_behaved(N) & decided(N,R,V)) -> 0 <= R & V ~= value.nil & exists Q . nset.is_quorum(Q) & forall N2 . nset.member(N2,Q) -> observed_precommitted(N2,R,V) | |||||
#------------------------------------------------------------------------------------------------------------------------------------------- | |||||
property agreement | exists Q1,Q2 . nset.is_quorum(Q1) & nset.is_quorum(Q2) & forall N . nset.member(N,Q1) & nset.member(N,Q2) -> observed_equivocation(N) | observed_unlawful_prevote(N) | |||||
} | |||||
proof { | |||||
assume lemma_1.thm | |||||
} | |||||
} with this, round, defs.agreement_def, lemma_1, nset, accountable_bft.max_2f_byzantine | |||||
} with round | |||||
# The final proof | |||||
# =============== | |||||
isolate accountable_safety_1 = { | |||||
# First we instantiate the concrete protocol: | |||||
instantiate tendermint(abstract_accountable_safety) | |||||
# We then define what we mean by agreement | |||||
relation agreement | |||||
definition [agreement_def] agreement = forall N1,N2. well_behaved(N1) & well_behaved(N2) & server.decision(N1) ~= value.nil & server.decision(N2) ~= value.nil -> server.decision(N1) = server.decision(N2) | |||||
invariant abstract_accountable_safety.agreement -> agreement | |||||
invariant [accountability] agreement | exists Q1,Q2 . nset.is_quorum(Q1) & nset.is_quorum(Q2) & forall N . nset.member(N,Q1) & nset.member(N,Q2) -> abstract_accountable_safety.observed_equivocation(N) | abstract_accountable_safety.observed_unlawful_prevote(N) | |||||
} with value, round, proposers, shim, abstract_accountable_safety, abstract_accountable_safety.defs.agreement_def, accountable_safety_1.agreement_def |
@ -0,0 +1,52 @@ | |||||
#lang ivy1.7 | |||||
include tendermint | |||||
include abstract_tendermint | |||||
# Here we prove the second accountability property: no well-behaved node is | |||||
# every observed to violate the accountability properties. | |||||
# The proof is done in two steps: first we prove the the abstract specification | |||||
# satisfies the property, and then we show by refinement that this property | |||||
# also holds in the concrete specification. | |||||
# To see what is checked in the refinement proof, use `ivy_show isolate=accountable_safety_2 accountable_safety_2.ivy` | |||||
# To see what is checked in the abstract correctness proof, use `ivy_show isolate=abstract_accountable_safety_2 accountable_safety_2.ivy` | |||||
# To check the whole proof, use `ivy_check accountable_safety_2.ivy`. | |||||
# Proof that the property holds in the abstract specification | |||||
# ============================================================ | |||||
isolate abstract_accountable_safety_2 = { | |||||
instantiate abstract_tendermint | |||||
# the main property: | |||||
invariant [wb_never_punished] well_behaved(N) -> ~(observed_equivocation(N) | observed_unlawful_prevote(N)) | |||||
# the main invariant for proving wb_not_punished: | |||||
invariant well_behaved(N) & precommitted(N,R,V) & ~locked(N,R,V) & V ~= value.nil -> exists R2,V2 . V2 ~= value.nil & R < R2 & precommitted(N,R2,V2) & locked(N,R2,V2) | |||||
invariant (exists N . well_behaved(N) & precommitted(N,R,V) & V ~= value.nil) -> exists Q . nset.is_quorum(Q) & forall N . nset.member(N,Q) -> observed_prevoted(N,R,V) | |||||
invariant well_behaved(N) -> (observed_prevoted(N,R,V) <-> prevoted(N,R,V)) | |||||
invariant well_behaved(N) -> (observed_precommitted(N,R,V) <-> precommitted(N,R,V)) | |||||
# nodes stop prevoting or precommitting in lower rounds when doing so in a higher round: | |||||
invariant well_behaved(N) & prevoted(N,R2,V2) & R1 < R2 -> left_round(N,R1) | |||||
invariant well_behaved(N) & locked(N,R2,V2) & R1 < R2 -> left_round(N,R1) | |||||
invariant [precommit_unique_per_round] well_behaved(N) & precommitted(N,R,V1) & precommitted(N,R,V2) -> V1 = V2 | |||||
} with nset, round, abstract_accountable_safety_2.defs.observed_equivocation_def, abstract_accountable_safety_2.defs.observed_unlawful_prevote_def | |||||
# Proof that the property holds in the concrete specification | |||||
# =========================================================== | |||||
isolate accountable_safety_2 = { | |||||
instantiate tendermint(abstract_accountable_safety_2) | |||||
invariant well_behaved(N) -> ~(abstract_accountable_safety_2.observed_equivocation(N) | abstract_accountable_safety_2.observed_unlawful_prevote(N)) | |||||
} with round, value, shim, abstract_accountable_safety_2, abstract_accountable_safety_2.defs.observed_equivocation_def, abstract_accountable_safety_2.defs.observed_unlawful_prevote_def |
@ -0,0 +1,38 @@ | |||||
#!/bin/bash | |||||
# returns non-zero error code if any proof fails | |||||
success=0 | |||||
log_dir=$(cat /dev/urandom | tr -cd 'a-f0-9' | head -c 6) | |||||
mkdir -p output/$log_dir | |||||
echo "Checking classic safety:" | |||||
res=$(ivy_check classic_safety.ivy | tee "output/$log_dir/classic_safety.txt" | tail -n 1) | |||||
if [ "$res" = "OK" ]; then | |||||
echo "OK" | |||||
else | |||||
echo "FAILED" | |||||
success=1 | |||||
fi | |||||
echo "Checking accountable safety 1:" | |||||
res=$(ivy_check complete=fo accountable_safety_1.ivy | tee "output/$log_dir/accountable_safety_1.txt" | tail -n 1) | |||||
if [ "$res" = "OK" ]; then | |||||
echo "OK" | |||||
else | |||||
echo "FAILED" | |||||
success=1 | |||||
fi | |||||
echo "Checking accountable safety 2:" | |||||
res=$(ivy_check complete=fo accountable_safety_2.ivy | tee "output/$log_dir/accountable_safety_2.txt" | tail -n 1) | |||||
if [ "$res" = "OK" ]; then | |||||
echo "OK" | |||||
else | |||||
echo "FAILED" | |||||
success=1 | |||||
fi | |||||
echo | |||||
echo "See ivy_check output in the output/ folder" | |||||
exit $success |
@ -0,0 +1,85 @@ | |||||
#lang ivy1.7 | |||||
# --- | |||||
# layout: page | |||||
# title: Proof of Classic Safety | |||||
# --- | |||||
include tendermint | |||||
include abstract_tendermint | |||||
# Here we prove the classic safety property: assuming that every two quorums | |||||
# have a well-behaved node in common, no two well-behaved nodes ever disagree. | |||||
# The proof is done in two steps: first we prove the the abstract specification | |||||
# satisfies the property, and then we show by refinement that this property | |||||
# also holds in the concrete specification. | |||||
# To see what is checked in the refinement proof, use `ivy_show isolate=classic_safety classic_safety.ivy` | |||||
# To see what is checked in the abstract correctness proof, use `ivy_show isolate=abstract_classic_safety classic_safety.ivy` | |||||
# To check the whole proof, use `ivy_check classic_safety.ivy`. | |||||
# Note that all the verification conditions sent to Z3 for this proof are in | |||||
# EPR. | |||||
# Classic safety in the abstract model | |||||
# ==================================== | |||||
# We start by proving that classic safety holds in the abstract model. | |||||
isolate abstract_classic_safety = { | |||||
instantiate abstract_tendermint | |||||
invariant [classic_safety] classic_bft.quorum_intersection & decided(N1,R1,V1) & decided(N2,R2,V2) -> V1 = V2 | |||||
# The notion of choosable value | |||||
# ----------------------------- | |||||
relation choosable(R:round, V:value) | |||||
definition choosable(R,V) = exists Q . nset.is_quorum(Q) & forall N . well_behaved(N) & nset.member(N,Q) -> ~left_round(N,R) | precommitted(N,R,V) | |||||
# Main invariants | |||||
# --------------- | |||||
# `classic_safety` is inductive relative to those invariants | |||||
invariant [decision_is_quorum_precommit] (exists N1 . decided(N1,R,V)) -> exists Q. nset.is_quorum(Q) & forall N2. well_behaved(N2) & nset.member(N2, Q) -> precommitted(N2,R,V) | |||||
invariant [precommitted_is_quorum_prevote] V ~= value.nil & (exists N1 . precommitted(N1,R,V)) -> exists Q. nset.is_quorum(Q) & forall N2. well_behaved(N2) & nset.member(N2, Q) -> prevoted(N2,R,V) | |||||
invariant [prevote_unique_per_round] prevoted(N,R,V1) & prevoted(N,R,V2) -> V1 = V2 | |||||
# This is the core invariant: as long as a precommitted value is still choosable, it remains protected by a lock and prevents any new value from being prevoted: | |||||
invariant [locks] classic_bft.quorum_intersection & V ~= value.nil & precommitted(N,R,V) & choosable(R,V) -> locked(N,R,V) & forall R2,V2 . R < R2 & prevoted(N,R2,V2) -> V2 = V | V2 = value.nil | |||||
# Supporting invariants | |||||
# --------------------- | |||||
# The main invariants are inductive relative to those | |||||
invariant decided(N,R,V) -> V ~= value.nil | |||||
invariant left_round(N,R2) & R1 < R2 -> left_round(N,R1) # if a node left round R2>R1, then it also left R1: | |||||
invariant prevoted(N,R2,V2) & R1 < R2 -> left_round(N,R1) | |||||
invariant precommitted(N,R2,V2) & R1 < R2 -> left_round(N,R1) | |||||
} with round, nset, classic_bft.quorum_intersection_def | |||||
# The refinement proof | |||||
# ==================== | |||||
# Now, thanks to the refinement relation that we establish in | |||||
# `concrete_tendermint.ivy`, we prove that classic safety transfers to the | |||||
# concrete specification: | |||||
isolate classic_safety = { | |||||
# We instantiate the `tendermint` module providing `abstract_classic_safety` as abstract model. | |||||
instantiate tendermint(abstract_classic_safety) | |||||
# We prove that if every two quorums have a well-behaved node in common, | |||||
# then well-behaved nodes never disagree: | |||||
invariant [classic_safety] classic_bft.quorum_intersection & server.decision(N1) ~= value.nil & server.decision(N2) ~= value.nil -> server.decision(N1) = server.decision(N2) | |||||
} with value, round, proposers, shim, abstract_classic_safety # here we list all the specifications that we rely on for this proof |
@ -0,0 +1,8 @@ | |||||
version: '3' | |||||
services: | |||||
tendermint-proof: | |||||
build: . | |||||
volumes: | |||||
- ./:/home/user/tendermint-proof:ro | |||||
- ./output:/home/user/tendermint-proof/output:rw | |||||
@ -0,0 +1,90 @@ | |||||
#lang ivy1.7 | |||||
# --- | |||||
# layout: page | |||||
# title: Proof of Classic Safety | |||||
# --- | |||||
include order # this is a file from the standard library (`ivy/ivy/include/1.7/order.ivy`) | |||||
isolate round = { | |||||
type this | |||||
individual minus_one:this | |||||
relation succ(R1:round, R2:round) | |||||
action incr(i:this) returns (j:this) | |||||
specification { | |||||
# to simplify verification, we treat rounds as an abstract totally ordered set with a successor relation. | |||||
instantiate totally_ordered(this) | |||||
property minus_one < 0 | |||||
property succ(X,Z) -> (X < Z & ~(X < Y & Y < Z)) | |||||
after incr { | |||||
ensure succ(i,j) | |||||
} | |||||
} | |||||
implementation { | |||||
# here we prove that the abstraction is sound. | |||||
interpret this -> int # rounds are integers in the Tendermint specification. | |||||
definition minus_one = 0-1 | |||||
definition succ(R1,R2) = R2 = R1 + 1 | |||||
implement incr { | |||||
j := i+1; | |||||
} | |||||
} | |||||
} | |||||
instance node : iterable # nodes are a set with an order, that can be iterated over (see order.ivy in the standard library) | |||||
relation well_behaved(N:node) # whether a node is well-behaved or not. NOTE: Use only in the proof! Nodes do know know that. | |||||
isolate proposers = { | |||||
# each round has a unique proposer in Tendermint. In order to avoid a | |||||
# function from round to node (which makes verification more difficult), we | |||||
# abstract over this function using a relation. | |||||
relation is_proposer(N:node, R:round) | |||||
action get_proposer(r:round) returns (n:node) | |||||
specification { | |||||
property is_proposer(N1,R) & is_proposer(N2,R) -> N1 = N2 | |||||
after get_proposer { | |||||
ensure is_proposer(n,r); | |||||
} | |||||
} | |||||
implementation { | |||||
# here we prove that the abstraction is sound | |||||
function f(R:round):node | |||||
definition is_proposer(N,R) = N = f(R) | |||||
implement get_proposer { | |||||
n := f(r); | |||||
} | |||||
} | |||||
} | |||||
isolate value = { # the type of values | |||||
type this | |||||
relation valid(V:value) | |||||
individual nil:value | |||||
specification { | |||||
property ~valid(nil) | |||||
} | |||||
implementation { | |||||
definition valid(V) = V ~= nil | |||||
} | |||||
} | |||||
object nset = { # the type of node sets | |||||
type this # a set of N=3f+i nodes for 0<i<=3 | |||||
relation member(N:node, S:nset) # set-membership relation | |||||
relation is_quorum(S:nset) # intent: sets of cardinality at least 2f+i+1 | |||||
relation is_blocking(S:nset) # intent: at least f+1 nodes | |||||
} | |||||
object classic_bft = { | |||||
relation quorum_intersection | |||||
private { | |||||
definition [quorum_intersection_def] quorum_intersection = forall Q1,Q2. exists N. well_behaved(N) & nset.member(N, Q1) & nset.member(N, Q2) # every two quorums have a well-behaved node in common | |||||
} | |||||
} | |||||
trusted isolate accountable_bft = { | |||||
# this is our baseline assumption about quorums: | |||||
private { | |||||
property [max_2f_byzantine] exists N . well_behaved(N) & nset.member(N,Q) # every quorum has a well-behaved member | |||||
} | |||||
}# with nset |
@ -0,0 +1,140 @@ | |||||
#lang ivy1.7 | |||||
# --- | |||||
# layout: page | |||||
# title: Network model and network shim | |||||
# --- | |||||
# Here we define a network module, which is our model of the network, and a | |||||
# shim module that sits on top of the network and which, upon receiving a | |||||
# message, calls the appropriate protocol handler. | |||||
include domain_model | |||||
# Here we define an enumeration type for identifying the 3 different types of | |||||
# messages that nodes send. | |||||
object msg_kind = { # TODO: merge with step_t | |||||
type this = {proposal, prevote, precommit} | |||||
} | |||||
# Here we define the type of messages `msg`. Its members are structs with the fields described below. | |||||
object msg = { | |||||
type this = struct { | |||||
m_kind : msg_kind, | |||||
m_src : node, | |||||
m_round : round, | |||||
m_value : value, | |||||
m_vround : round | |||||
} | |||||
} | |||||
# This is our model of the network: | |||||
trusted isolate net = { | |||||
# `trusted` indicates to Ivy that this is a trusted object, i.e. we assume | |||||
# without proof that receive is only called when its requirement is true. | |||||
export action recv(dst:node,v:msg) | |||||
action send(src:node,dst:node,v:msg) | |||||
# Note that the `recv` action is exported, meaning that it can be called | |||||
# non-deterministically by the environment any time it is enabled. In other | |||||
# words, a packet that is in flight can be received at any time. In this | |||||
# sense, the network is fully asynchronous. Moreover, there is no | |||||
# requirement that a given message will be received at all. | |||||
specification { | |||||
# The state of the network consists of all the packets that have been | |||||
# sent so far, along with their destination. | |||||
relation sent(V:msg, N:node) | |||||
after init { | |||||
sent(V, N) := false | |||||
} | |||||
before send { | |||||
sent(v,dst) := true | |||||
} | |||||
before recv { | |||||
require sent(v,dst) # only sent messages can be received. | |||||
} | |||||
} | |||||
} | |||||
# The network shim sits on top of the network and, upon receiving a message, | |||||
# calls the appropriate protocol handler. It also exposes a `broadcast` action | |||||
# that sends to all nodes. | |||||
isolate shim = { | |||||
# In order not repeat the same code for each handler, we use a handler | |||||
# module parameterized by the type of message it will handle. Below we | |||||
# instantiate this module for the 3 types of messages of Tendermint | |||||
module handler(p_kind) = { | |||||
action handle(dst:node,m:msg) | |||||
object spec = { | |||||
before handle { | |||||
assert sent(m,dst) & m.m_kind = p_kind | |||||
} | |||||
} | |||||
} | |||||
instance proposal_handler : handler(msg_kind.proposal) | |||||
instance prevote_handler : handler(msg_kind.prevote) | |||||
instance precommit_handler : handler(msg_kind.precommit) | |||||
relation sent(M:msg,N:node) | |||||
action broadcast(src:node,m:msg) | |||||
action send(src:node,dst:node,m:msg) | |||||
specification { | |||||
after init { | |||||
sent(M,D) := false; | |||||
} | |||||
before broadcast { | |||||
sent(m,D) := true | |||||
} | |||||
before send { | |||||
sent(m,dst) := true | |||||
} | |||||
} | |||||
# In contrast to the network object, the shim is not a trusted component. | |||||
# Here we give an implementation of it that satisfies its specification: | |||||
implementation { | |||||
implement net.recv(dst:node,m:msg) { | |||||
if m.m_kind = msg_kind.proposal { | |||||
call proposal_handler.handle(dst,m) | |||||
} | |||||
else if m.m_kind = msg_kind.prevote { | |||||
call prevote_handler.handle(dst,m) | |||||
} | |||||
else if m.m_kind = msg_kind.proposal { | |||||
call precommit_handler.handle(dst,m) | |||||
} | |||||
} | |||||
implement broadcast { # broadcast sends to all nodes, including the sender. | |||||
var iter := node.iter.create(0); | |||||
while ~iter.is_end | |||||
invariant net.sent(M,D) -> sent(M,D) | |||||
{ | |||||
var n := iter.val; | |||||
call net.send(src,n,m); | |||||
iter := iter.next; | |||||
} | |||||
} | |||||
implement send { | |||||
call net.send(src,dst,m) | |||||
} | |||||
private { | |||||
invariant net.sent(M,D) -> sent(M,D) | |||||
} | |||||
} | |||||
} with net, node # to prove that the shim implementation satisfies the shim specification, we rely on the specification of net and node. |
@ -0,0 +1,4 @@ | |||||
# Ignore everything in this directory | |||||
* | |||||
# Except this file | |||||
!.gitignore |
@ -0,0 +1,430 @@ | |||||
#lang ivy1.7 | |||||
# --- | |||||
# layout: page | |||||
# title: Specification of Tendermint in Ivy | |||||
# --- | |||||
# This specification closely follows the pseudo-code given in "The latest | |||||
# gossip on BFT consensus" by E. Buchman, J. Kwon, Z. Milosevic | |||||
# <https://arxiv.org/abs/1807.04938> | |||||
include domain_model | |||||
include network_shim | |||||
# We model the Tendermint protocol as an Ivy object. Like in Object-Oriented | |||||
# Programming, the basic structuring unit in Ivy is the object. Objects have | |||||
# internal state and actions (i.e. methods in OO parlance) that modify their | |||||
# state. We model Tendermint as an object whose actions represent steps taken | |||||
# by individual nodes in the protocol. Actions in Ivy can have preconditions, | |||||
# and a valid execution is a sequence of actions whose preconditions are all | |||||
# satisfied in the state in which they are called. | |||||
# For technical reasons, we define below a `tendermint` module instead of an | |||||
# object. Ivy modules are a little bit like classes in OO programs, and like | |||||
# classes they can be instantiated to obtain objects. To instantiate the | |||||
# `tendermint` module, we must provide an abstract-protocol object. This allows | |||||
# us to use different abstract-protocol objects for different parts of the | |||||
# proof, and to do so without too much notational burden (we could have used | |||||
# Ivy monitors, but then we would need to prefix every variable name by the | |||||
# name of the object containing it, which clutters things a bit compared to the | |||||
# approach we took). | |||||
# The abstract-protocol object is called by the resulting tendermint object so | |||||
# as to run the abstract protocol alongside the concrete protocol. This allows | |||||
# us to transfer properties proved of the abstract protocol to the concrete | |||||
# protocol, as follows. First, we prove that running the abstract protocol in | |||||
# this way results in a valid execution of the abstract protocol. This is done | |||||
# by checking that all preconditions of the abstract actions are satisfied at | |||||
# their call sites. Second, we establish a relation between abstract state and | |||||
# concrete state (in the form of invariants of the resulting, two-object | |||||
# transition system) that allow us to transfer properties proved in the | |||||
# abstract protocol to the concrete protocol (for example, we prove that any | |||||
# decision made in the Tendermint protocol is also made in the abstract | |||||
# protocol; if the abstract protocol satisfies the agreement property, this | |||||
# allows us to conclude that the Tendermint protocol also does). | |||||
# The abstract protocol object that we will use is always the same, and only | |||||
# the abstract properties that we prove about it change in the different | |||||
# instantiations of the `tendermint` module. Thus we provide common invariants | |||||
# that a) allow to prove that the abstract preconditions are met, and b) | |||||
# provide a refinement relation (see end of the module) relating the state of | |||||
# Tendermint to the state of the abstract protocol. | |||||
# In the model, Byzantine nodes can send whatever messages they want, except | |||||
# that they cannot forge sender identities. This reflects the fact that, in | |||||
# practice, nodes use public key cryptography to sign their messages. | |||||
# Finally, note that the observations that serve to adjudicate misbehavior are | |||||
# defined only in the abstract protocol (they happen in the abstract actions). | |||||
module tendermint(abstract_protocol) = { | |||||
# the initial value of a node: | |||||
parameter init_val(N:node): value | |||||
# the three type of steps | |||||
object step_t = { | |||||
type this = {propose, prevote, precommit} | |||||
} # refer to those e.g. as step_t.propose | |||||
object server(n:node) = { | |||||
# the current round of a node | |||||
individual round_p: round | |||||
individual step: step_t | |||||
individual decision: value | |||||
individual lockedValue: value | |||||
individual lockedRound: round | |||||
individual validValue: value | |||||
individual validRound: round | |||||
relation done_l34(R:round) | |||||
relation done_l36(R:round, V:value) | |||||
relation done_l47(R:round) | |||||
# variables for scheduling request | |||||
relation propose_timer_scheduled(R:round) | |||||
relation prevote_timer_scheduled(R:round) | |||||
relation precommit_timer_scheduled(R:round) | |||||
relation _recved_proposal(Sender:node, R:round, V:value, VR:round) | |||||
relation _recved_prevote(Sender:node, R:round, V:value) | |||||
relation _recved_precommit(Sender:node, R:round, V:value) | |||||
relation _has_started | |||||
after init { | |||||
round_p := 0; | |||||
step := step_t.propose; | |||||
decision := value.nil; | |||||
lockedValue := value.nil; | |||||
lockedRound := round.minus_one; | |||||
validValue := value.nil; | |||||
validRound := round.minus_one; | |||||
done_l34(R) := false; | |||||
done_l36(R, V) := false; | |||||
done_l47(R) := false; | |||||
propose_timer_scheduled(R) := false; | |||||
prevote_timer_scheduled(R) := false; | |||||
precommit_timer_scheduled(R) := false; | |||||
_recved_proposal(Sender, R, V, VR) := false; | |||||
_recved_prevote(Sender, R, V) := false; | |||||
_recved_precommit(Sender, R, V) := false; | |||||
_has_started := false; | |||||
} | |||||
action getValue returns (v:value) = { | |||||
v := init_val(n) | |||||
} | |||||
export action start = { | |||||
assume ~_has_started; | |||||
_has_started := true; | |||||
# line 10 | |||||
call startRound(0); | |||||
} | |||||
# line 11-21 | |||||
action startRound(r:round) = { | |||||
# line 12 | |||||
round_p := r; | |||||
# line 13 | |||||
step := step_t.propose; | |||||
var proposal : value; | |||||
# line 14 | |||||
if (proposers.get_proposer(r) = n) { | |||||
if validValue ~= value.nil { # line 15 | |||||
proposal := validValue; # line 16 | |||||
} else { | |||||
proposal := getValue(); # line 18 | |||||
}; | |||||
call broadcast_proposal(r, proposal, validRound); # line 19 | |||||
} else { | |||||
propose_timer_scheduled(r) := true; # line 21 | |||||
}; | |||||
call abstract_protocol.l_11(n, r); | |||||
} | |||||
# This action, as not exported, can only be called at specific call sites. | |||||
action broadcast_proposal(r:round, v:value, vr:round) = { | |||||
var m: msg; | |||||
m.m_kind := msg_kind.proposal; | |||||
m.m_src := n; | |||||
m.m_round := r; | |||||
m.m_value := v; | |||||
m.m_vround := vr; | |||||
call shim.broadcast(n,m); | |||||
} | |||||
implement shim.proposal_handler.handle(msg:msg) { | |||||
_recved_proposal(msg.m_src, msg.m_round, msg.m_value, msg.m_vround) := true; | |||||
} | |||||
# line 22-27 | |||||
export action l_22(v:value) = { | |||||
assume _has_started; | |||||
assume _recved_proposal(proposers.get_proposer(round_p), round_p, v, round.minus_one); | |||||
assume step = step_t.propose; | |||||
if (value.valid(v) & (lockedRound = round.minus_one | lockedValue = v)) { | |||||
call broadcast_prevote(round_p, v); # line 24 | |||||
call abstract_protocol.l_22(n, round_p, v); | |||||
} else { | |||||
call broadcast_prevote(round_p, value.nil); # line 26 | |||||
call abstract_protocol.l_22(n, round_p, value.nil); | |||||
}; | |||||
# line 27 | |||||
step := step_t.prevote; | |||||
} | |||||
# line 28-33 | |||||
export action l_28(r:round, v:value, vr:round, q:nset) = { | |||||
assume _has_started; | |||||
assume r = round_p; | |||||
assume _recved_proposal(proposers.get_proposer(r), r, v, vr); | |||||
assume nset.is_quorum(q); | |||||
assume nset.member(N,q) -> _recved_prevote(N,vr,v); | |||||
assume step = step_t.propose; | |||||
assume vr >= 0 & vr < r; | |||||
# line 29 | |||||
if (value.valid(v) & (lockedRound <= vr | lockedValue = v)) { | |||||
call broadcast_prevote(r, v); | |||||
} else { | |||||
call broadcast_prevote(r, value.nil); | |||||
}; | |||||
call abstract_protocol.l_28(n,r,v,vr,q); | |||||
step := step_t.prevote; | |||||
} | |||||
action broadcast_prevote(r:round, v:value) = { | |||||
var m: msg; | |||||
m.m_kind := msg_kind.prevote; | |||||
m.m_src := n; | |||||
m.m_round := r; | |||||
m.m_value := v; | |||||
call shim.broadcast(n,m); | |||||
} | |||||
implement shim.prevote_handler.handle(msg:msg) { | |||||
assume msg.m_vround = round.minus_one; | |||||
_recved_prevote(msg.m_src, msg.m_round, msg.m_value) := true; | |||||
} | |||||
# line 34-35 | |||||
export action l_34(r:round, q:nset) = { | |||||
assume _has_started; | |||||
assume round_p = r; | |||||
assume nset.is_quorum(q); | |||||
assume exists V . nset.member(N,q) -> _recved_prevote(N,r,V); | |||||
assume step = step_t.prevote; | |||||
assume ~done_l34(r); | |||||
done_l34(r) := true; | |||||
prevote_timer_scheduled(r) := true; | |||||
} | |||||
# line 36-43 | |||||
export action l_36(r:round, v:value, q:nset) = { | |||||
assume _has_started; | |||||
assume r = round_p; | |||||
assume exists VR . _recved_proposal(proposers.get_proposer(r), r, v, VR); | |||||
assume nset.is_quorum(q); | |||||
assume nset.member(N,q) -> _recved_prevote(N,r,v); | |||||
assume value.valid(v); | |||||
assume step = step_t.prevote | step = step_t.precommit; | |||||
assume ~done_l36(r,v); | |||||
done_l36(r, v) := true; | |||||
if step = step_t.prevote { | |||||
lockedValue := v; # line 38 | |||||
lockedRound := r; # line 39 | |||||
call broadcast_precommit(r, v); # line 40 | |||||
step := step_t.precommit; # line 41 | |||||
call abstract_protocol.l_36(n, r, v, q); | |||||
}; | |||||
validValue := v; # line 42 | |||||
validRound := r; # line 43 | |||||
} | |||||
# line 44-46 | |||||
export action l_44(r:round, q:nset) = { | |||||
assume _has_started; | |||||
assume r = round_p; | |||||
assume nset.is_quorum(q); | |||||
assume nset.member(N,q) -> _recved_prevote(N,r,value.nil); | |||||
assume step = step_t.prevote; | |||||
call broadcast_precommit(r, value.nil); # line 45 | |||||
step := step_t.precommit; # line 46 | |||||
call abstract_protocol.l_44(n, r, q); | |||||
} | |||||
action broadcast_precommit(r:round, v:value) = { | |||||
var m: msg; | |||||
m.m_kind := msg_kind.precommit; | |||||
m.m_src := n; | |||||
m.m_round := r; | |||||
m.m_value := v; | |||||
m.m_vround := round.minus_one; | |||||
call shim.broadcast(n,m); | |||||
} | |||||
implement shim.precommit_handler.handle(msg:msg) { | |||||
assume msg.m_vround = round.minus_one; | |||||
_recved_precommit(msg.m_src, msg.m_round, msg.m_value) := true; | |||||
} | |||||
# line 47-48 | |||||
export action l_47(r:round, q:nset) = { | |||||
assume _has_started; | |||||
assume round_p = r; | |||||
assume nset.is_quorum(q); | |||||
assume nset.member(N,q) -> exists V . _recved_precommit(N,r,V); | |||||
assume ~done_l47(r); | |||||
done_l47(r) := true; | |||||
precommit_timer_scheduled(r) := true; | |||||
} | |||||
# line 49-54 | |||||
export action l_49_decide(r:round, v:value, q:nset) = { | |||||
assume _has_started; | |||||
assume exists VR . _recved_proposal(proposers.get_proposer(r), r, v, VR); | |||||
assume nset.is_quorum(q); | |||||
assume nset.member(N,q) -> _recved_precommit(N,r,v); | |||||
assume decision = value.nil; | |||||
if value.valid(v) { | |||||
decision := v; | |||||
# MORE for next height | |||||
call abstract_protocol.decide(n, r, v, q); | |||||
} | |||||
} | |||||
# line 55-56 | |||||
export action l_55(r:round, b:nset) = { | |||||
assume _has_started; | |||||
assume nset.is_blocking(b); | |||||
assume nset.member(N,b) -> exists V,VR . _recved_proposal(N,r,V,VR) | _recved_prevote(N,r,V) | _recved_precommit(N,r,V); | |||||
assume r > round_p; | |||||
call startRound(r); # line 56 | |||||
} | |||||
# line 57-60 | |||||
export action onTimeoutPropose(r:round) = { | |||||
assume _has_started; | |||||
assume propose_timer_scheduled(r); | |||||
assume r = round_p; | |||||
assume step = step_t.propose; | |||||
call broadcast_prevote(r,value.nil); | |||||
step := step_t.prevote; | |||||
call abstract_protocol.l_57(n,r); | |||||
propose_timer_scheduled(r) := false; | |||||
} | |||||
# line 61-64 | |||||
export action onTimeoutPrevote(r:round) = { | |||||
assume _has_started; | |||||
assume prevote_timer_scheduled(r); | |||||
assume r = round_p; | |||||
assume step = step_t.prevote; | |||||
call broadcast_precommit(r,value.nil); | |||||
step := step_t.precommit; | |||||
call abstract_protocol.l_61(n,r); | |||||
prevote_timer_scheduled(r) := false; | |||||
} | |||||
# line 65-67 | |||||
export action onTimeoutPrecommit(r:round) = { | |||||
assume _has_started; | |||||
assume precommit_timer_scheduled(r); | |||||
assume r = round_p; | |||||
call startRound(round.incr(r)); | |||||
precommit_timer_scheduled(r) := false; | |||||
} | |||||
# The Byzantine actions | |||||
# --------------------- | |||||
# Byzantine nodes can send whatever they want, but they cannot send | |||||
# messages on behalf of well-behaved nodes. In practice this is implemented | |||||
# using cryprography (e.g. public-key cryptography). | |||||
export action byzantine_send = { | |||||
assume ~well_behaved(n); | |||||
var m:msg; | |||||
var dst:node; | |||||
assume ~well_behaved(m.m_src); # cannot forge the identity of well-behaved nodes | |||||
call shim.send(n,dst,m); | |||||
} | |||||
# Byzantine nodes can also report fake observations, as defined in the abstract protocol. | |||||
export action fake_observations = { | |||||
call abstract_protocol.misbehave | |||||
} | |||||
# Invariants | |||||
# ---------- | |||||
# We provide common invariants that a) allow to prove that the abstract | |||||
# preconditions are met, and b) provide a refinement relation. | |||||
invariant 0 <= round_p | |||||
invariant abstract_protocol.left_round(n,R) <-> R < round_p | |||||
invariant lockedRound ~= round.minus_one -> forall R,V . abstract_protocol.locked(n,R,V) <-> R <= lockedRound & lockedValue = V | |||||
invariant lockedRound = round.minus_one -> forall R,V . ~abstract_protocol.locked(n,R,V) | |||||
invariant forall M:msg . well_behaved(M.m_src) & M.m_kind = msg_kind.prevote & shim.sent(M,N) -> abstract_protocol.prevoted(M.m_src,M.m_round,M.m_value) | |||||
invariant well_behaved(N) & _recved_prevote(N,R,V) -> abstract_protocol.prevoted(N,R,V) | |||||
invariant forall M:msg . well_behaved(M.m_src) & M.m_kind = msg_kind.precommit & shim.sent(M,N) -> abstract_protocol.precommitted(M.m_src,M.m_round,M.m_value) | |||||
invariant well_behaved(N) & _recved_precommit(N,R,V) -> abstract_protocol.precommitted(N,R,V) | |||||
invariant (step = step_t.prevote | step = step_t.propose) -> ~abstract_protocol.precommitted(n,round_p,V) | |||||
invariant step = step_t.propose -> ~abstract_protocol.prevoted(n,round_p,V) | |||||
invariant step = step_t.prevote -> exists V . abstract_protocol.prevoted(n,round_p,V) | |||||
invariant round_p < R -> ~(abstract_protocol.prevoted(n,R,V) | abstract_protocol.precommitted(n,R,V)) | |||||
invariant ~_has_started -> step = step_t.propose & ~(abstract_protocol.prevoted(n,R,V) | abstract_protocol.precommitted(n,R,V)) & round_p = 0 | |||||
invariant decision ~= value.nil -> exists R . abstract_protocol.decided(n,R,decision) | |||||
} | |||||
} |