Skip to content

feat(gas-keys): charge gas on failed deposit for gas key txs#15034

Merged
darioush merged 3 commits into
masterfrom
darioush/gaskeys-failedtx-3
Feb 10, 2026
Merged

feat(gas-keys): charge gas on failed deposit for gas key txs#15034
darioush merged 3 commits into
masterfrom
darioush/gaskeys-failedtx-3

Conversation

@darioush

@darioush darioush commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

This is 3 out of 3 PRs for charging gas keys for failed transactions. See near/NEPs#611 for details. For reviewer's reference, all changes together: #15031

  • When a gas key transaction fails because the account can't cover the deposit, gas is still charged from the gas key balance. This prevents SPICE spam where an attacker drains their account between submission and execution to get free transactions.
  • Adds TxVerdict::DepositFailed variant: the verifier returns this when gas key balance is sufficient but the account can't cover the deposit
  • Adds NotEnoughBalanceForDeposit error variant
  • test_gas_key_tx_deposit_insufficient_charges_gas (apply-level), test_gas_key_tx_deposit_failed_for_{account_balance, storage_stake} (verifier-level)
  • Will fix protocol schema and openapi.json after review.

@codecov

codecov Bot commented Feb 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.26291% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.82%. Comparing base (9a5ad27) to head (8e66605).

Files with missing lines Patch % Lines
core/primitives/src/errors.rs 0.00% 14 Missing ⚠️
runtime/runtime/src/verifier.rs 95.55% 5 Missing and 1 partial ⚠️
chain/chain/src/runtime/mod.rs 0.00% 2 Missing and 1 partial ⚠️
runtime/runtime/src/lib.rs 96.00% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@                       Coverage Diff                       @@
##           darioush/gaskeys-failedtx-2   #15034      +/-   ##
===============================================================
+ Coverage                        68.55%   68.82%   +0.27%     
===============================================================
  Files                              920      920              
  Lines                           201632   201832     +200     
  Branches                        201632   201832     +200     
===============================================================
+ Hits                            138227   138910     +683     
+ Misses                           57431    56954     -477     
+ Partials                          5974     5968       -6     
Flag Coverage Δ
pytests-nightly 1.30% <0.00%> (-0.01%) ⬇️
unittests 68.43% <88.26%> (+<0.01%) ⬆️
unittests-nightly 68.41% <88.26%> (?)

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.

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

This PR extends gas-key transaction handling so that when execution fails due to insufficient account balance for the deposit portion, gas is still charged against the gas key (mitigating free-tx spam via balance-draining between submission and execution).

Changes:

  • Introduces TxVerdict::DepositFailed to represent “deposit/account-level failure but still charge gas key”.
  • Adds InvalidTxError::NotEnoughBalanceForDeposit with DepositCostFailureReason to classify deposit failure reasons.
  • Updates runtime processing and tests to ensure gas-burn + gas-key nonce/balance updates occur on deposit failure.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
test-loop-tests/src/tests/gas_keys.rs Adds a TODO for a future test-loop/integration test covering DepositFailed.
runtime/runtime/src/verifier.rs Returns TxVerdict::DepositFailed for gas-key txs that fail deposit checks, while still producing a VerificationResult for gas-only application. Adds unit test for the new verdict.
runtime/runtime/src/tests/apply.rs Adds an apply-level test asserting gas is burnt + gas key balance/nonce update when deposit is insufficient.
runtime/runtime/src/lib.rs Applies gas-only state changes and records an execution outcome (with gas burnt) when DepositFailed is returned.
core/primitives/src/transaction.rs Adds ExecutionOutcomeWithId::failed_with_gas_burnt helper to construct failure outcomes with explicit gas/tokens burnt.
core/primitives/src/errors.rs Adds DepositCostFailureReason and InvalidTxError::NotEnoughBalanceForDeposit plus display formatting.
chain/chain/src/runtime/mod.rs Handles TxVerdict::DepositFailed in validation paths (treating it as an error for admission/selection).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +307 to +311
/// Gas key transaction failed because the account could not cover the deposit cost.
/// Gas is still charged from the gas key in this case.
NotEnoughBalanceForDeposit {
signer_id: AccountId,
balance: Balance,

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

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

InvalidTxError::NotEnoughBalanceForDeposit’s cost field is used with different semantics depending on reason (total deposit cost vs “required more for storage”). That makes the error payload ambiguous for API consumers; consider splitting into distinct fields (e.g. deposit_cost and storage_shortfall) or renaming cost/documenting it precisely per reason.

Suggested change
/// Gas key transaction failed because the account could not cover the deposit cost.
/// Gas is still charged from the gas key in this case.
NotEnoughBalanceForDeposit {
signer_id: AccountId,
balance: Balance,
/// Gas key transaction failed because the account could not cover the deposit cost.
///
/// `cost` is used in conjunction with `reason` and its exact meaning depends on
/// the value of `reason`. In particular, depending on the failure mode it may
/// represent either:
/// * the total deposit amount required by the transaction, or
/// * the additional amount required to cover storage (a shortfall over `balance`).
///
/// Gas is still charged from the gas key in this case.
NotEnoughBalanceForDeposit {
signer_id: AccountId,
balance: Balance,
/// Deposit-related cost associated with this failure. The precise semantics
/// of this field are determined by `reason`: for some reasons it encodes
/// the total deposit cost of the transaction, while for others it encodes
/// only the extra amount required to cover storage (the shortfall).

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.

This seems somewhat minor to me, leave it up to reviewer.

Comment thread runtime/runtime/src/verifier.rs Outdated
env.shutdown_and_drain_remaining_events(Duration::seconds(5));
}

// TODO(gas-keys): Add test-loop test for DepositFailed (deposit-insufficient gas key tx).

Copilot AI Feb 9, 2026

Copy link

Choose a reason for hiding this comment

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

Leaving this as a TODO means the new DepositFailed behavior isn’t covered by test-loop/integration tests. Please either add the test here (mirroring the unit/apply tests) or link this TODO to a tracking issue so it doesn’t get lost.

Copilot uses AI. Check for mistakes.

@darioush darioush Feb 9, 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.

This test loop is not super trivial to write because it either requires chunk producer to skip some checks and include failed transactions or needs spice for execution delay.

Comment thread runtime/runtime/src/verifier.rs
Comment thread core/primitives/src/errors.rs
@darioush darioush force-pushed the darioush/gaskeys-failedtx-3 branch from 5f1f71d to bbcf978 Compare February 9, 2026 18:41
@darioush darioush requested a review from pugachAG February 9, 2026 18:50
@darioush darioush marked this pull request as ready for review February 9, 2026 18:50
@darioush darioush requested a review from a team as a code owner February 9, 2026 18:50
@darioush darioush force-pushed the darioush/gaskeys-failedtx-3 branch from bbcf978 to 8e66605 Compare February 10, 2026 20:33
Base automatically changed from darioush/gaskeys-failedtx-2 to master February 10, 2026 21:27
@darioush darioush force-pushed the darioush/gaskeys-failedtx-3 branch from 8e66605 to 33e2b3c Compare February 10, 2026 22:29
@darioush darioush enabled auto-merge February 10, 2026 22:30
@darioush darioush requested a review from frol as a code owner February 10, 2026 22:36
@darioush darioush added this pull request to the merge queue Feb 10, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Feb 10, 2026
@darioush darioush added this pull request to the merge queue Feb 10, 2026
Merged via the queue into master with commit 25b9eb6 Feb 10, 2026
27 checks passed
@darioush darioush deleted the darioush/gaskeys-failedtx-3 branch February 10, 2026 23:22
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