Skip to content

feat(gas-keys): add verify_and_charge_gas_key_tx_ephemeral#14924

Merged
darioush merged 6 commits into
masterfrom
darioush/gaskey-tx1
Jan 30, 2026
Merged

feat(gas-keys): add verify_and_charge_gas_key_tx_ephemeral#14924
darioush merged 6 commits into
masterfrom
darioush/gaskey-tx1

Conversation

@darioush

@darioush darioush commented Jan 23, 2026

Copy link
Copy Markdown
Contributor

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 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.

@darioush darioush force-pushed the darioush/gaskey-tx0 branch from e9b3b93 to 13dbfca Compare January 23, 2026 19:03
@darioush darioush force-pushed the darioush/gaskey-tx1 branch from 117218b to 5e9d56e Compare January 23, 2026 19:04
@codecov

codecov Bot commented Jan 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 30.29197% with 191 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.69%. Comparing base (b13d4f7) to head (fb17b49).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
runtime/runtime/src/lib.rs 18.29% 62 Missing and 5 partials ⚠️
runtime/runtime/src/verifier.rs 17.10% 62 Missing and 1 partial ⚠️
chain/chain/src/runtime/mod.rs 48.24% 50 Missing and 9 partials ⚠️
core/primitives/src/errors.rs 0.00% 2 Missing ⚠️
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     
Flag Coverage Δ
pytests-nightly 1.30% <0.00%> (-0.01%) ⬇️
unittests 68.41% <30.29%> (-0.06%) ⬇️
unittests-nightly 68.21% <30.29%> (-0.07%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@darioush darioush requested a review from pugachAG January 23, 2026 22:21
@darioush darioush marked this pull request as ready for review January 23, 2026 22:21
@darioush darioush requested a review from a team as a code owner January 23, 2026 22:21
@darioush darioush requested a review from attila0x2A January 23, 2026 22:22
Comment thread chain/chain/src/runtime/mod.rs Outdated
@darioush darioush force-pushed the darioush/gaskey-tx1 branch 2 times, most recently from 4f98022 to 8ffc310 Compare January 26, 2026 14:21
Some(next_block_height),
)
};
// Update cached gas key nonce for subsequent transactions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

@darioush darioush Jan 30, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread runtime/runtime/src/verifier.rs Outdated
transaction_cost: &TransactionCost,
block_height: Option<BlockHeight>,
) -> Result<VerificationResult, InvalidTxError> {
// Regular access key transactions must not have a nonce_index

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a part of the api contract, so debug_assert!s are probably more appropriate than error here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. Actually I will use assert! / panic! since it's better to crash than try to process transaction incorrectly.

github-merge-queue Bot pushed a commit that referenced this pull request Jan 30, 2026
…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 #14924 to
help reduce duplication
Base automatically changed from darioush/gaskey-tx0 to master January 30, 2026 01:21
Copilot AI review requested due to automatic review settings January 30, 2026 15:45
@darioush darioush force-pushed the darioush/gaskey-tx1 branch from 8ffc310 to d6824ad Compare January 30, 2026 15:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_ephemeral and extended VerificationResult to carry a gas-key nonce update.
  • Updated transaction processing to prefetch gas-key nonces and persist nonce updates when executing transactions.
  • Added InvalidTxError::InvalidNonceIndex and 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.

Comment on lines +929 to +939
// 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 {

Copilot AI Jan 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As is, it's consistent with account and access_key.

Comment thread runtime/runtime/src/verifier.rs
@darioush darioush requested a review from frol as a code owner January 30, 2026 16:27
@darioush darioush added this pull request to the merge queue Jan 30, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jan 30, 2026
@darioush darioush added this pull request to the merge queue Jan 30, 2026
Merged via the queue into master with commit c6e0568 Jan 30, 2026
29 of 31 checks passed
@darioush darioush deleted the darioush/gaskey-tx1 branch January 30, 2026 21:54
darioush added a commit to darioush/nearcore that referenced this pull request Feb 4, 2026
…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
darioush added a commit to darioush/nearcore that referenced this pull request Feb 4, 2026
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.
pull Bot pushed a commit to See887777/nearcore that referenced this pull request Jun 3, 2026
# 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants