#lang ivy1.7 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: Used only in the proof and the Byzantine model; Nodes do know know who is well-behaved and who is not. 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) export 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 { function f(R:round):node definition f(r:round) = <<>> 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 { interpret value -> bv[2] definition nil = <<< -1 >>> # let's say nil is -1 definition valid(V) = V ~= nil } } object nset = { # the type of node sets type this # a set of N=3f+i nodes for 0 #include namespace hash_space { template class hash > { public: size_t operator()(const std::set &s) const { hash h; size_t res = 0; for (const T &e : s) res += h(e); return res; } }; } >>> interpret nset -> <<< std::set<`node`> >>> definition member(n:node, s:nset) = <<< `s`.find(`n`) != `s`.end() >>> definition is_quorum(s:nset) = <<< 3*`s`.size() > 2*`node.size` >>> definition is_blocking(s:nset) = <<< 3*`s`.size() > `node.size` >>> implement empty { <<< >>> } implement insert { <<< `t` = `s`; `t`.insert(`n`); >>> } <<< encode `nset` std::ostream &operator <<(std::ostream &s, const `nset` &a) { s << "{"; for (auto iter = a.begin(); iter != a.end(); iter++) { if (iter != a.begin()) s << ", "; s << *iter; } s << "}"; return s; } template <> `nset` _arg<`nset`>(std::vector &args, unsigned idx, long long bound) { throw std::invalid_argument("Not implemented"); // no syntax for nset values in the REPL } >>> } } 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 } }