feat(gas-keys): add verify_and_charge_gas_key_tx_ephemeral#14924
Conversation
e9b3b93 to
13dbfca
Compare
117218b to
5e9d56e
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #14924 +/- ##
==========================================
- Coverage 68.76% 68.69% -0.07%
==========================================
Files 918 918
Lines 200303 200526 +223
Branches 200303 200526 +223
==========================================
+ Hits 137737 137758 +21
- Misses 56630 56821 +191
- Partials 5936 5947 +11
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:
|
4f98022 to
8ffc310
Compare
| Some(next_block_height), | ||
| ) | ||
| }; | ||
| // Update cached gas key nonce for subsequent transactions |
There was a problem hiding this comment.
as far as I understand this is not needed - if verify_result is OK then the loop stops and we no longer need cached data:
match verify_result {
Ok(cost) => {
...
break;
There was a problem hiding this comment.
It is used for updating the nonce in:
if let Some((nonce_index, nonce)) = cache.gas_key_nonce {
set_gas_key_nonce(
&mut state_update.trie_update,
cache.account_id.clone(),
cache.public_key.clone(),
nonce_index,
nonce,
)
};
It is needed for the same reason that set_access_key was added here:
https://github.com/near/nearcore/pull/14747/changes#diff-1b2a4c8b931f5c37e5d2ce805c09e542058b5063f9548f8ed623514dbbe99b71R914
TBH this code could overall be a bit better but for now I prefer to maintain the existing pattern and not refactor this in case it adds some subtle bug.
| transaction_cost: &TransactionCost, | ||
| block_height: Option<BlockHeight>, | ||
| ) -> Result<VerificationResult, InvalidTxError> { | ||
| // Regular access key transactions must not have a nonce_index |
There was a problem hiding this comment.
this seems like a part of the api contract, so debug_assert!s are probably more appropriate than error here
There was a problem hiding this comment.
I agree. Actually I will use assert! / panic! since it's better to crash than try to process transaction incorrectly.
8ffc310 to
d6824ad
Compare
There was a problem hiding this comment.
Pull request overview
Adds runtime support for “gas key” transactions by introducing a separate verification/charging path that supports multiple nonces per key, including nonce prefetching and persistence, plus a new transaction error for invalid nonce index usage.
Changes:
- Added
verify_and_charge_gas_key_tx_ephemeraland extendedVerificationResultto carry a gas-key nonce update. - Updated transaction processing to prefetch gas-key nonces and persist nonce updates when executing transactions.
- Added
InvalidTxError::InvalidNonceIndexand integrated it into verification/dispatch logic.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| runtime/runtime/src/verifier.rs | Adds gas-key verification/charging path and enforces nonce_index rules for regular vs gas-key transactions. |
| runtime/runtime/src/lib.rs | Dispatches tx verification based on nonce_index, prefetches gas-key nonces, and persists gas-key nonce updates. |
| core/primitives/src/errors.rs | Introduces InvalidTxError::InvalidNonceIndex and its display formatting. |
| chain/chain/src/runtime/mod.rs | Updates runtime adapter paths to load/update gas-key nonces during tx validation and preparation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // For gas key transactions, also load the gas key nonce | ||
| let gas_key_nonce = if let Some(idx) = nonce_index { | ||
| let nonce = get_gas_key_nonce( | ||
| &state_update, | ||
| signer_id, | ||
| validated_tx.public_key(), | ||
| idx, | ||
| ); | ||
| let nonce = nonce.transpose().and_then(|v| v.ok()); | ||
| Some((idx, nonce.ok_or(Error::InvalidTransactions)?)) | ||
| } else { |
There was a problem hiding this comment.
In prepare_transactions_extra, when nonce_index is present you load the gas-key nonce from storage and then call nonce.ok_or(Error::InvalidTransactions)?. If the nonce entry is missing (e.g. tx uses nonce_index with a non-gas key, or an out-of-range index, or state corruption), this aborts the entire transaction-preparation run instead of just rejecting that single transaction. This is inconsistent with the runtime process_transactions path which treats missing gas-key nonce as InvalidTxError::InvalidNonceIndex and continues.
Handle None from get_gas_key_nonce as an invalid transaction (increment rejected counter + continue / produce an InvalidTxError::InvalidNonceIndex for tracing), rather than returning Error::InvalidTransactions from the whole function.
There was a problem hiding this comment.
As is, it's consistent with account and access_key.
…ear#14923) * Refactored and extracted the `FunctionCallPermission` validation logic into a new function `verify_function_call_permission` * Extracted nonce validation into a new function `verify_nonce` This refactor is used for near#14924 to help reduce duplication
Add support for gas key transactions, which require handling multiple nonces per key. I completely leave out the fund accounting and balance accounting changes from this PR and we can add them later to keep scope small. I added a TODO for this, and for now account's balance continues to pay all `tx_cost`. (This is a "safe" approach, gas keys are just like ordinary access keys with multiple nonces with this change). * Updated transaction verification logic to distinguish between regular and gas key transactions, calling `verify_and_charge_gas_key_tx_ephemeral` for the latter and updating nonce values accordingly. * Modified the runtime to prefetch gas key nonces in parallel with accounts and access keys, using a new `gas_key_nonces`. The cache in `prepare_transactions_extra` is also modified to accommodate gas key nonces. * Added a new `InvalidTxError::InvalidNonceIndex` variant to handle cases where a transaction specifies an invalid nonce index. I took an approach of having a separate code path for gas keys based on feedback from last attempt at this and further changes that will be introduced for balance handling. In case we want a _unified_ code path instead, an unpolished draft is available in near@8bf4f28. Tests in separate PR to manage diff size: near#14925 The separate code path introduces duplication, but we can mitigate that by refactoring common parts which is done here: near#14923 (This PR is based on that branch) As usual I defer openapi & protocol schema changes to last step before merging.
# 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.
Add support for gas key transactions, which require handling multiple nonces per key.
I completely leave out the fund accounting and balance accounting changes from this PR and we can add them later to keep scope small. I added a TODO for this, and for now account's balance continues to pay all
tx_cost.(This is a "safe" approach, gas keys are just like ordinary access keys with multiple nonces with this change).
verify_and_charge_gas_key_tx_ephemeralfor the latter and updating nonce values accordingly.gas_key_nonces. The cache inprepare_transactions_extrais also modified to accommodate gas key nonces.InvalidTxError::InvalidNonceIndexvariant to handle cases where a transaction specifies an invalid nonce index.I took an approach of having a separate code path for gas keys based on feedback from last attempt at this and further changes that will be introduced for balance handling. In case we want a unified code path instead, an unpolished draft is available in 8bf4f28.
Tests in separate PR to manage diff size: #14925
The separate code path introduces duplication, but we can mitigate that by refactoring common parts which is done here: #14923 (This PR is based on that branch)
As usual I defer openapi & protocol schema changes to last step before merging.