Skip to main content

planarian_txn/
ledger.rs

1//! Journal records and the ledger they build (§12, §12c).
2//!
3//! The ledger is the live state the journal establishes. A checkpoint is the
4//! serialized ledger, so replaying (checkpoint + tail) and replaying the full
5//! history produce equal ledgers by construction.
6//!
7//! `validate` runs before a record is written (write-ahead) and again on
8//! replay. It enforces I6, I15 and the recovery interlock at the journal
9//! itself, so no caller can journal a step that breaks them.
10//!
11//! Verified with Verus (docs/verification.md): `validate` accepts a record
12//! only when `rules` hold, and `apply` preserves `inv` (I6 among the live
13//! transactions, unique ids, leases naming their transaction). Bookkeeping no
14//! invariant reads (the terminal archive, overrides, incidents, tokens) is
15//! done by small unverified helpers that cannot reach the verified fields.
16
17use crate::machine::{step_allowed, tag};
18use planarian_schema::{
19    Actor, GenerationId, Hash, Incident, IncidentId, Lease, OpId, Outcome, Override, OwnerId, Resolution, Timestamp,
20    Transaction, Transition, TxnId, TxnState, UnitId,
21};
22use serde::{Deserialize, Deserializer, Serialize, Serializer};
23use std::collections::BTreeMap;
24use vstd::prelude::*;
25
26verus! {
27
28#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
29#[verifier::external_derive]
30pub struct Entry {
31    pub seq: u64,
32    pub at: Timestamp,
33    pub record: Record,
34}
35
36#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
37#[verifier::external_derive]
38pub enum Record {
39    /// The transaction exists from this record on (§12 step 4).
40    Prepared(Box<Transaction>),
41    Advance {
42        txn: TxnId,
43        epoch: u64,
44        to: TxnState,
45        note: String,
46        /// §12f: on a `Rejected(Interrupted)`, the rollback it was withdrawn
47        /// for; the daemon prepares it again once that rollback is terminal,
48        /// across a restart too. Absent in records from before it.
49        #[serde(default)]
50        withdrawn_for: Option<TxnId>,
51    },
52    /// Compare-and-swap on the epoch: valid only if `from_epoch` is current.
53    Takeover {
54        txn: TxnId,
55        from_epoch: u64,
56        lease: Lease,
57    },
58    Renew {
59        txn: TxnId,
60        epoch: u64,
61        worker_pid: Option<u32>,
62        /// The renewal's time on the boot clock (§12h); 0 in records written
63        /// before v4.5, which then read as long ago.
64        #[serde(default)]
65        boot_at: Timestamp,
66    },
67    /// Written and fsynced before the backend call it names.
68    OpBegin {
69        op: OpId,
70        call: String,
71    },
72    OpAck {
73        op: OpId,
74        outcome: Outcome,
75    },
76    OverrideSet(Override),
77    OverrideLifted(UnitId),
78    Incident(Incident),
79    KnownGood(GenerationId),
80    LiveTested(Option<GenerationId>),
81    AcceptedBaseline(Option<GenerationId>),
82    /// The security floor (§12e): derived when a generation commits, or set by
83    /// an administrator's `mark-security-floor`; never by a proposal.
84    SecurityFloor(Option<GenerationId>),
85    /// The external desired-state document accepted (§9 hook, §12c, B-16):
86    /// its sequence number and the hash of its bytes. Replay protection
87    /// lives here, in the journal and its checkpoint, not in a cache file.
88    ExternalAccepted {
89        seq: u64,
90        digest: Hash,
91    },
92    /// An approval used (§12 step 4, I7): by whom, for which transaction.
93    TokenUsed {
94        nonce: [u8; 16],
95        expires: Timestamp,
96        /// The approver's PrincipalId (a human's is their uid); 0 in records
97        /// from before v4.6.
98        #[serde(default)]
99        approver: u64,
100        #[serde(default)]
101        txn: u64,
102    },
103    /// `planarian agent run <name>` by this uid (§11, I24): the started
104    /// unit's instance (its InvocationID) acts with that user's permissions,
105    /// attributed to the agent. Journaled once the unit has started, so a
106    /// failed or refused start binds nothing; covered by the chain.
107    AgentStarted {
108        name: String,
109        /// Zero in records written before v4.6, which then match no peer.
110        #[serde(default)]
111        instance: [u8; 16],
112        by: u32,
113    },
114    Resolved {
115        txn: TxnId,
116        resolution: Resolution,
117        by: Actor,
118    },
119    Reconciliation(Reconciliation),
120    /// §12d *Network security fails closed*: the unit's traffic is held,
121    /// journaled before the call (write-ahead). A failure posture response,
122    /// not a transaction (docs/bypasses.md).
123    HoldApplied {
124        unit: UnitId,
125        by: HoldBy,
126    },
127    /// The hold is lifted: the unit is Running and its Readiness probes
128    /// pass, or an attested release. Journaled before the call.
129    HoldLifted {
130        unit: UnitId,
131        reason: String,
132    },
133}
134
135/// Who applied a hold: systemd's `OnFailure=` (seen by the daemon) or the
136/// daemon itself, for a stop outside Planarian that is not attributed.
137#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
138#[verifier::external_derive]
139pub enum HoldBy {
140    OnFailure,
141    Daemon,
142}
143
144/// What a restarted owner found and did (§12 daemon restart reconciliation).
145#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
146#[verifier::external_derive]
147pub struct Reconciliation {
148    pub by: OwnerId,
149    pub found: Vec<(TxnId, TxnState)>,
150    pub actions: Vec<String>,
151}
152
153#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
154#[verifier::external_derive]
155pub struct OpRecord {
156    pub op: OpId,
157    pub call: String,
158    pub ack: Option<Outcome>,
159}
160
161#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
162#[verifier::external_derive]
163pub struct LiveTxn {
164    pub txn: Transaction,
165    pub ops: Vec<OpRecord>,
166    /// Journal states this transaction has passed through, in order.
167    pub path: Vec<String>,
168    /// The most recent non-empty note on an Advance.
169    #[serde(default)]
170    pub why: String,
171    /// It took the boot-only path (set when it entered Publishing). Once
172    /// admitted at boot (Promoted), it no longer excludes unit and device
173    /// repairs (I6): the change is fully in place, only its confirmation
174    /// remains.
175    #[serde(default)]
176    pub boot_only: bool,
177}
178
179/// What remains of a terminal transaction: enough to refuse stale epochs and
180/// to show the outcome. Bounded to the most recent `TERMINAL_KEEP`.
181#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
182#[verifier::external_derive]
183pub struct TerminalTxn {
184    pub state: TxnState,
185    pub epoch: u64,
186    pub owner: OwnerId,
187    pub path: Vec<String>,
188    pub transition: Transition,
189    /// The most recent reason journaled with a step (why it reverted, …).
190    pub why: String,
191    /// §12f: the rollback it was withdrawn for (its last Advance's).
192    #[serde(default)]
193    pub withdrawn_for: Option<TxnId>,
194    /// §12f: the withdrawn transaction it prepared again.
195    #[serde(default)]
196    pub prepared_again_from: Option<TxnId>,
197}
198
199/// An unresolved RecoveryFailed (§12c). While any exists, autonomous mutation is blocked.
200#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
201#[verifier::external_derive]
202pub struct InterlockEntry {
203    pub txn: TxnId,
204    pub transition: Transition,
205    pub failed_from: String,
206    pub base_override: Option<Hash>,
207    pub candidate_override: Option<Hash>,
208    pub revert_to: GenerationId,
209    pub note: String,
210}
211
212/// A map from transaction id, stored as a vector sorted by id; vstd
213/// specifies `BTreeMap` only for keys with an ordering spec, which `TxnId`
214/// lacks. Serialized as the JSON object the `BTreeMap` was.
215#[derive(Clone, Debug, PartialEq, Eq)]
216pub struct IdMap<V> {
217    /// Sorted by id, ids unique. Read freely; change through the methods.
218    pub entries: Vec<(TxnId, V)>,
219}
220
221#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
222#[verifier::external_derive]
223pub struct Ledger {
224    pub last_seq: u64,
225    pub next_txn: u64,
226    pub live: IdMap<LiveTxn>,
227    pub terminal: BTreeMap<TxnId, TerminalTxn>,
228    pub overrides: Vec<Override>,
229    pub incidents: BTreeMap<IncidentId, Incident>,
230    pub tokens_used: Vec<([u8; 16], Timestamp)>,
231    pub known_good: Option<GenerationId>,
232    pub live_tested: Option<GenerationId>,
233    pub accepted_baseline: Option<GenerationId>,
234    #[serde(default)]
235    pub security_floor: Option<GenerationId>,
236    /// The last accepted external document's (sequence, digest) (B-16).
237    #[serde(default)]
238    pub external: Option<(u64, Hash)>,
239    pub interlock: IdMap<InterlockEntry>,
240    pub last_reconciliation: Option<Reconciliation>,
241    /// Every agent started, in order: (name, instance, the uid that started
242    /// it). A peer is matched by its instance, never its name (I24).
243    #[serde(default)]
244    pub agents: Vec<(String, [u8; 16], u32)>,
245    /// §12d: every hold applied (`true`) and lifted (`false`), in order; the
246    /// current holds are `holds()`. Append-only, so the fold needs no
247    /// comparison of units (and no new trusted helper). A journal from before
248    /// holds replays to none.
249    #[serde(default)]
250    pub hold_events: Vec<(UnitId, bool)>,
251}
252
253#[derive(Clone, Debug, PartialEq, Eq)]
254pub enum LedgerError {
255    Sequence {
256        expected: u64,
257        got: u64,
258    },
259    DuplicateTxn(TxnId),
260    NotLive(TxnId),
261    /// I15: only the current lease holder's epoch may advance the journal.
262    StaleEpoch {
263        txn: TxnId,
264        current: u64,
265        got: u64,
266    },
267    IllegalStep {
268        txn: TxnId,
269        from: &'static str,
270        to: &'static str,
271    },
272    /// I6: a generation-changing transaction overlaps another in-flight one.
273    Exclusive {
274        txn: TxnId,
275        blocker: TxnId,
276    },
277    /// §12c: autonomous mutation while the recovery interlock is set.
278    Interlocked {
279        txn: TxnId,
280    },
281    UnknownOp(OpId),
282    DuplicateOp(OpId),
283    NotInterlocked(TxnId),
284    BadPrepared(&'static str),
285    /// B-16: an external document no newer than the one in force.
286    ExternalReplay {
287        have: u64,
288        got: u64,
289    },
290}
291
292// ---------- Specifications ----------
293
294/// Anyone but a user: the daemon, the watchdog, the model.
295pub open spec fn autonomous(a: Actor) -> bool {
296    !(a is User)
297}
298
299pub open spec fn in_flight(x: LiveTxn) -> bool {
300    !x.txn.state.pre_mutation()
301}
302
303pub open spec fn generation(x: LiveTxn) -> bool {
304    x.txn.transition.changes_generation_spec()
305}
306
307/// A state in which a transaction may have mutated and has not finished.
308pub open spec fn flying(s: TxnState) -> bool {
309    !s.pre_mutation() && !s.terminal()
310}
311
312/// I6 for a pair of states, `g` saying whether each transaction changes the
313/// generation. A generation transaction in an exclusive state
314/// (`TxnState::exclusive`, the one list) excludes every other in-flight
315/// transaction (unit and device repairs may run while it waits in `Built` or
316/// `AwaitingBoot`); and at most one generation transaction is active, where
317/// waiting in `Built` and `AwaitingBoot` count as active.
318pub open spec fn compatible(s1: TxnState, g1: bool, b1: bool, s2: TxnState, g2: bool, b2: bool) -> bool {
319    let excl1 = g1 && s1.exclusive() && !(s1 is Promoted && b1);
320    let excl2 = g2 && s2.exclusive() && !(s2 is Promoted && b2);
321    let act1 = g1 && (flying(s1) || s1 is Built);
322    let act2 = g2 && (flying(s2) || s2 is Built);
323    &&& excl1 ==> !flying(s2)
324    &&& excl2 ==> !flying(s1)
325    &&& !(act1 && act2)
326}
327
328pub open spec fn has_id<V>(s: Seq<(TxnId, V)>, id: TxnId) -> bool {
329    exists|i: int| 0 <= i < s.len() && (#[trigger] s[i]).0 == id
330}
331
332pub open spec fn interlocked(l: Ledger) -> bool {
333    l.interlock.entries@.len() > 0
334}
335
336/// One live transaction is well formed: keyed by its id, its lease names it,
337/// its id was issued, it is not terminal, its recovery strategy is the one
338/// its transition fixes.
339pub open spec fn live_ok(x: (TxnId, LiveTxn), next_txn: u64) -> bool {
340    &&& x.0 == x.1.txn.id
341    &&& x.1.txn.lease.txn == x.0
342    &&& x.0.0 < next_txn
343    &&& !x.1.txn.state.terminal()
344    &&& x.1.txn.recovery == x.1.txn.transition.recovery_spec()
345}
346
347/// Two distinct live transactions: different ids, and compatible (I6).
348pub open spec fn pair_ok(a: (TxnId, LiveTxn), b: (TxnId, LiveTxn)) -> bool {
349    &&& a.0 != b.0
350    &&& compatible(a.1.txn.state, generation(a.1), a.1.boot_only, b.1.txn.state, generation(b.1), b.1.boot_only)
351}
352
353/// The ledger invariant. I6: at most one generation-changing transaction is
354/// active, and while one is in flight (other than awaiting a boot), no other
355/// transaction is.
356pub open spec fn inv(l: Ledger) -> bool {
357    let e = l.live.entries@;
358    &&& forall|i: int| 0 <= i < e.len() ==> live_ok(#[trigger] e[i], l.next_txn)
359    &&& forall|i: int, j: int| 0 <= i < e.len() && 0 <= j < e.len() && i != j ==> pair_ok(#[trigger] e[i], #[trigger] e[j])
360}
361
362pub open spec fn prepared_rule(l: Ledger, t: Transaction) -> bool {
363    &&& !has_id(l.live.entries@, t.id)
364    &&& l.next_txn <= t.id.0 < u64::MAX
365    &&& t.state == TxnState::Prepared
366    &&& t.lease.epoch == 1
367    &&& t.lease.txn == t.id
368    &&& t.recovery == t.transition.recovery_spec()
369    &&& !(interlocked(l) && autonomous(t.facts.caller))
370}
371
372pub open spec fn advance_rule(l: Ledger, txn: TxnId, epoch: u64, to: TxnState) -> bool {
373    exists|i: int| 0 <= i < l.live.entries@.len() && (#[trigger] l.live.entries@[i]).0 == txn && advance_ok(l, i, epoch, to)
374}
375
376pub open spec fn takeover_rule(l: Ledger, txn: TxnId, from_epoch: u64, lease: Lease) -> bool {
377    exists|i: int|
378        0 <= i < l.live.entries@.len() && (#[trigger] l.live.entries@[i]).0 == txn && l.live.entries@[i].1.txn.lease.epoch
379            == from_epoch && lease.epoch > from_epoch && lease.txn == txn
380}
381
382pub open spec fn renew_rule(l: Ledger, txn: TxnId, epoch: u64) -> bool {
383    exists|i: int|
384        0 <= i < l.live.entries@.len() && (#[trigger] l.live.entries@[i]).0 == txn && l.live.entries@[i].1.txn.lease.epoch
385            == epoch
386}
387
388pub open spec fn op_begin_rule(l: Ledger, op: OpId) -> bool {
389    exists|i: int|
390        0 <= i < l.live.entries@.len() && (#[trigger] l.live.entries@[i]).0 == op.txn && l.live.entries@[i].1.txn.lease.epoch
391            == op.epoch && forall|k: int| 0 <= k < l.live.entries@[i].1.ops@.len() ==> (
392        #[trigger] l.live.entries@[i].1.ops@[k]).op != op
393}
394
395pub open spec fn op_ack_rule(l: Ledger, op: OpId) -> bool {
396    exists|i: int, k: int|
397        0 <= i < l.live.entries@.len() && (#[trigger] l.live.entries@[i]).0 == op.txn && 0 <= k
398            < l.live.entries@[i].1.ops@.len() && (#[trigger] l.live.entries@[i].1.ops@[k]).op == op
399            && l.live.entries@[i].1.ops@[k].ack is None
400}
401
402/// What `validate` checks, per record. I15 is in the epoch conditions; I6 in
403/// the exclusivity condition; §12c in the interlock conditions.
404pub open spec fn rules(l: Ledger, e: Entry) -> bool {
405    &&& e.seq == l.last_seq + 1
406    &&& match e.record {
407        Record::Prepared(t) => prepared_rule(l, *t),
408        Record::Advance { txn, epoch, to, .. } => advance_rule(l, txn, epoch, to),
409        Record::Takeover { txn, from_epoch, lease } => takeover_rule(l, txn, from_epoch, lease),
410        Record::Renew { txn, epoch, .. } => renew_rule(l, txn, epoch),
411        Record::OpBegin { op, .. } => op_begin_rule(l, op),
412        Record::OpAck { op, .. } => op_ack_rule(l, op),
413        Record::Resolved { txn, .. } => has_id(l.interlock.entries@, txn),
414        Record::ExternalAccepted { seq, .. } => external_rule(l, seq),
415        _ => true,
416    }
417}
418
419/// B-16: an external document is accepted only with a sequence number above
420/// the last one the journal accepted, so a replayed or rewound document
421/// cannot come back into force.
422pub open spec fn external_rule(l: Ledger, seq: u64) -> bool {
423    match l.external {
424        None => true,
425        Some(p) => seq > p.0,
426    }
427}
428
429/// An Advance of the live transaction at `i`: by its lease holder (I15),
430/// along the state machine; if it starts mutating, not while the interlock
431/// holds for an autonomous caller; and its new state compatible with every
432/// other live transaction (I6).
433pub open spec fn advance_ok(l: Ledger, i: int, epoch: u64, to: TxnState) -> bool {
434    let live = l.live.entries@;
435    let x = live[i].1;
436    &&& x.txn.lease.epoch == epoch
437    &&& crate::machine::step_spec(x.txn.state, to, x.txn.recovery)
438    &&& (x.txn.state.pre_mutation() && !to.pre_mutation() && !to.terminal()) ==> !(interlocked(l) && autonomous(
439        x.txn.facts.caller,
440    ))
441    &&& forall|j: int| 0 <= j < live.len() && j != i ==> compatible(
442        to,
443        generation(x),
444        x.boot_only,
445        (#[trigger] live[j]).1.txn.state,
446        generation(live[j].1),
447        live[j].1.boot_only,
448    )
449}
450
451// ---------- Small verified helpers ----------
452
453fn flying_exec(s: &TxnState) -> (r: bool)
454    ensures
455        r == flying(*s),
456{
457    !s.is_pre_mutation() && !s.is_terminal()
458}
459
460fn compatible_exec(s1: &TxnState, g1: bool, b1: bool, s2: &TxnState, g2: bool, b2: bool) -> (r: bool)
461    ensures
462        r == compatible(*s1, g1, b1, *s2, g2, b2),
463{
464    let built1 = match s1 {
465        TxnState::Built => true,
466        _ => false,
467    };
468    let built2 = match s2 {
469        TxnState::Built => true,
470        _ => false,
471    };
472    let f1 = flying_exec(s1);
473    let f2 = flying_exec(s2);
474    let promoted1 = match s1 {
475        TxnState::Promoted => true,
476        _ => false,
477    };
478    let promoted2 = match s2 {
479        TxnState::Promoted => true,
480        _ => false,
481    };
482    let excl1 = g1 && s1.is_exclusive() && !(promoted1 && b1);
483    let excl2 = g2 && s2.is_exclusive() && !(promoted2 && b2);
484    let act1 = g1 && (f1 || built1);
485    let act2 = g2 && (f2 || built2);
486    !(excl1 && f2 || excl2 && f1 || act1 && act2)
487}
488
489fn find<V>(m: &IdMap<V>, id: TxnId) -> (r: Option<usize>)
490    ensures
491        match r {
492            Some(i) => i < m.entries@.len() && m.entries@[i as int].0 == id,
493            None => !has_id(m.entries@, id),
494        },
495{
496    let mut i: usize = 0;
497    while i < m.entries.len()
498        invariant
499            i <= m.entries@.len(),
500            forall|k: int| 0 <= k < i ==> (#[trigger] m.entries@[k]).0 != id,
501        decreases m.entries.len() - i,
502    {
503        if m.entries[i].0.0 == id.0 {
504            return Some(i);
505        }
506        i += 1;
507    }
508    None
509}
510
511fn is_autonomous(a: &Actor) -> (r: bool)
512    ensures
513        r == autonomous(*a),
514{
515    match a {
516        Actor::User { .. } => false,
517        _ => true,
518    }
519}
520
521fn op_eq(a: OpId, b: OpId) -> (r: bool)
522    ensures
523        r == (a == b),
524{
525    a.txn.0 == b.txn.0 && a.epoch == b.epoch && a.seq == b.seq
526}
527
528fn stale(txn: TxnId, current: u64, got: u64) -> LedgerError {
529    LedgerError::StaleEpoch { txn, current, got }
530}
531
532impl Ledger {
533    /// The ledger invariant, checked: used on a checkpoint read from disk.
534    pub fn well_formed(&self) -> (r: bool)
535        ensures
536            r == inv(*self),
537    {
538        let n = self.live.entries.len();
539        let mut i: usize = 0;
540        while i < n
541            invariant
542                n == self.live.entries@.len(),
543                i <= n,
544                forall|k: int| 0 <= k < i ==> live_ok(#[trigger] self.live.entries@[k], self.next_txn),
545            decreases n - i,
546        {
547            let x = &self.live.entries[i];
548            let ok = x.0.0 == x.1.txn.id.0 && x.1.txn.lease.txn.0 == x.0.0 && x.0.0 < self.next_txn
549                && !x.1.txn.state.is_terminal() && recovery_eq(x.1.txn.recovery, x.1.txn.transition.recovery());
550            if !ok {
551                assert(!live_ok(self.live.entries@[i as int], self.next_txn));
552                return false;
553            }
554            i += 1;
555        }
556        let mut i: usize = 0;
557        while i < n
558            invariant
559                n == self.live.entries@.len(),
560                i <= n,
561                forall|a: int, b: int|
562                    0 <= a < i && 0 <= b < n && a != b ==> pair_ok(
563                        #[trigger] self.live.entries@[a],
564                        #[trigger] self.live.entries@[b],
565                    ),
566            decreases n - i,
567        {
568            let mut j: usize = 0;
569            while j < n
570                invariant
571                    n == self.live.entries@.len(),
572                    i < n,
573                    j <= n,
574                    forall|a: int, b: int|
575                        0 <= a < i && 0 <= b < n && a != b ==> pair_ok(
576                            #[trigger] self.live.entries@[a],
577                            #[trigger] self.live.entries@[b],
578                        ),
579                    forall|b: int|
580                        0 <= b < j && b != i ==> pair_ok(self.live.entries@[i as int], #[trigger] self.live.entries@[b]),
581                decreases n - j,
582            {
583                if j != i {
584                    let a = &self.live.entries[i];
585                    let b = &self.live.entries[j];
586                    let ok = a.0.0 != b.0.0 && compatible_exec(
587                        &a.1.txn.state,
588                        a.1.txn.transition.changes_generation(),
589                        a.1.boot_only,
590                        &b.1.txn.state,
591                        b.1.txn.transition.changes_generation(),
592                        b.1.boot_only,
593                    );
594                    if !ok {
595                        assert(!pair_ok(self.live.entries@[i as int], self.live.entries@[j as int]));
596                        return false;
597                    }
598                }
599                j += 1;
600            }
601            i += 1;
602        }
603        true
604    }
605
606    #[verifier::loop_isolation(false)]
607    pub fn validate(&self, e: &Entry) -> (r: Result<(), LedgerError>)
608        ensures
609            r is Ok ==> rules(*self, *e),
610    {
611        if self.last_seq == u64::MAX || e.seq != self.last_seq + 1 {
612            return Err(LedgerError::Sequence { expected: self.last_seq.wrapping_add(1), got: e.seq });
613        }
614        match &e.record {
615            Record::Prepared(t) => {
616                if find(&self.live, t.id).is_some() || t.id.0 < self.next_txn || t.id.0 == u64::MAX {
617                    return Err(LedgerError::DuplicateTxn(t.id));
618                }
619                let prepared = match t.state {
620                    TxnState::Prepared => true,
621                    _ => false,
622                };
623                if !prepared || t.lease.epoch != 1 || t.lease.txn.0 != t.id.0 {
624                    return Err(LedgerError::BadPrepared("state Prepared, lease epoch 1 for this txn"));
625                }
626                if !recovery_eq(t.recovery, t.transition.recovery()) {
627                    return Err(LedgerError::BadPrepared("recovery strategy is fixed by the transition"));
628                }
629                if !self.interlock.entries.is_empty() && is_autonomous(&t.facts.caller) {
630                    return Err(LedgerError::Interlocked { txn: t.id });
631                }
632                Ok(())
633            },
634            Record::Advance { txn, epoch, to, .. } => {
635                let i = match find(&self.live, *txn) {
636                    Some(i) => i,
637                    None => return Err(LedgerError::NotLive(*txn)),
638                };
639                let l = &self.live.entries[i].1;
640                if l.txn.lease.epoch != *epoch {
641                    return Err(stale(*txn, l.txn.lease.epoch, *epoch));
642                }
643                if !step_allowed(&l.txn.state, to, l.txn.recovery) {
644                    return Err(LedgerError::IllegalStep { txn: *txn, from: tag(&l.txn.state), to: tag(to) });
645                }
646                if l.txn.state.is_pre_mutation() && !to.is_pre_mutation() && !to.is_terminal() {
647                    // Starting to mutate.
648                    if !self.interlock.entries.is_empty() && is_autonomous(&l.txn.facts.caller) {
649                        return Err(LedgerError::Interlocked { txn: *txn });
650                    }
651                }
652                let mine = l.txn.transition.changes_generation();
653                let boot_only = l.boot_only;
654                let mut j: usize = 0;
655                while j < self.live.entries.len()
656                    invariant
657                        j <= self.live.entries@.len(),
658                        mine == generation(self.live.entries@[i as int].1),
659                        boot_only == self.live.entries@[i as int].1.boot_only,
660                        forall|k: int|
661                            0 <= k < j && k != i ==> compatible(
662                                *to,
663                                mine,
664                                boot_only,
665                                (#[trigger] self.live.entries@[k]).1.txn.state,
666                                generation(self.live.entries@[k].1),
667                                self.live.entries@[k].1.boot_only,
668                            ),
669                    decreases self.live.entries.len() - j,
670                {
671                    let o = &self.live.entries[j].1;
672                    if j != i && !compatible_exec(
673                        to,
674                        mine,
675                        boot_only,
676                        &o.txn.state,
677                        o.txn.transition.changes_generation(),
678                        o.boot_only,
679                    ) {
680                        return Err(LedgerError::Exclusive { txn: *txn, blocker: o.txn.id });
681                    }
682                    j += 1;
683                }
684                assert(advance_ok(*self, i as int, *epoch, *to));
685                Ok(())
686            },
687            Record::Takeover { txn, from_epoch, lease } => {
688                let i = match find(&self.live, *txn) {
689                    Some(i) => i,
690                    None => return Err(LedgerError::NotLive(*txn)),
691                };
692                let l = &self.live.entries[i].1;
693                if l.txn.lease.epoch != *from_epoch {
694                    return Err(stale(*txn, l.txn.lease.epoch, *from_epoch));
695                }
696                if lease.epoch <= *from_epoch || lease.txn.0 != txn.0 {
697                    return Err(stale(*txn, *from_epoch, lease.epoch));
698                }
699                Ok(())
700            },
701            Record::Renew { txn, epoch, .. } => {
702                let i = match find(&self.live, *txn) {
703                    Some(i) => i,
704                    None => return Err(LedgerError::NotLive(*txn)),
705                };
706                let l = &self.live.entries[i].1;
707                if l.txn.lease.epoch != *epoch {
708                    return Err(stale(*txn, l.txn.lease.epoch, *epoch));
709                }
710                Ok(())
711            },
712            Record::OpBegin { op, .. } => {
713                let i = match find(&self.live, op.txn) {
714                    Some(i) => i,
715                    None => return Err(LedgerError::NotLive(op.txn)),
716                };
717                let l = &self.live.entries[i].1;
718                if l.txn.lease.epoch != op.epoch {
719                    return Err(stale(op.txn, l.txn.lease.epoch, op.epoch));
720                }
721                let mut k: usize = 0;
722                while k < l.ops.len()
723                    invariant
724                        k <= l.ops@.len(),
725                        forall|m: int| 0 <= m < k ==> (#[trigger] l.ops@[m]).op != *op,
726                    decreases l.ops.len() - k,
727                {
728                    if op_eq(l.ops[k].op, *op) {
729                        return Err(LedgerError::DuplicateOp(*op));
730                    }
731                    k += 1;
732                }
733                Ok(())
734            },
735            Record::OpAck { op, .. } => {
736                let i = match find(&self.live, op.txn) {
737                    Some(i) => i,
738                    None => return Err(LedgerError::NotLive(op.txn)),
739                };
740                let l = &self.live.entries[i].1;
741                let mut k: usize = 0;
742                while k < l.ops.len()
743                    invariant
744                        k <= l.ops@.len(),
745                    decreases l.ops.len() - k,
746                {
747                    if op_eq(l.ops[k].op, *op) {
748                        return match &l.ops[k].ack {
749                            None => {
750                                assert(self.live.entries@[i as int].1.ops@[k as int].op == *op);
751                                Ok(())
752                            },
753                            Some(_) => Err(LedgerError::DuplicateOp(*op)),
754                        };
755                    }
756                    k += 1;
757                }
758                Err(LedgerError::UnknownOp(*op))
759            },
760            Record::Resolved { txn, .. } => {
761                if find(&self.interlock, *txn).is_none() {
762                    return Err(LedgerError::NotInterlocked(*txn));
763                }
764                Ok(())
765            },
766            Record::ExternalAccepted { seq, .. } => match self.external {
767                Some((have, _)) if *seq <= have => Err(LedgerError::ExternalReplay { have, got: *seq }),
768                _ => Ok(()),
769            },
770            _ => Ok(()),
771        }
772    }
773}
774
775fn recovery_eq(a: planarian_schema::RecoveryStrategy, b: planarian_schema::RecoveryStrategy) -> (r: bool)
776    ensures
777        r == (a == b),
778{
779    use planarian_schema::RecoveryStrategy as R;
780    match (a, b) {
781        (R::ReobserveOnly, R::ReobserveOnly) => true,
782        (R::RevertGeneration, R::RevertGeneration) => true,
783        (R::UnbindDevice, R::UnbindDevice) => true,
784        (R::RestoreSnapshotThenRevert, R::RestoreSnapshotThenRevert) => true,
785        _ => false,
786    }
787}
788
789/// The transaction a record marks RecoveryFailed, if it does.
790pub open spec fn fails(r: Record) -> Option<TxnId> {
791    match r {
792        Record::Advance { txn, to: TxnState::RecoveryFailed, .. } => Some(txn),
793        _ => None,
794    }
795}
796
797/// No live transaction's lease epoch went down (I15: epochs only grow).
798pub open spec fn epochs_grow(a: Ledger, b: Ledger) -> bool {
799    forall|i: int, j: int|
800        0 <= i < a.live.entries@.len() && 0 <= j < b.live.entries@.len() && (#[trigger] a.live.entries@[i]).0
801            == (#[trigger] b.live.entries@[j]).0 ==> a.live.entries@[i].1.txn.lease.epoch
802            <= b.live.entries@[j].1.txn.lease.epoch
803}
804
805// Bookkeeping no invariant reads. Unverified: each touches only the field it
806// is given (docs/assumptions.md).
807
808#[verifier::external_body]
809fn push_tag(path: &mut Vec<String>, s: &TxnState) {
810    path.push(tag(s).into());
811}
812
813#[verifier::external_body]
814fn tag_string(s: &TxnState) -> String {
815    tag(s).into()
816}
817
818#[verifier::external_body]
819fn note_why(why: &mut String, note: &String) {
820    if !note.is_empty() && !note.starts_with("observed: ") && !note.starts_with("Succeeded") {
821        *why = note.clone();
822    }
823}
824
825#[verifier::external_body]
826fn archive(terminal: &mut BTreeMap<TxnId, TerminalTxn>, id: TxnId, l: LiveTxn, withdrawn_for: Option<TxnId>) {
827    terminal.insert(
828        id,
829        TerminalTxn {
830            state: l.txn.state,
831            epoch: l.txn.lease.epoch,
832            owner: l.txn.lease.owner,
833            path: l.path,
834            transition: l.txn.transition,
835            why: l.why,
836            withdrawn_for,
837            prepared_again_from: l.txn.prepared_again_from,
838        },
839    );
840    while terminal.len() > TERMINAL_KEEP {
841        let oldest = *terminal.keys().next().expect("non-empty");
842        terminal.remove(&oldest);
843    }
844}
845
846#[verifier::external_body]
847fn set_override(list: &mut Vec<Override>, s: Override) {
848    list.retain(|x| x.unit != s.unit);
849    list.push(s);
850}
851
852#[verifier::external_body]
853fn lift_override(list: &mut Vec<Override>, u: &UnitId) {
854    list.retain(|x| &x.unit != u);
855}
856
857#[verifier::external_body]
858fn record_incident(incidents: &mut BTreeMap<IncidentId, Incident>, i: Incident) {
859    if i.state == planarian_schema::IncidentState::Closed {
860        incidents.remove(&i.id);
861    } else {
862        incidents.insert(i.id, i);
863    }
864}
865
866#[verifier::external_body]
867fn use_token(tokens: &mut Vec<([u8; 16], Timestamp)>, at: Timestamp, nonce: [u8; 16], expires: Timestamp) {
868    tokens.retain(|(_, exp)| *exp > at);
869    tokens.push((nonce, expires));
870}
871
872/// Insert or replace, keeping the entries sorted by id.
873fn put<V>(m: &mut IdMap<V>, id: TxnId, v: V)
874    ensures
875        has_id(final(m).entries@, id),
876{
877    match find(m, id) {
878        Some(i) => {
879            m.entries.remove(i);
880            m.entries.insert(i, (id, v));
881            assert(final(m).entries@[i as int].0 == id);
882        },
883        None => {
884            let mut p: usize = 0;
885            while p < m.entries.len() && m.entries[p].0.0 < id.0
886                invariant
887                    p <= m.entries@.len(),
888                decreases m.entries.len() - p,
889            {
890                p += 1;
891            }
892            m.entries.insert(p, (id, v));
893            assert(final(m).entries@[p as int].0 == id);
894        },
895    }
896}
897
898/// Under the invariant, ids identify live transactions.
899proof fn lemma_unique_index(l: Ledger, i: int, j: int)
900    requires
901        inv(l),
902        0 <= i < l.live.entries@.len(),
903        0 <= j < l.live.entries@.len(),
904        l.live.entries@[i].0 == l.live.entries@[j].0,
905    ensures
906        i == j,
907{
908    if i != j {
909        assert(pair_ok(l.live.entries@[i], l.live.entries@[j]));
910    }
911}
912
913impl Ledger {
914    /// Validate, then apply. The record is consumed: the ledger keeps its parts.
915    pub fn apply(&mut self, e: Entry) -> (r: Result<(), LedgerError>)
916        ensures
917            r is Ok ==> rules(*old(self), e),
918            r is Err ==> *final(self) == *old(self),
919            r is Ok ==> final(self).last_seq == e.seq,
920            (r is Ok && inv(*old(self))) ==> inv(*final(self)),
921            (r is Ok && inv(*old(self))) ==> epochs_grow(*old(self), *final(self)),
922            (r is Ok && fails(e.record) is Some) ==> has_id(final(self).interlock.entries@, fails(e.record)->Some_0),
923    {
924        match self.validate(&e) {
925            Err(x) => return Err(x),
926            Ok(()) => {},
927        }
928        self.apply_valid(e);
929        Ok(())
930    }
931
932    /// Mark a transaction RecoveryFailed from the sentinel, when the journal
933    /// itself could not take the record (§12c).
934    pub fn add_interlock(&mut self, entry: InterlockEntry)
935        ensures
936            inv(*old(self)) ==> inv(*final(self)),
937            has_id(final(self).interlock.entries@, entry.txn),
938    {
939        let id = entry.txn;
940        put(&mut self.interlock, id, entry);
941    }
942
943    fn apply_valid(&mut self, e: Entry)
944        requires
945            rules(*old(self), e),
946        ensures
947            final(self).last_seq == e.seq,
948            inv(*old(self)) ==> inv(*final(self)),
949            inv(*old(self)) ==> epochs_grow(*old(self), *final(self)),
950            fails(e.record) is Some ==> has_id(final(self).interlock.entries@, fails(e.record)->Some_0),
951    {
952        self.last_seq = e.seq;
953        let at = e.at;
954        match e.record {
955            Record::Prepared(t) => self.apply_prepared(*t),
956            Record::Advance { txn, epoch, to, note, withdrawn_for } => {
957                self.apply_advance(txn, epoch, to, note, withdrawn_for)
958            },
959            Record::Takeover { txn, from_epoch, lease } => self.apply_takeover(txn, from_epoch, lease),
960            Record::Renew { txn, epoch, worker_pid, boot_at } => self.apply_renew(txn, epoch, boot_at, worker_pid),
961            Record::OpBegin { op, call } => self.apply_op_begin(op, call),
962            Record::OpAck { op, outcome } => self.apply_op_ack(op, outcome),
963            Record::OverrideSet(s) => set_override(&mut self.overrides, s),
964            Record::OverrideLifted(u) => lift_override(&mut self.overrides, &u),
965            Record::Incident(i) => record_incident(&mut self.incidents, i),
966            Record::KnownGood(g) => self.known_good = Some(g),
967            Record::LiveTested(g) => self.live_tested = g,
968            Record::AcceptedBaseline(g) => self.accepted_baseline = g,
969            Record::SecurityFloor(g) => self.security_floor = g,
970            Record::ExternalAccepted { seq, digest } => self.external = Some((seq, digest)),
971            Record::TokenUsed { nonce, expires, .. } => use_token(&mut self.tokens_used, at, nonce, expires),
972            Record::Resolved { txn, .. } => {
973                if let Some(k) = find(&self.interlock, txn) {
974                    self.interlock.entries.remove(k);
975                }
976            },
977            Record::Reconciliation(r) => self.last_reconciliation = Some(r),
978            Record::AgentStarted { name, instance, by } => self.agents.push((name, instance, by)),
979            Record::HoldApplied { unit, .. } => self.hold_events.push((unit, true)),
980            Record::HoldLifted { unit, .. } => self.hold_events.push((unit, false)),
981        }
982    }
983
984    fn apply_prepared(&mut self, t: Transaction)
985        requires
986            prepared_rule(*old(self), t),
987        ensures
988            final(self).last_seq == old(self).last_seq,
989            inv(*old(self)) ==> inv(*final(self)),
990            inv(*old(self)) ==> epochs_grow(*old(self), *final(self)),
991    {
992        let ghost pre = *self;
993        let id = t.id;
994        self.next_txn = id.0 + 1;
995        let mut path = Vec::new();
996        push_tag(&mut path, &t.state);
997        let why = String::new();
998        self.live.entries.push((id, LiveTxn { txn: t, ops: Vec::new(), path, why, boot_only: false }));
999        proof {
1000            let n = pre.live.entries@.len();
1001            assert(self.live.entries@ =~= pre.live.entries@.push(self.live.entries@[n as int]));
1002            if inv(pre) {
1003                lemma_push(pre, *self);
1004            }
1005        }
1006    }
1007
1008    #[allow(unused_variables)] // used in the specification
1009    fn apply_advance(&mut self, txn: TxnId, epoch: u64, to: TxnState, note: String, withdrawn_for: Option<TxnId>)
1010        requires
1011            advance_rule(*old(self), txn, epoch, to),
1012        ensures
1013            final(self).last_seq == old(self).last_seq,
1014            inv(*old(self)) ==> inv(*final(self)),
1015            inv(*old(self)) ==> epochs_grow(*old(self), *final(self)),
1016            to == TxnState::RecoveryFailed ==> has_id(final(self).interlock.entries@, txn),
1017    {
1018        let ghost pre = *self;
1019        let i = match find(&self.live, txn) {
1020            Some(i) => i,
1021            None => {
1022                proof {
1023                    assert(false);
1024                }
1025                return;
1026            },
1027        };
1028        proof {
1029            if inv(pre) {
1030                let j = choose|j: int|
1031                    0 <= j < pre.live.entries@.len() && (#[trigger] pre.live.entries@[j]).0 == txn && advance_ok(
1032                        pre,
1033                        j,
1034                        epoch,
1035                        to,
1036                    );
1037                lemma_unique_index(pre, i as int, j);
1038            }
1039        }
1040        let (id, mut l) = self.live.entries.remove(i);
1041        let failed = match to {
1042            TxnState::RecoveryFailed => true,
1043            _ => false,
1044        };
1045        if failed {
1046            let entry = InterlockEntry {
1047                txn,
1048                transition: l.txn.transition.clone(),
1049                failed_from: tag_string(&l.txn.state),
1050                base_override: l.txn.base_override,
1051                candidate_override: l.txn.candidate_override,
1052                revert_to: l.txn.revert_to,
1053                note: note.clone(),
1054            };
1055            put(&mut self.interlock, txn, entry);
1056        }
1057        push_tag(&mut l.path, &to);
1058        note_why(&mut l.why, &note);
1059        let publishing = match to {
1060            TxnState::Publishing => true,
1061            _ => false,
1062        };
1063        l.boot_only = l.boot_only || publishing;
1064        l.txn.state = to;
1065        if l.txn.state.is_terminal() {
1066            archive(&mut self.terminal, id, l, withdrawn_for);
1067            proof {
1068                assert(self.live.entries@ =~= pre.live.entries@.remove(i as int));
1069                if inv(pre) {
1070                    lemma_remove(pre, *self, i as int);
1071                }
1072            }
1073        } else {
1074            self.live.entries.insert(i, (id, l));
1075            proof {
1076                assert(self.live.entries@ =~= pre.live.entries@.update(i as int, self.live.entries@[i as int]));
1077                if inv(pre) {
1078                    let x = pre.live.entries@[i as int].1;
1079                    let y = self.live.entries@[i as int].1;
1080                    assert(generation(y) == generation(x));
1081                    lemma_update(pre, *self, i as int);
1082                }
1083            }
1084        }
1085    }
1086
1087    #[allow(unused_variables)] // used in the specification
1088    fn apply_takeover(&mut self, txn: TxnId, from_epoch: u64, lease: Lease)
1089        requires
1090            takeover_rule(*old(self), txn, from_epoch, lease),
1091        ensures
1092            final(self).last_seq == old(self).last_seq,
1093            inv(*old(self)) ==> inv(*final(self)),
1094            inv(*old(self)) ==> epochs_grow(*old(self), *final(self)),
1095    {
1096        let ghost pre = *self;
1097        let i = match find(&self.live, txn) {
1098            Some(i) => i,
1099            None => {
1100                proof {
1101                    assert(false);
1102                }
1103                return;
1104            },
1105        };
1106        proof {
1107            if inv(pre) {
1108                let j = choose|j: int|
1109                    0 <= j < pre.live.entries@.len() && (#[trigger] pre.live.entries@[j]).0 == txn
1110                        && pre.live.entries@[j].1.txn.lease.epoch == from_epoch && lease.epoch > from_epoch
1111                        && lease.txn == txn;
1112                lemma_unique_index(pre, i as int, j);
1113            }
1114        }
1115        let (id, mut l) = self.live.entries.remove(i);
1116        l.txn.lease = lease;
1117        self.live.entries.insert(i, (id, l));
1118        proof {
1119            assert(self.live.entries@ =~= pre.live.entries@.update(i as int, self.live.entries@[i as int]));
1120            if inv(pre) {
1121                lemma_update(pre, *self, i as int);
1122            }
1123        }
1124    }
1125
1126    #[allow(unused_variables)] // used in the specification
1127    fn apply_renew(&mut self, txn: TxnId, epoch: u64, at: Timestamp, worker_pid: Option<u32>)
1128        requires
1129            renew_rule(*old(self), txn, epoch),
1130        ensures
1131            final(self).last_seq == old(self).last_seq,
1132            inv(*old(self)) ==> inv(*final(self)),
1133            inv(*old(self)) ==> epochs_grow(*old(self), *final(self)),
1134    {
1135        let ghost pre = *self;
1136        let i = match find(&self.live, txn) {
1137            Some(i) => i,
1138            None => {
1139                proof {
1140                    assert(false);
1141                }
1142                return;
1143            },
1144        };
1145        let (id, mut l) = self.live.entries.remove(i);
1146        l.txn.lease.renewed = at;
1147        l.txn.lease.worker_pid = worker_pid;
1148        self.live.entries.insert(i, (id, l));
1149        proof {
1150            assert(self.live.entries@ =~= pre.live.entries@.update(i as int, self.live.entries@[i as int]));
1151            if inv(pre) {
1152                lemma_update(pre, *self, i as int);
1153            }
1154        }
1155    }
1156
1157    fn apply_op_begin(&mut self, op: OpId, call: String)
1158        requires
1159            op_begin_rule(*old(self), op),
1160        ensures
1161            final(self).last_seq == old(self).last_seq,
1162            inv(*old(self)) ==> inv(*final(self)),
1163            inv(*old(self)) ==> epochs_grow(*old(self), *final(self)),
1164    {
1165        let ghost pre = *self;
1166        let i = match find(&self.live, op.txn) {
1167            Some(i) => i,
1168            None => {
1169                proof {
1170                    assert(false);
1171                }
1172                return;
1173            },
1174        };
1175        let (id, mut l) = self.live.entries.remove(i);
1176        l.ops.push(OpRecord { op, call, ack: None });
1177        self.live.entries.insert(i, (id, l));
1178        proof {
1179            assert(self.live.entries@ =~= pre.live.entries@.update(i as int, self.live.entries@[i as int]));
1180            if inv(pre) {
1181                lemma_update(pre, *self, i as int);
1182            }
1183        }
1184    }
1185
1186    fn apply_op_ack(&mut self, op: OpId, outcome: Outcome)
1187        requires
1188            op_ack_rule(*old(self), op),
1189        ensures
1190            final(self).last_seq == old(self).last_seq,
1191            inv(*old(self)) ==> inv(*final(self)),
1192            inv(*old(self)) ==> epochs_grow(*old(self), *final(self)),
1193    {
1194        let ghost pre = *self;
1195        let i = match find(&self.live, op.txn) {
1196            Some(i) => i,
1197            None => {
1198                proof {
1199                    assert(false);
1200                }
1201                return;
1202            },
1203        };
1204        let (id, mut l) = self.live.entries.remove(i);
1205        let ghost before = l;
1206        let mut k: usize = 0;
1207        while k < l.ops.len()
1208            invariant
1209                k <= l.ops@.len(),
1210                l.txn == before.txn,
1211            decreases l.ops.len() - k,
1212        {
1213            if op_eq(l.ops[k].op, op) {
1214                let mut o = l.ops.remove(k);
1215                o.ack = Some(outcome);
1216                l.ops.insert(k, o);
1217                break;
1218            }
1219            k += 1;
1220        }
1221        self.live.entries.insert(i, (id, l));
1222        proof {
1223            assert(self.live.entries@ =~= pre.live.entries@.update(i as int, self.live.entries@[i as int]));
1224            if inv(pre) {
1225                lemma_update(pre, *self, i as int);
1226            }
1227        }
1228    }
1229}
1230
1231/// I6, as SPEC §7 states it, from the invariant: of two live generation
1232/// transactions at most one is active (in flight, or waiting in `Built`);
1233/// and while a generation transaction is in flight other than awaiting a
1234/// boot, no other transaction is in flight.
1235pub proof fn i6_one_generation_active(l: Ledger, a: int, b: int)
1236    requires
1237        inv(l),
1238        0 <= a < l.live.entries@.len(),
1239        0 <= b < l.live.entries@.len(),
1240        a != b,
1241    ensures
1242        ({
1243            let x = l.live.entries@[a].1;
1244            let y = l.live.entries@[b].1;
1245            &&& (generation(x) && generation(y)) ==> !((flying(x.txn.state) || x.txn.state is Built) && (flying(
1246                y.txn.state,
1247            ) || y.txn.state is Built))
1248            &&& (generation(x) && x.txn.state.exclusive() && !(x.txn.state is Promoted && x.boot_only)) ==> !flying(
1249                y.txn.state,
1250            )
1251        }),
1252{
1253    assert(pair_ok(l.live.entries@[a], l.live.entries@[b]));
1254}
1255
1256/// Changing one live transaction keeps the invariant when its id, its
1257/// lease's transaction, its transition and recovery stay, its epoch does not
1258/// drop, and its state stays or is compatible with every other live
1259/// transaction, as `advance_ok` requires (I6).
1260proof fn lemma_update(pre: Ledger, post: Ledger, i: int)
1261    requires
1262        inv(pre),
1263        0 <= i < pre.live.entries@.len(),
1264        post.next_txn == pre.next_txn,
1265        post.live.entries@ == pre.live.entries@.update(i, post.live.entries@[i]),
1266        live_ok(post.live.entries@[i], post.next_txn),
1267        post.live.entries@[i].0 == pre.live.entries@[i].0,
1268        generation(post.live.entries@[i].1) == generation(pre.live.entries@[i].1),
1269        post.live.entries@[i].1.txn.lease.epoch >= pre.live.entries@[i].1.txn.lease.epoch,
1270        post.live.entries@[i].1.boot_only == pre.live.entries@[i].1.boot_only
1271            || !(post.live.entries@[i].1.txn.state is Promoted),
1272        post.live.entries@[i].1.txn.state == pre.live.entries@[i].1.txn.state || forall|j: int|
1273            0 <= j < pre.live.entries@.len() && j != i ==> compatible(
1274                post.live.entries@[i].1.txn.state,
1275                generation(post.live.entries@[i].1),
1276                post.live.entries@[i].1.boot_only,
1277                (#[trigger] pre.live.entries@[j]).1.txn.state,
1278                generation(pre.live.entries@[j].1),
1279                pre.live.entries@[j].1.boot_only,
1280            ),
1281    ensures
1282        inv(post),
1283        epochs_grow(pre, post),
1284{
1285    let a = pre.live.entries@;
1286    let b = post.live.entries@;
1287    assert forall|x: int, y: int| 0 <= x < b.len() && 0 <= y < b.len() && x != y implies pair_ok(
1288        #[trigger] b[x],
1289        #[trigger] b[y],
1290    ) by {
1291        assert(pair_ok(a[x], a[y]));
1292        if x == i {
1293            assert(pair_ok(a[i], a[y]));
1294        } else if y == i {
1295            assert(pair_ok(a[x], a[i]));
1296        }
1297    }
1298    assert forall|x: int| 0 <= x < b.len() implies live_ok(#[trigger] b[x], post.next_txn) by {
1299        if x != i {
1300            assert(live_ok(a[x], pre.next_txn));
1301        }
1302    }
1303    assert forall|x: int, y: int| 0 <= x < a.len() && 0 <= y < b.len() && (#[trigger] a[x]).0 == (#[trigger] b[y]).0 implies a[x].1.txn.lease.epoch <= b[y].1.txn.lease.epoch by {
1304        lemma_unique_index(pre, x, y);
1305    }
1306}
1307
1308/// Removing a live transaction keeps the invariant.
1309proof fn lemma_remove(pre: Ledger, post: Ledger, i: int)
1310    requires
1311        inv(pre),
1312        0 <= i < pre.live.entries@.len(),
1313        post.next_txn == pre.next_txn,
1314        post.live.entries@ == pre.live.entries@.remove(i),
1315    ensures
1316        inv(post),
1317        epochs_grow(pre, post),
1318{
1319    let a = pre.live.entries@;
1320    let b = post.live.entries@;
1321    assert forall|x: int| 0 <= x < b.len() implies #[trigger] b[x] == a[if x < i { x } else { x + 1 }] by {}
1322    assert forall|x: int, y: int| 0 <= x < b.len() && 0 <= y < b.len() && x != y implies pair_ok(
1323        #[trigger] b[x],
1324        #[trigger] b[y],
1325    ) by {
1326        let x2 = if x < i { x } else { x + 1 };
1327        let y2 = if y < i { y } else { y + 1 };
1328        assert(pair_ok(a[x2], a[y2]));
1329    }
1330    assert forall|x: int| 0 <= x < b.len() implies live_ok(#[trigger] b[x], post.next_txn) by {
1331        let x2 = if x < i { x } else { x + 1 };
1332        assert(live_ok(a[x2], pre.next_txn));
1333    }
1334    assert forall|x: int, y: int| 0 <= x < a.len() && 0 <= y < b.len() && (#[trigger] a[x]).0 == (#[trigger] b[y]).0 implies a[x].1.txn.lease.epoch <= b[y].1.txn.lease.epoch by {
1335        let y2 = if y < i { y } else { y + 1 };
1336        lemma_unique_index(pre, x, y2);
1337    }
1338}
1339
1340/// Adding a newly Prepared transaction keeps the invariant.
1341proof fn lemma_push(pre: Ledger, post: Ledger)
1342    requires
1343        inv(pre),
1344        post.live.entries@.len() == pre.live.entries@.len() + 1,
1345        post.live.entries@ == pre.live.entries@.push(post.live.entries@[pre.live.entries@.len() as int]),
1346        post.next_txn > pre.next_txn,
1347        ({
1348            let x = post.live.entries@[pre.live.entries@.len() as int];
1349            &&& live_ok(x, post.next_txn)
1350            &&& x.1.txn.state is Prepared
1351            &&& x.0.0 >= pre.next_txn
1352        }),
1353    ensures
1354        inv(post),
1355        epochs_grow(pre, post),
1356{
1357    let a = pre.live.entries@;
1358    let b = post.live.entries@;
1359    let n = a.len() as int;
1360    assert forall|x: int| 0 <= x < b.len() implies live_ok(#[trigger] b[x], post.next_txn) by {
1361        if x < n {
1362            assert(live_ok(a[x], pre.next_txn));
1363        }
1364    }
1365    assert forall|x: int, y: int| 0 <= x < b.len() && 0 <= y < b.len() && x != y implies pair_ok(
1366        #[trigger] b[x],
1367        #[trigger] b[y],
1368    ) by {
1369        if x < n && y < n {
1370            assert(pair_ok(a[x], a[y]));
1371        } else if x < n {
1372            assert(live_ok(a[x], pre.next_txn));
1373        } else {
1374            assert(live_ok(a[y], pre.next_txn));
1375        }
1376    }
1377    assert forall|x: int, y: int| 0 <= x < a.len() && 0 <= y < b.len() && (#[trigger] a[x]).0 == (#[trigger] b[y]).0 implies a[x].1.txn.lease.epoch <= b[y].1.txn.lease.epoch by {
1378        if y < n {
1379            lemma_unique_index(pre, x, y);
1380        } else {
1381            assert(live_ok(a[x], pre.next_txn));
1382        }
1383    }
1384}
1385
1386} // verus!
1387
1388pub const TERMINAL_KEEP: usize = 256;
1389
1390impl Ledger {
1391    pub fn epoch(&self, txn: TxnId) -> Option<u64> {
1392        self.live.get(&txn).map(|l| l.txn.lease.epoch).or_else(|| self.terminal.get(&txn).map(|t| t.epoch))
1393    }
1394
1395    pub fn state(&self, txn: TxnId) -> Option<TxnState> {
1396        self.live.get(&txn).map(|l| l.txn.state.clone()).or_else(|| self.terminal.get(&txn).map(|t| t.state.clone()))
1397    }
1398
1399    pub fn path(&self, txn: TxnId) -> Option<&[String]> {
1400        self.live.get(&txn).map(|l| &l.path[..]).or_else(|| self.terminal.get(&txn).map(|t| &t.path[..]))
1401    }
1402
1403    pub fn interlocked(&self) -> bool {
1404        !self.interlock.is_empty()
1405    }
1406
1407    /// The uid the agent `name`'s running `instance` acts for (§11, I24):
1408    /// the start that journaled that very instance under that name. A zero
1409    /// instance matches nothing.
1410    pub fn agent_for(&self, name: &str, instance: [u8; 16]) -> Option<u32> {
1411        if instance == [0; 16] {
1412            return None;
1413        }
1414        self.agents.iter().rev().find(|(n, i, _)| *i == instance && n == name).map(|(_, _, by)| *by)
1415    }
1416
1417    /// Who started the last instance under `name`, for a refusal's message.
1418    pub fn agent_owner(&self, name: &str) -> Option<u32> {
1419        self.agents.iter().rev().find(|(n, _, _)| n == name).map(|(_, _, by)| *by)
1420    }
1421
1422    /// §12d: the units whose traffic the journal says is held now: the
1423    /// last event for each is an apply. The recovery source of truth after a
1424    /// restart (the daemon reconciles it with the host, failing closed).
1425    pub fn holds(&self) -> std::collections::BTreeSet<UnitId> {
1426        let mut held = std::collections::BTreeSet::new();
1427        for (u, on) in &self.hold_events {
1428            if *on {
1429                held.insert(u.clone());
1430            } else {
1431                held.remove(u);
1432            }
1433        }
1434        held
1435    }
1436}
1437
1438impl LiveTxn {
1439    pub fn unacked_ops(&self) -> impl Iterator<Item = &OpRecord> {
1440        self.ops.iter().filter(|o| o.ack.is_none())
1441    }
1442}
1443
1444impl<V> Default for IdMap<V> {
1445    fn default() -> Self {
1446        IdMap { entries: Vec::new() }
1447    }
1448}
1449
1450impl<V> IdMap<V> {
1451    fn find_index(&self, id: &TxnId) -> Result<usize, usize> {
1452        self.entries.binary_search_by(|(k, _)| k.cmp(id))
1453    }
1454
1455    pub fn get(&self, id: &TxnId) -> Option<&V> {
1456        self.find_index(id).ok().map(|i| &self.entries[i].1)
1457    }
1458
1459    pub fn contains_key(&self, id: &TxnId) -> bool {
1460        self.find_index(id).is_ok()
1461    }
1462
1463    pub fn len(&self) -> usize {
1464        self.entries.len()
1465    }
1466
1467    pub fn is_empty(&self) -> bool {
1468        self.entries.is_empty()
1469    }
1470
1471    pub fn keys(&self) -> impl DoubleEndedIterator<Item = &TxnId> {
1472        self.entries.iter().map(|(k, _)| k)
1473    }
1474
1475    pub fn values(&self) -> impl DoubleEndedIterator<Item = &V> {
1476        self.entries.iter().map(|(_, v)| v)
1477    }
1478
1479    pub fn iter(&self) -> impl DoubleEndedIterator<Item = (&TxnId, &V)> {
1480        self.entries.iter().map(|(k, v)| (k, v))
1481    }
1482}
1483
1484impl<V> std::ops::Index<&TxnId> for IdMap<V> {
1485    type Output = V;
1486
1487    fn index(&self, id: &TxnId) -> &V {
1488        self.get(id).expect("no entry for this transaction")
1489    }
1490}
1491
1492impl<V: Serialize> Serialize for IdMap<V> {
1493    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1494        s.collect_map(self.iter())
1495    }
1496}
1497
1498impl<'de, V: Deserialize<'de>> Deserialize<'de> for IdMap<V> {
1499    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
1500        // A BTreeMap is already sorted and unique.
1501        Ok(IdMap { entries: BTreeMap::<TxnId, V>::deserialize(d)?.into_iter().collect() })
1502    }
1503}