Last active
October 29, 2024 14:25
-
-
Save zicklag/149d84584894336d10f22d78e483f499 to your computer and use it in GitHub Desktop.
Work-in-progress non-racy dns txt reservation protocol Quint specification.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module dns { | |
type Server = str | |
type Hostname = str | |
type Pubkey = str | |
type Option[a] = | |
| Some(a) | |
| None | |
type Reservation = { hostname: Hostname, pubkey: Pubkey } | |
pure val weirdKey = "weird" | |
type SrvReserveState = | |
| Idle | |
| WantsReservation(Reservation) | |
| CheckForExistingReservation(Reservation) | |
| TryReserveRecord(Reservation) | |
| ReservationSucceeded(Reservation) | |
| ReservationFailed(Reservation) | |
def serverIsIdle(server: Server): bool = | |
match srvStates.get(server) { | |
| Idle => true | |
| _ => false | |
} | |
def serverWantsReservation(server: Server): bool = | |
match srvStates.get(server) { | |
| WantsReservation(_) => true | |
| _ => false | |
} | |
def serverCheckingForExistingReservation(server: Server): bool = | |
match srvStates.get(server) { | |
| CheckForExistingReservation(_) => true | |
| _ => false | |
} | |
type DnsTxtRecord = { key: str, value: str, id: int } | |
type DnsTxtRecords = List[DnsTxtRecord] | |
type ViewedRecords = { hostname: Hostname, records: DnsTxtRecords } | |
// The possible server instances ( or threads ) that could all be trying to reserve DNS entries at the same | |
// time, | |
pure val SERVERS = Set("alice", "bob", "charlie", "eva", "matthew", "jimmy") | |
// The different usernames we might try to register | |
pure val HOSTNAMES = Set("zicklag", "erlend", "sebadob", "anders") | |
// The different public keys that a username might resolve to | |
pure val PUBKEYS = Set("k1", "k2", "k3", "k4", "k5") | |
// The actual DNS records as stored in, for example, cloudflare. | |
var records: Hostname -> DnsTxtRecords | |
// The latest list of records that each server has fetched from the DNS server | |
var srvViewedRecords: Server -> Option[ViewedRecords] | |
var srvStates: Server -> SrvReserveState | |
// Combined state | |
val state = { records: records, viewedRecords: srvViewedRecords, srvStates: srvStates } | |
// TODO: add invariants | |
val invariants = true | |
action init = { | |
all { | |
records' = Map(), | |
srvViewedRecords' = SERVERS.mapBy(srv => None), | |
srvStates' = SERVERS.mapBy(s => Idle), | |
} | |
} | |
action unchanged = all { | |
records' = records, | |
srvViewedRecords' = srvViewedRecords, | |
srvStates' = srvStates, | |
} | |
action step = any { | |
handleServerIdle, | |
handleServerWantsRegistration, | |
handleServerCheckForExistingRegistration, | |
} | |
// a user wants to try and map a username to a key | |
action handleServerIdle = all { | |
SERVERS.exists(serverIsIdle), | |
{ | |
nondet server = oneOf(SERVERS.filter(serverIsIdle)) | |
nondet hostname = oneOf(HOSTNAMES) | |
nondet pubkey = oneOf(PUBKEYS) | |
all { | |
records' = records, | |
srvViewedRecords' = srvViewedRecords, | |
srvStates' = srvStates.set(server, WantsReservation({ hostname: hostname, pubkey: pubkey })), | |
} | |
} | |
} | |
action handleServerWantsRegistration = all { | |
SERVERS.exists(serverWantsReservation), | |
{ | |
nondet server = oneOf(SERVERS.filter(serverWantsReservation)) | |
match srvStates.get(server) { | |
| WantsReservation(reservation) => all { | |
fetchRecords(server, reservation.hostname), | |
srvStates' = srvStates.set(server, CheckForExistingReservation(reservation)), | |
records' = records, | |
} | |
| _ => unchanged | |
} | |
} | |
} | |
action handleServerCheckForExistingRegistration = all { | |
SERVERS.exists(serverCheckingForExistingReservation), | |
{ | |
nondet server = oneOf(SERVERS.filter(serverCheckingForExistingReservation)) | |
match srvStates.get(server) { | |
| CheckForExistingReservation(reservation) => { | |
match srvViewedRecords.get(server) { | |
| Some(existingRecords) => { | |
all { | |
existingRecords.hostname == reservation.hostname, | |
// Record not available | |
if (existingRecords.records.select(x => x.key == weirdKey).length() >= 1) { | |
all { | |
records' = records, | |
srvStates' = srvStates.set(server, ReservationFailed(reservation)), | |
srvViewedRecords' = srvViewedRecords, | |
} | |
// Record is available | |
} else { | |
all { | |
records' = records, | |
srvViewedRecords' = srvViewedRecords, | |
srvStates' = srvStates.set(server, TryReserveRecord(reservation)) | |
} | |
} | |
} | |
} | |
| _ => unchanged | |
} | |
} | |
| _ => unchanged | |
} | |
} | |
} | |
action fetchRecords(server: Server, hostname: Hostname): bool = all { | |
srvViewedRecords' = srvViewedRecords.set( | |
server, | |
if (hostname.in(records.keys())) { | |
Some({ hostname: hostname, records: records.get(hostname) }) | |
} else { | |
Some({ hostname: hostname, records: List() }) | |
} | |
), | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment