package link import "strconv" // The order a declaration carries, and the order a report about one carries back (novox/hq to-be 45 // §6, ADR 0227 rule 2). // // **This file is the controller's side of the contract with the node-engine for Phase 2**, and the one // place it is written here. It mirrors mesh-host internal/link/messages.go (`Order`, and the report's // `report_sequence`, `older_than`, `refused_older`) field for field and rule for rule, as Report has // always mirrored the host's report: a key added on one side and not the other is a key the other side // does not know exists. // // **The wire.** // // - A declaration's order is two top-level keys of its signed envelope, beside "declaration" and // "resources": `sequence` (since issue 107) and `epoch` (new). Inside the signed bytes, so a message // cannot be given a newer order than the controller gave it. // - A report carries the order of the declaration it is about as the same two top-level keys, beside // "declared"; `report_sequence`, the node-engine's own number for the report; `older_than`, on a // refusal of a declaration older than one it applied, the order of the one it holds; and // `refused_older`, how many it has refused so, ever, on every report. // // Every key is optional and absent when zero, and zero is "no order claimed", never "first". So each // side reads the other's older shape: // // - **An older node-engine with this controller.** It decodes a declaration strictly and refuses a key // it does not know, whole — so `epoch` is sent only to a machine whose latest report carried a // `report_sequence`, which a node-engine that reads the epoch always does. Until then it is sent the // sequence alone, as today; its reports carry no report sequence and are judged by the digest they // name, as today (issue 267). // - **A newer node-engine with an older controller.** It is sent no epoch, which claims none: it // refuses nothing by epoch. The keys it adds to a report are fields the older controller ignores. // - **A controller rolled back to a build without the lease** sends no epoch again and is not refused // for it: a node-engine refuses by epoch only when both declarations claim one. That gives up the // epoch's protection while such a build runs — the price of a rollback that cannot strand every // machine. // // **Epoch** is the controller lease's epoch (to-be 45 §6): the lease bucket's revision at which the // instance that composed the declaration took the lease. It only grows: a later holder's is higher, and // the controller never issues one at or under the highest it has issued (internal/lease, the floor). // **Sequence** is the declaration's number for that machine, one higher every send, taken from the // controller's store before the declaration is composed (issues 107, 204). // Order is where a declaration stands among everything the mesh has sent a machine: the lease epoch it // was sent under and its sequence. Zero in either claims none. type Order struct { Epoch int64 `json:"epoch,omitempty"` Sequence int64 `json:"sequence,omitempty"` } // Older is the node-engine's rule, as mesh-host states it: a declaration of this order is older than // one of order `than` the machine applied **only when both claim an epoch** — and then when its epoch // is lower, or its epoch is the same and its sequence lower (both claimed). A higher epoch is a new lease // holder and is never older, whatever its sequence. Here so the controller's tests state what the // machines will do with what it sends. func (o Order) Older(than Order) bool { if o.Epoch <= 0 || than.Epoch <= 0 { return false } if o.Epoch != than.Epoch { return o.Epoch < than.Epoch } return o.Sequence > 0 && than.Sequence > 0 && o.Sequence < than.Sequence } // Words is an order as a person reads it in a log line. Not String: Report embeds Order, and a Stringer // promoted onto every report would print each one as its order alone. func (o Order) Words() string { switch { case o.Epoch > 0: return "epoch " + strconv.FormatInt(o.Epoch, 10) + ", sequence " + strconv.FormatInt(o.Sequence, 10) case o.Sequence > 0: return "sequence " + strconv.FormatInt(o.Sequence, 10) + ", no epoch" } return "no order" } // Account is the order of one account of a machine — a report about a declaration: that declaration's // order and the node-engine's own number for the report. type Account struct { Order ReportSequence int64 } // AccountOf is a report's account order. func AccountOf(r Report) Account { return Account{Order: r.Order, ReportSequence: r.ReportSequence} } // Ordered says a report comes from a node-engine that orders its reports: it carries a report sequence. // Such a report is judged by its order (OlderThan); one without is judged by the digest it names, as // before (issue 267). func (r Report) Ordered() bool { return r.ReportSequence > 0 } // ReadsEpoch says the node-engine that made the report takes an epoch in a declaration: one that orders // its reports does (mesh-host: "its presence also says this host reads a declaration's epoch"). func (r Report) ReadsEpoch() bool { return r.ReportSequence > 0 } // StaleRefusalOf says a report refuses a declaration older than one the machine applied: the node-engine // names the order it holds (`older_than`), or, from a node-engine older than that, says so in the words // of its sequence refusal. func (r Report) StaleRefusalOf() bool { return r.OlderThan != nil || (r.Refused != "" && IsStaleRefusal(r.Refused)) } // OlderThan is the controller's rule (to-be 45 §6): whether this account is older than the one kept for // the machine — **by epoch, then sequence, then report sequence**, each compared only where both claim // one. An account of a declaration of an older epoch is refused; of an older sequence, refused; an older // report of the same declaration (a reconcile's, overtaken by the apply's — issue 267), refused. Equal is // the same report again, redelivered, and is not refused. func (a Account) OlderThan(kept Account) bool { if a.Epoch > 0 && kept.Epoch > 0 && a.Epoch != kept.Epoch { return a.Epoch < kept.Epoch } if a.Sequence > 0 && kept.Sequence > 0 && a.Sequence != kept.Sequence { return a.Sequence < kept.Sequence } return a.ReportSequence > 0 && kept.ReportSequence > 0 && a.ReportSequence < kept.ReportSequence }