@@ -23,12 +23,17 @@ without any vote.
2323pub struct AllowedLauncherImage {
2424 launcher_hash : LauncherImageHash ,
2525 compose_hashes : Vec <LauncherDockerComposeHash >,
26- last_used : Timestamp , // NEW: stamped when voted in / re-voted, and refreshed on
27- // each attestation by a *current participant*.
26+ expires_at : Timestamp , // NEW: stamped `now + TTL` when voted in / re-voted, and
27+ // restamped on each attestation by a *current participant*.
2828}
2929```
3030
31- An entry is ** expired** when ` last_used + TTL < now ` .
31+ An entry is ** expired** when ` expires_at < now ` . The expiry is computed once at write
32+ time (` expires_at = now + TTL ` , saturating on overflow) — mirroring how attestations store
33+ their own expiry — so reads are a plain ` expires_at < now ` comparison and the TTL is only
34+ needed at the write sites (vote-in, refresh, migration), not threaded through every read.
35+ A consequence: changing the TTL config applies to an entry on its * next* stamp (a node
36+ resubmits hourly), not retroactively.
3237TTL is a new config field ` launcher_hash_unused_ttl_seconds ` , default ** 14 days** .
3338Config validation enforces `launcher_hash_unused_ttl_seconds >=
3439mpc_attestation::attestation::DEFAULT_EXPIRATION_DURATION_SECONDS` (the
@@ -40,7 +45,7 @@ period, 7 days).
4045Three parts:
4146
42471 . ** Refresh on use** — after a successful verify in ` submit_participant_info `
43- (hourly per node), the contract sets ` last_used = now` on the entry
48+ (hourly per node), the contract restamps ` expires_at = now + TTL ` on the entry
4449 owning the validated compose hash. No node-side changes.
4550 ** Only a current participant's submission refreshes the timestamp.**
4651 ` submit_participant_info ` is also callable by prospective (non-participant)
@@ -64,19 +69,19 @@ Three parts:
6469 ` clean_tee_status_tera_gas ` etc. used by the post-resharing cleanups in
6570 ` vote_reshared ` .
6671
67- Stamping ` last_used ` at vote-in means a hash voted in but ** never adopted**
72+ Stamping ` expires_at = now + TTL ` at vote-in means a hash voted in but ** never adopted**
6873(e.g. a newly voted launcher image no node ever migrated to) also expires after
6974one TTL window. Recovery: ` vote_add_launcher_hash ` for an already-present entry
70- refreshes its ` last_used ` (threshold vote, not unanimity).
75+ restamps its ` expires_at ` (threshold vote, not unanimity).
7176
7277### Safety invariants
7378
7479- A hash backing a ** valid participant attestation is never expired** : a
7580 current participant resubmits hourly, so its valid attestation (at most
76- ` DEFAULT_EXPIRATION_DURATION_SECONDS ` , currently 7 days, old) refreshed the
77- entry's ` last_used ` that recently, and ` last_used + TTL >= now` holds whenever
78- ` TTL >= DEFAULT_EXPIRATION_DURATION_SECONDS ` — regardless of the constant's
79- exact value. Enforced by config validation
81+ ` DEFAULT_EXPIRATION_DURATION_SECONDS ` , currently 7 days, old) restamped the
82+ entry's ` expires_at = now + TTL ` that recently, so ` expires_at >= now` holds
83+ whenever ` TTL >= DEFAULT_EXPIRATION_DURATION_SECONDS ` — regardless of the
84+ constant's exact value. Enforced by config validation
8085 (` launcher_hash_unused_ttl_seconds >= DEFAULT_EXPIRATION_DURATION_SECONDS ` );
8186 the 14-day default is ` >= DEFAULT_EXPIRATION_DURATION_SECONDS ` (the
8287 attestation window, currently 7 days), leaving ample margin.
@@ -99,12 +104,12 @@ sequenceDiagram
99104 participant N as Nodes
100105
101106 Ops->>C: vote_add_launcher_hash(B), threshold reached
102- Note over C: B.last_used = now, 14-day adoption clock starts
107+ Note over C: B.expires_at = now + 14d, adoption clock starts
103108 N->>C: hourly submit_participant_info with launcher A
104- Note over C: A.last_used refreshed hourly
109+ Note over C: A.expires_at restamped hourly (now + 14d)
105110 N->>C: hourly submit_participant_info with launcher B (after migration)
106- Note over C: B refreshed . A freezes once the last node leaves it
107- Note over C: A.last_used + 14d passes. A is rejected by all reads
111+ Note over C: B restamped . A freezes once the last node leaves it
112+ Note over C: A.expires_at passes. A is rejected by all reads
108113 Ops->>C: verify_tee (routine)
109114 Note over C: spawns detached self-call clean_expired_launcher_hashes
110115 C-->>C: clean_expired_launcher_hashes (private, separate receipt)
@@ -125,8 +130,10 @@ sequenceDiagram
125130## Migration
126131
127132New borsh field ⇒ state migration: existing entries get
128- ` last_used = migration time ` — every current hash starts a fresh 14-day clock;
129- stale testnet hashes age out with no further action.
133+ ` expires_at = migration time + TTL ` — every current hash starts a fresh 14-day
134+ clock; stale testnet hashes age out with no further action. The 3.13.0 migration
135+ also stamps an expiry on legacy ` MockAttestation::Valid ` entries (from #3785 ) so
136+ they become cleanable; the two steps run together.
130137
131138## Alternatives considered
132139
0 commit comments