feat(contract): auto-expire unused launcher image hashes#3564
feat(contract): auto-expire unused launcher image hashes#3564barakeinav1 wants to merge 4 commits into
Conversation
2c212db to
ba05472
Compare
| pub(crate) launcher_hash: LauncherImageHash, | ||
| pub(crate) compose_hashes: Vec<LauncherDockerComposeHash>, | ||
| pub(crate) added: Timestamp, | ||
| pub(crate) last_attested: Timestamp, |
There was a problem hiding this comment.
Two keep-alive signals, expiry is max(added, last_attested):
added— voted-in/re-vote time; gives a freshly voted hash a full TTL grace before any node runs it.last_attested— last participant attestation; keeps an in-use hash alive.
Alive if either is recent. added is also read on its own to pick the newest entry for the all-expired fallback, so the pair is not collapsible to one field.
ba05472 to
b2d72fe
Compare
Launcher image hashes accumulated forever; removal required a unanimous vote. This adds usage-based expiry: - AllowedLauncherImage gains added/last_attested timestamps; an entry is expired when max(added, last_attested) + TTL < now - last_attested is refreshed on a successful attestation, but ONLY for a current participant (enforced by requiring an AuthenticatedParticipantId); a prospective/non-participant node cannot keep a stale launcher alive - reads filter out expired entries, with a newest-entry fallback so the allowed set never goes empty - verify_tee spawns a detached self-call to a new #[private] clean_expired_launcher_hashes that sweeps expired entries from storage - re-voting an existing launcher hash refreshes its added timestamp - new config launcher_hash_unused_ttl_seconds (default 14d), validated >= DEFAULT_EXPIRATION_DURATION_SECONDS - state migration (v3_12_0_state) initializes timestamps for existing entries Updates the design doc status to Implemented and regenerates the borsh-schema and ABI snapshots for the new fields/method. Closes #3381
b2d72fe to
99f6904
Compare
There was a problem hiding this comment.
Pull request overview
Implements the approved “auto-expire unused launcher image hashes” design by adding usage-based expiry for allowed launcher images in the contract, including config/ABI updates, state migration for the new borsh layout, and test coverage for refresh/expiry/cleanup behavior.
Changes:
- Add
added/last_attestedtimestamps to launcher allowlist entries and filter expired entries at read/verify time (with newest-entry fallback). - Refresh
last_attestedon successful participant submissions and add a detached private cleanup self-call to physically evict expired entries. - Introduce new config knobs (
launcher_hash_unused_ttl_seconds,clean_expired_launcher_hashes_tera_gas) with interface + DTO mapping + snapshots + migration/tests updated.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/design/auto-remove-launcher-hashes-design.md | Marks the design as implemented and updates wording/invariants/decisions to match the shipped behavior. |
| crates/test-utils/src/contract_types.rs | Extends dummy config builder with the new TTL + cleanup gas fields. |
| crates/near-mpc-contract-interface/src/types/config.rs | Adds the new config fields to InitConfig/Config and updates serialization tests. |
| crates/near-mpc-contract-interface/src/method_names.rs | Adds the clean_expired_launcher_hashes method name constant. |
| crates/mpc-attestation/src/attestation.rs | Exposes the launcher compose hash from a verified attestation to support refresh-on-use. |
| crates/contract/tests/snapshots/abi__abi_has_not_changed.snap | Updates ABI snapshot for the new private cleanup method and config fields. |
| crates/contract/tests/sandbox/upgrade_from_current_contract.rs | Updates sandbox upgrade test config to include the new TTL + cleanup gas fields. |
| crates/contract/tests/sandbox/contract_configuration.rs | Updates sandbox init config test to include the new TTL + cleanup gas fields. |
| crates/contract/src/v3_12_0_state.rs | Adds 3.12.0 shadow types for launcher allowlist migration and a migration round-trip test. |
| crates/contract/src/tee/tee_state.rs | Threads launcher TTL through verification paths and adds refresh-on-use + cleanup hooks. |
| crates/contract/src/tee/proposal.rs | Implements TTL filtering, fallback selection, refresh-on-use, re-vote refresh, and cleanup for launcher allowlist entries (with tests). |
| crates/contract/src/snapshots/mpc_contract__tests__mpc_contract_borsh_schema_has_not_changed.snap | Updates borsh schema snapshot for new fields. |
| crates/contract/src/lib.rs | Wires TTL into submit/verify/read paths, spawns detached cleanup self-call, adds private cleanup endpoint, and validates config updates. |
| crates/contract/src/dto_mapping.rs | Maps new config fields between DTOs and contract config. |
| crates/contract/src/config.rs | Adds new config fields, defaults, and a validation invariant tying TTL to attestation expiry window. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| pub fn update_config(&mut self, config: dtos::Config) { | ||
| self.config = config.into(); | ||
| let new_config: Config = config.into(); | ||
| if let Err(e) = new_config.validate() { | ||
| env::panic_str(e); | ||
| } | ||
| self.config = new_config; |
There was a problem hiding this comment.
Good catch — fixed in d7c0c65. init and init_running now build the config, call config.validate(), and propagate the error (both are #[handle_result]), so the contract can no longer be initialized with a TTL below the attestation validity window. Added init_rejects_launcher_ttl_below_attestation_validity to lock it in.
Pull request overviewImplements usage-based expiry for Changes:
Reviewed changesPer-file summary
FindingsBlocking (must fix before merge):
Non-blocking (nits, follow-ups, suggestions):
|
Address review feedback: - init/init_running now validate the config (not just update_config), so the contract cannot be initialized with launcher_hash_unused_ttl_seconds below the attestation validity window; add a regression test. - dummy_config: give clean_expired_launcher_hashes_tera_gas a unique offset (was duplicating remove_non_participant_tee_verifier_votes_tera_gas).
Address non-blocking review comments: - is_expired: note why overflow returns not-expired (never panic on a bogus timestamp) - add(): note re-vote refreshes only the clock; compose hashes managed separately - refresh_launcher_usage: note the AuthenticatedParticipantId is a capability token
|
Thanks for the thorough review. Responses inline:
Fixed in d7c0c65 — both init paths now call
Done (a8694aa): added a comment explaining the deadline is unrepresentably far off and we must never panic on a bogus timestamp.
Done (a8694aa): the re-vote branch now notes compose hashes are maintained separately via
Done (a8694aa): docstring now states it is a capability token with the value intentionally unused.
Left as-is: the proposal-level
Left as-is — micro / the list is tiny, as you noted. |
…and design doc DEFAULT_EXPIRATION_DURATION_SECONDS was changed 7d -> 1d in #3626. Update the stale '7 days' wording; reference the constant instead of hardcoding a day-count in test comments to avoid future drift. The MPC docker-image grace period (DEFAULT_TEE_UPGRADE_DEADLINE_DURATION_SECONDS) remains 7 days.
Good catch — fixed in cb955fb. It was deliberately changed 7d → 1d in #3626, so the wording was stale. Updated the comments/design doc to "1 day", and to avoid this drift recurring I dropped the hardcoded day-count in the test comments (they now just reference the constant). The design-doc safety argument is now phrased around the constant ( |
Implements the approved design (docs/design/auto-remove-launcher-hashes-design.md, PR #3488).
Usage-based expiry for
allowed_launcher_image_hashes, replacing unanimous-vote-only removal for routine cleanup:AllowedLauncherImagegainsadded/last_attested; expired whenmax(added, last_attested) + TTL < nowlast_attestedis refreshed on a successful attestation insubmit_participant_info, but only for a current participant (enforced by requiring anAuthenticatedParticipantId) — a prospective/non-participant node cannot keep a launcher alive. No node-side changes.verify_teespawns a detached self-call to a new#[private] clean_expired_launcher_hashes(gas viaclean_expired_launcher_hashes_tera_gas); never removes the last entryadded(recovery for never-adopted hashes)launcher_hash_unused_ttl_seconds(default 14d), validated>= DEFAULT_EXPIRATION_DURATION_SECONDSTests: refresh-on-use, expiry filtering, newest fallback,
cleanup_expired, re-vote refresh, a migration round-trip test, plus updated existing launcher/tee tests.Reviewer note — migration assumption
The migration shadows the 3.12.0 layout in
v3_12_0_state.rs, addingOldTeeState/OldAllowedLauncherImageand filling the new timestamps withTimestamp::now()on upgrade. Onlyallowed_launcher_imageschanged borsh layout; all otherTeeStatefields reuse the real (byte-identical) types. Please confirm the live deployed version is 3.12.0 so this shadow path is the correct one. (migrating_launcher_images_preserves_hashes_and_stamps_timestampscovers the round-trip, including that timestamps are stamped to migration time rather than defaulting to epoch 0.)Closes #3381