github.com/aakash4dev/cometbft@v0.38.2/spec/ivy-proofs/network_shim.ivy (about) 1 #lang ivy1.7 2 # --- 3 # layout: page 4 # title: Network model and network shim 5 # --- 6 7 # Here we define a network module, which is our model of the network, and a 8 # shim module that sits on top of the network and which, upon receiving a 9 # message, calls the appropriate protocol handler. 10 11 include domain_model 12 13 # Here we define an enumeration type for identifying the 3 different types of 14 # messages that nodes send. 15 object msg_kind = { # TODO: merge with step_t 16 type this = {proposal, prevote, precommit} 17 } 18 19 # Here we define the type of messages `msg`. Its members are structs with the fields described below. 20 object msg = { 21 type this = struct { 22 m_kind : msg_kind, 23 m_src : node, 24 m_round : round, 25 m_value : value, 26 m_vround : round 27 } 28 } 29 30 # This is our model of the network: 31 isolate net = { 32 33 export action recv(dst:node,v:msg) 34 action send(src:node,dst:node,v:msg) 35 # Note that the `recv` action is exported, meaning that it can be called 36 # non-deterministically by the environment any time it is enabled. In other 37 # words, a packet that is in flight can be received at any time. In this 38 # sense, the network is fully asynchronous. Moreover, there is no 39 # requirement that a given message will be received at all. 40 41 # The state of the network consists of all the packets that have been 42 # sent so far, along with their destination. 43 relation sent(V:msg, N:node) 44 45 after init { 46 sent(V, N) := false 47 } 48 49 before send { 50 sent(v,dst) := true 51 } 52 53 before recv { 54 require sent(v,dst) # only sent messages can be received. 55 } 56 } 57 58 # The network shim sits on top of the network and, upon receiving a message, 59 # calls the appropriate protocol handler. It also exposes a `broadcast` action 60 # that sends to all nodes. 61 62 isolate shim = { 63 64 # In order not repeat the same code for each handler, we use a handler 65 # module parameterized by the type of message it will handle. Below we 66 # instantiate this module for the 3 types of messages of Tendermint 67 module handler(p_kind) = { 68 action handle(dst:node,m:msg) 69 object spec = { 70 before handle { 71 assert sent(m,dst) & m.m_kind = p_kind 72 } 73 } 74 } 75 76 instance proposal_handler : handler(msg_kind.proposal) 77 instance prevote_handler : handler(msg_kind.prevote) 78 instance precommit_handler : handler(msg_kind.precommit) 79 80 relation sent(M:msg,N:node) 81 82 action broadcast(src:node,m:msg) 83 action send(src:node,dst:node,m:msg) 84 85 specification { 86 after init { 87 sent(M,D) := false; 88 } 89 before broadcast { 90 sent(m,D) := true 91 } 92 before send { 93 sent(m,dst) := true 94 } 95 } 96 97 # Here we give an implementation of it that satisfies its specification: 98 implementation { 99 100 implement net.recv(dst:node,m:msg) { 101 102 if m.m_kind = msg_kind.proposal { 103 call proposal_handler.handle(dst,m) 104 } 105 else if m.m_kind = msg_kind.prevote { 106 call prevote_handler.handle(dst,m) 107 } 108 else if m.m_kind = msg_kind.precommit { 109 call precommit_handler.handle(dst,m) 110 } 111 } 112 113 implement broadcast { # broadcast sends to all nodes, including the sender. 114 var iter := node.iter.create(0); 115 while ~iter.is_end 116 invariant net.sent(M,D) -> sent(M,D) 117 { 118 var n := iter.val; 119 call net.send(src,n,m); 120 iter := iter.next; 121 } 122 } 123 124 implement send { 125 call net.send(src,dst,m) 126 } 127 128 private { 129 invariant net.sent(M,D) -> sent(M,D) 130 } 131 } 132 133 } with net, node # to prove that the shim implementation satisfies the shim specification, we rely on the specification of net and node.