blob: 35ca7d85adb4ea3c312b36c923cd238f7fcc28a9 [file] [log] [blame]
= 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
----