feat: burn gas key balances on key/account deletion#14933
Conversation
| state_update: &mut TrieUpdate, | ||
| account_id: &AccountId, | ||
| ) -> Result<(), StorageError> { | ||
| ) -> Result<Balance, StorageError> { |
There was a problem hiding this comment.
We can make a separate utility function instead, it seemed easier to return it here since we already iterate keys.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #14933 +/- ##
==========================================
+ Coverage 68.74% 68.76% +0.01%
==========================================
Files 918 918
Lines 200153 200301 +148
Branches 200153 200301 +148
==========================================
+ Hits 137605 137732 +127
- Misses 56627 56636 +9
- Partials 5921 5933 +12
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR adds explicit accounting for tokens burned when gas keys (or accounts containing gas keys) are deleted, so the protocol can correctly track total supply changes attributable to gas-key balance destruction.
Changes:
- Add
tokens_burnt: BalancetoActionResult, including merge/default behavior. - Burn and accumulate remaining gas-key balances on
DeleteKey(gas key) andDeleteAccount, and incorporate it into receipttokens_burnt. - Update
remove_accountto return total gas-key balances removed; add unit tests and expandExecutionOutcome::tokens_burntdocs.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| runtime/runtime/src/lib.rs | Adds ActionResult.tokens_burnt and folds it into receipt burn accounting. |
| runtime/runtime/src/actions.rs | Collects gas-key balances burned during account deletion via updated remove_account. |
| runtime/runtime/src/access_keys.rs | Burns gas-key balances on gas-key deletion; adds tests for key/account deletion burn totals. |
| core/store/src/utils/mod.rs | Changes remove_account to compute/return total gas-key balances removed. |
| core/primitives/src/transaction.rs | Updates ExecutionOutcome::tokens_burnt documentation to mention gas-key deletion burns. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| .unwrap(); | ||
| tx_burnt_amount = safe_add_balance(tx_burnt_amount, gas_refund_result.price_surplus)?; | ||
| tx_burnt_amount = safe_add_balance(tx_burnt_amount, gas_refund_result.refund_penalty)?; | ||
| tx_burnt_amount = safe_add_balance(tx_burnt_amount, result.tokens_burnt)?; |
There was a problem hiding this comment.
result.tokens_burnt is added into tx_burnt_amount unconditionally, but state changes from deleting gas keys/accounts are rolled back when result.result is Err (state_update.rollback() above). This can report/charge burned gas-key balances for receipts that ultimately fail and revert, leaving the gas keys (and balances) intact. Consider only adding result.tokens_burnt when the receipt commits (e.g., gate on result.result.is_ok()), and add a regression test for a multi-action receipt where a gas-key deletion is followed by a failing action.
| tx_burnt_amount = safe_add_balance(tx_burnt_amount, result.tokens_burnt)?; | |
| if result.result.is_ok() { | |
| tx_burnt_amount = safe_add_balance(tx_burnt_amount, result.tokens_burnt)?; | |
| } |
There was a problem hiding this comment.
@pugachAG This seems like legit issue, I made this change and added a test.
Please let me know what you think.
There was a problem hiding this comment.
legit issue indeed, the test LGTM, thanks!
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
| self.tokens_burnt = self | ||
| .tokens_burnt | ||
| .checked_add(next_result.tokens_burnt) | ||
| .ok_or(IntegerOverflowError)?; |
There was a problem hiding this comment.
@pugachAG I think this is the correct fix for the issue. I think the result should have tokens_burnt = 0 if it is Err, so fixing it in merge makes more sense to me.
We can add the prior check as a defensive check too. Please let me know what you think.
There was a problem hiding this comment.
I don't see fundamentally why failed action cannot burn any tokens in the future use cases, so I'd rather make sure that tokens_burnt is properly set when handling the action. Not a strong opinion though, up to you.
| self.tokens_burnt = self | ||
| .tokens_burnt | ||
| .checked_add(next_result.tokens_burnt) | ||
| .ok_or(IntegerOverflowError)?; |
There was a problem hiding this comment.
I don't see fundamentally why failed action cannot burn any tokens in the future use cases, so I'd rather make sure that tokens_burnt is properly set when handling the action. Not a strong opinion though, up to you.
| .unwrap(); | ||
| tx_burnt_amount = safe_add_balance(tx_burnt_amount, gas_refund_result.price_surplus)?; | ||
| tx_burnt_amount = safe_add_balance(tx_burnt_amount, gas_refund_result.refund_penalty)?; | ||
| tx_burnt_amount = safe_add_balance(tx_burnt_amount, result.tokens_burnt)?; |
There was a problem hiding this comment.
legit issue indeed, the test LGTM, thanks!
Since NEAR has an accounting of total supply it is necessary to track tokens burnt in gas key deletions. --------- Co-authored-by: Copilot <[email protected]>
# Gas Keys ([NEP-611](near/NEPs#611)) Gas keys are a new type of access key that carries a pre-funded NEAR balance and multiple independent nonce sequences. When a transaction is signed with a gas key, gas costs are deducted from the gas key's balance rather than the signer's account balance (deposits like NEAR transfers still come from the account). ## Why do we need gas keys? - Enabling SPICE without a DoS vector. The SPICE project (Separation of Execution from Consensus) decouples transaction execution from block production: transactions are included in blocks first, and executed asynchronously later. This means chunk producers can no longer verify account balances at inclusion time. An attacker could drain an account's balance in one transaction, then flood the chain with many transactions signed by that account. All would be accepted (since execution hasn't caught up), consuming block space for free when they inevitably fail. Gas keys close this gap by requiring gas to be pre-committed in the key itself, so chunk producers can verify gas availability without access to the latest state. - Parallel transaction submission. Today, programmatic users who submit many transactions in parallel must manage multiple access keys (each with its own nonce sequence). A gas key supports up to 1,024 independent nonce slots via a single key, removing that complexity. # Context - NEP (if exists): near/NEPs#611 - Implementation: - near#14868 - near#14869 - near#14883 - near#14891 - near#14892 - near#14911 - near#14924 - near#14925 - near#14930 - near#14933 - near#14969 - near#14985 - near#15033 - near#15034 - near#15067 - near#15068 - near#15100 - near#15101 - near#15186 # Testing and QA - unit tests - test-loop: https://github.com/near/nearcore/blob/master/test-loop-tests/src/tests/gas_keys.rs - forknet (automated): near#15207 - forknet (manual testing, planned) # Checklist - [x] Link to nightly nayduck run: https://nayduck.nearone.org/#/run/4756 - [x] Update CHANGELOG.md to include this protocol feature in the `Unreleased` section.
Since NEAR has an accounting of total supply it is necessary to track tokens burnt in gas key deletions.