You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implements the funding model designed in #3972 and specified in docs/design/operator-prepaid-attestation-storage.md. An operator prepays for attestation-entry storage in a separate transaction; the node keeps self-submitting with its deposit-less function-call key. One prepayment buys one grant — permission to hold one attestation entry — and a grant returns when its entry is reclaimed. Fee is 0.02 NEAR. Nothing is refunded.
This closes the drain that is live today: submit_participant_info takes no deposit on every deployed version, so any account can create unlimited entries at roughly 1/7th of what each costs the contract, and an exhausted balance stops the contract writing state at all.
Acceptance Criteria
prepay_attestation_storage(account_id, grants) — #[payable], permissionless, requires an attached deposit of exactly fee × grants and rejects anything else.
available_attestation_grants(account_id) -> u32 view. No dedicated fee view: config() already returns attestation_storage_fee_millinear, and an operator reads it once by hand (#4011 discussion).
available_attestation_grants: LookupMap<AccountId, u32> holds available grants; prepay +1, new entry −1, reclaimed entry +1, row deleted at zero.
Charging rules: a re-attestation under a TLS key the caller already owns consumes nothing; a new entry consumes one grant and is rejected if none is available; clean_invalid_attestations returns one grant to the owner of each entry it removes.
The precondition is evaluated read-only at the top of submit_participant_info — before any Mock verification or verify_quote round trip — and keys on ownership, not TLS-key presence. The grant is consumed at insert, so a failed attestation consumes nothing.
Config.attestation_storage_fee is governance-votable, defaulting to 0.02 NEAR, with the ConfigExt DTO plumbing in crates/contract/src/dto_mapping.rs.
State migration for the new map and Config field: frozen snapshot module plus a migrate() arm as crates/contract/src/v3_13_0_state.rs does, with regenerated borsh-schema and ABI snapshots.
clean_invalid_attestations_tera_gas and RESHARE_CLEAN_INVALID_ATTESTATIONS_MAX_SCAN re-validated against the added per-entry write, which is a row insert whenever the owner's row was deleted at zero.
Tests: prepay-then-submit; re-attestation consuming no grant; prepay-on-behalf by another account; rejection with no grant; a TLS key owned by another account rejected by the early check; a failed attestation consuming nothing on both sync and async paths; a reclaimed entry returning a spendable grant; the row deleted at zero; a kicked node keeping its grants; two prepayments allowing two entries; and a clean_invalid_attestations gas-budget guard for the worst case (every scanned entry removed, every owner's row absent).
#3991 - can potentially be done in same PR as well.
Check the stored-entry count before deploying. Pre-upgrade entries yield a grant when swept, since the rule cannot distinguish them from paid ones. That is accepted at today's scale — 14 entries on mainnet, 31 on testnet — but a count in the thousands would mean the still-open drain was exploited first, and those entries should be purged rather than granted.
Operators need one grant per node plus a spare, because a grant returns only once its entry expires (7 days). An owner-callable release method was considered and declined; see the design doc's Alternatives.
Background
Implements the funding model designed in #3972 and specified in
docs/design/operator-prepaid-attestation-storage.md. An operator prepays for attestation-entry storage in a separate transaction; the node keeps self-submitting with its deposit-less function-call key. One prepayment buys one grant — permission to hold one attestation entry — and a grant returns when its entry is reclaimed. Fee is 0.02 NEAR. Nothing is refunded.This closes the drain that is live today:
submit_participant_infotakes no deposit on every deployed version, so any account can create unlimited entries at roughly 1/7th of what each costs the contract, and an exhausted balance stops the contract writing state at all.Acceptance Criteria
prepay_attestation_storage(account_id, grants)—#[payable], permissionless, requires an attached deposit of exactlyfee × grantsand rejects anything else.available_attestation_grants(account_id) -> u32view. No dedicated fee view:config()already returnsattestation_storage_fee_millinear, and an operator reads it once by hand (#4011 discussion).available_attestation_grants: LookupMap<AccountId, u32>holds available grants; prepay+1, new entry−1, reclaimed entry+1, row deleted at zero.clean_invalid_attestationsreturns one grant to the owner of each entry it removes.submit_participant_info— before anyMockverification orverify_quoteround trip — and keys on ownership, not TLS-key presence. The grant is consumed at insert, so a failed attestation consumes nothing.Config.attestation_storage_feeis governance-votable, defaulting to 0.02 NEAR, with theConfigExtDTO plumbing incrates/contract/src/dto_mapping.rs.Configfield: frozen snapshot module plus amigrate()arm ascrates/contract/src/v3_13_0_state.rsdoes, with regenerated borsh-schema and ABI snapshots.clean_invalid_attestations_tera_gasandRESHARE_CLEAN_INVALID_ATTESTATIONS_MAX_SCANre-validated against the added per-entry write, which is a row insert whenever the owner's row was deleted at zero.clean_invalid_attestationsgas-budget guard for the worst case (every scanned entry removed, every owner's row absent).#3991 - can potentially be done in same PR as well.
Resources & Additional Notes
fail_attestation_submissionstays removable independently of this work.