= Network Tables Alloy Model Alloy (http://alloy.mit.edu/alloy/) is a formal logic tool that can analyze first-order logic expressions. Under the proposed sequence number -based protocol, assuming that all nodes start from the same state, Alloy is unable to find a way where two nodes with the same sequence number have different state when activity ceases. The model used is included below. Although Alloy cannot test all cases, since such an exhaustive search is intractable, it provides a high level of confidence in the proposed protocol. ---- --- Models a distributed, centralized hash table system called NetworkTables --- System state is protected by sequence numbers; the server's value for a certain sequence number always wins --- Paul Malmsten, 2012 pmalmsten@gmail.com open util/ordering[Time] as TO open util/natural as natural sig Time {} sig State {} --- Define nodes and server sig Node { state: State -> Time, sequenceNumber: Natural -> Time } --- Only one server one sig Server extends Node { } --- Define possible events abstract sig Event { pre,post: Time, receiver: one Node } // For all events, event.post is the time directly following event.pre fact { all e:Event { e.post = e.pre.next } } // Represents that state has changed on a node sig StateChangeEvent extends Event { } // Represents that state has been transferred from one node to another sig StateTransferEvent extends Event { sender: one Node } fact { --- Every node must assume at most one state all t:Time, n:Node | #n.state.t = 1 --- Every node must assume one sequence number all t:Time, n:Node | #n.sequenceNumber.t = 1 --- Sequence numbers may only increment all t:Time - last, n:Node | let t' = t.next | natural/gte[n.sequenceNumber.t', n.sequenceNumber.t] } fact stateChangedImpliesAStateTransfer { all sce:StateChangeEvent { // A StateChange on a client causes a transfer to the Server if its sequence number is greater than the server's sce.receiver in Node - Server and natural/gt[sce.receiver.sequenceNumber.(sce.post), Server.sequenceNumber.(sce.post)] implies some ste:StateTransferEvent { ste.pre = sce.post and ste.sender = sce.receiver and ste.receiver = Server } } all sce:StateChangeEvent { // A StateChange on the server causes a transfer to all clients sce.receiver = Server implies all n:Node - Server { some ste:StateTransferEvent { ste.pre = sce.post and ste.sender = Server and ste.receiver = n } } } all sce:StateTransferEvent { // A StateTransfer to the server causes a transfer to all clients sce.receiver = Server implies all n:Node - Server { some ste:StateTransferEvent { ste.pre = sce.post and ste.sender = Server and ste.receiver = n } } } } fact stateTransferEventsMoveState { all ste:StateTransferEvent { ste.sender = Server and not ste.receiver = Server or ste.receiver = Server and not ste.sender = Server // Nodes can only post to the server if their sequence number is greater than the servers ste.receiver = Server implies natural/gt[ste.sender.sequenceNumber.(ste.pre), ste.receiver.sequenceNumber.(ste.pre)] // Server can only post to clients if its sequence number is greater than or equal to the client ste.sender = Server implies natural/gte[ste.sender.sequenceNumber.(ste.pre), ste.receiver.sequenceNumber.(ste.pre)] // Actual transfer (ste.receiver.state.(ste.post) = ste.sender.state.(ste.pre) and ste.receiver.sequenceNumber.(ste.post) = ste.sender.sequenceNumber.(ste.pre)) } } fact noEventsPendingAtEnd { no e:Event { e.pre = last } } fact noDuplicateEvents { all e,e2:Event { // Two different events with the same receiver imply they occurred at different times e.receiver = e2.receiver and e != e2 implies e.pre != e2.pre } } fact noStateTransfersToSelf { all ste:StateTransferEvent { ste.sender != ste.receiver } } fact noDuplicateStateTransferEvents { all ste,ste2:StateTransferEvent { // Two state transfer events with the same nodes imply that they occurred at different times ste.sender = ste2.sender and ste.receiver = ste2.receiver and ste != ste2 implies ste.pre != ste2.pre } } --- Trace (time invariant) fact trace { all t:Time - last | let t' = t.next { all n:Node { // A node in (pre.t).receiver means it is being pointed to by some event that will happen over the next time step n in (pre.t).receiver implies n.state.t' != n.state.t and n.sequenceNumber.t' != n.sequenceNumber.t // A node which receives some sort of event at time t causes it to change state else n.state.t' = n.state.t and n.sequenceNumber.t' = n.sequenceNumber.t // Otherwise, it does not change state } } } --- Things we might like to be true, but are not always true pred atLeastOneEvent { #Event >= 1 } pred allNodesStartAtSameStateAndSequenceNumber { all n,n2:Node { n.state.first = n2.state.first and n.sequenceNumber.first = n2.sequenceNumber.first } } pred noStateChangeEventsAtEnd { no e:StateChangeEvent { e.post = last } } --- Demonstration (Alloy will try to satisfy this) pred show { atLeastOneEvent } run show --- Assertions (Alloy will try to break these) assert allNodesConsistentAtEnd { allNodesStartAtSameStateAndSequenceNumber implies all n,n2:Node { // At the end of a sequence (last) all nodes with the same sequence number have the same state n.sequenceNumber.last = n2.sequenceNumber.last implies n.state.last = n2.state.last } } check allNodesConsistentAtEnd for 3 Event, 10 Node, 3 State, 5 Time, 5 Natural check allNodesConsistentAtEnd for 8 Event, 2 Node, 5 State, 9 Time, 9 Natural assert serverHasHighestSeqNumAtEnd { allNodesStartAtSameStateAndSequenceNumber implies all n:Node - Server{ // At the end of a sequence (last) all nodes with the same sequence number have the same state natural/gte[Server.sequenceNumber.last, n.sequenceNumber.last] } } check serverHasHighestSeqNumAtEnd for 3 Event, 10 Node, 3 State, 5 Time, 5 Natural ----