mirror of
https://github.com/tendermint/tendermint.git
synced 2026-07-30 20:12:52 +00:00
@@ -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
|
||||
Executable
+38
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user