Commit a9970fe
committed
feat(contract): operator-prepaid attestation storage (grant counter)
Implements the design in docs/design/operator-prepaid-attestation-storage.md
(#4015). An operator prepays for attestation-entry storage in a separate
transaction; the node keeps self-submitting with its deposit-less function-call key.
Payment and submission have to be separate: a function-call key cannot attach a
deposit, and report_data binds the quote to env::signer_account_pk(), so neither
party can do both halves.
One prepayment buys one grant -- permission to hold one stored attestation entry --
and the grant returns when that entry is reclaimed, so it is a slot the operator
keeps rather than a per-attestation charge.
Contract:
- prepay_attestation_storage(account_id, grants), payable and permissionless,
requiring exactly fee x grants so there is no remainder to keep or refund.
- available_attestation_grants(account_id) view. The fee is read from config();
there is deliberately no dedicated view for it.
- available_attestation_grants: LookupMap<AccountId, u32> holds available grants;
the row is removed at zero so the map does not accumulate rows for accounts
holding none.
- Config.attestation_storage_fee_millinear defaults to 20 (0.02 NEAR) and is votable
through ConfigExt. Zero stays permitted: the value is governance's to choose.
Charging rules: a re-attestation under a key the caller already owns consumes
nothing; a new entry consumes one grant; clean_invalid_attestations returns one grant
to the owner of each entry it removes. The precondition is read-only and runs before
any verification, and compares the owning account rather than just testing key
presence -- otherwise a submission for somebody else's key would be classified as
needing no grant and would still reach verify_quote. It is re-checked inside
resolve_verification, since that callback runs in a later receipt where the grant may
since have been consumed.
Entries that predate the fee need no handling: they already hold a slot no grant was
bought for, and re-attestation is free, so those operators need no grant and no
action. Migration just initialises the map.
TeeState::clean_invalid_attestations now returns the owners of the entries it removed
rather than a count, so the caller can credit them; MpcContract still returns the
count, leaving the external interface unchanged.
Docs: operator guide gains the prepayment step after Create a NEAR Account for Your
Node, where the operator still holds that account's full-access key and the node has
not started yet; it reads the fee from config() rather than hard-coding it, and warns
that a grant prepaid to a mistyped account cannot be recovered. Drops the stale "will
incur a cost (TBD, XXX NEAR)" note citing the closed #903. Both new methods are
documented in the contract README's User API. Five docstrings that still claimed
storage is contract-funded now describe the grant instead.
Tests cover the guards as well as the happy path: exact-deposit rejection either side
by one yocto, zero grants, one account funding another, rejection without a grant, a
key owned by another account rejected before verification by its concrete error, and
a swept entry returning a grant that is then spendable without paying again. Both
sandbox and in-process harnesses prepay only when a submission would actually consume
a grant, and the e2e harness prepays for every node in the cluster -- a node joining
by resharing attests from its own process with a key that cannot attach a deposit, so
its grant must exist beforehand.
Sweep gas is out of scope and tracked in #4035: this adds a per-removal write that
takes the marginal cost from 0.347 to 0.504 TGas, but the budget was already short of
RESHARE_CLEAN_INVALID_ATTESTATIONS_MAX_SCAN on main.1 parent f13945f commit a9970fe
22 files changed
Lines changed: 879 additions & 62 deletions
File tree
- crates
- contract
- src
- snapshots
- tee
- tests
- inprocess
- sandbox
- utils
- snapshots
- e2e-tests/src
- near-mpc-contract-interface/src/types
- test-utils/src
- docs
- design
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
264 | 264 | | |
265 | 265 | | |
266 | 266 | | |
| 267 | + | |
| 268 | + | |
267 | 269 | | |
268 | 270 | | |
269 | 271 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
26 | 30 | | |
27 | 31 | | |
28 | 32 | | |
| |||
81 | 85 | | |
82 | 86 | | |
83 | 87 | | |
| 88 | + | |
| 89 | + | |
84 | 90 | | |
85 | 91 | | |
86 | 92 | | |
| |||
110 | 116 | | |
111 | 117 | | |
112 | 118 | | |
| 119 | + | |
113 | 120 | | |
114 | 121 | | |
115 | 122 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
501 | 501 | | |
502 | 502 | | |
503 | 503 | | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
504 | 507 | | |
505 | 508 | | |
506 | 509 | | |
| |||
533 | 536 | | |
534 | 537 | | |
535 | 538 | | |
| 539 | + | |
536 | 540 | | |
537 | 541 | | |
538 | 542 | | |
| |||
564 | 568 | | |
565 | 569 | | |
566 | 570 | | |
| 571 | + | |
567 | 572 | | |
568 | 573 | | |
569 | 574 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
127 | 127 | | |
128 | 128 | | |
129 | 129 | | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
130 | 138 | | |
131 | 139 | | |
132 | 140 | | |
| |||
0 commit comments