Skip to content

feat(primitives): add ExecutionOutcome V1 with persisted compute_usage#15816

Closed
pugachAG wants to merge 1 commit into
refactor/execution-outcome-versioned-enumfrom
feat/execution-outcome-v1
Closed

feat(primitives): add ExecutionOutcome V1 with persisted compute_usage#15816
pugachAG wants to merge 1 commit into
refactor/execution-outcome-versioned-enumfrom
feat/execution-outcome-v1

Conversation

@pugachAG

@pugachAG pugachAG commented May 28, 2026

Copy link
Copy Markdown
Contributor
  • Adds ExecutionOutcomeV1, a new variant of the versioned enum whose fields mirror V0 except compute_usage is no longer #[borsh(skip)] - it now survives borsh round-trips
  • Introduces borsh serialization hack similar to how Transaction is handled: we rely on logs.len() being at most 100, so any value above that is treated as an enum tag.
  • Part of Feature: add GlobalContractIdentifier to ExecutionOutcomeView #14622: executed_contract field will be added as part of V1 in the later PR.
  • Stacked on refactor: turn ExecutionOutcome struct into a versioned enum #15814 (the preparatory pure refactor)
  • V0 wire format unchanged: no tag prefix, byte 0 is the low byte of logs.len() and stays in [0, V0_MAX_FIRST_BYTE=100] thanks to the max_number_logs runtime limit
  • V1 wire format: prefixed with V1_TAG = 101 (strictly > V0_MAX_FIRST_BYTE), then the V1 borsh body — the deserializer disambiguates on byte 0
  • V0 serializer rejects logs.len() > V0_MAX_FIRST_BYTE so it never silently produces bytes a future reader would misclassify as V1+
  • No production code emits V1 yet - gating the cutover behind a ProtocolFeature is a follow-up, since changing the on-the-wire / on-disk outcome format requires coordinated activation across the validator set

Introduces ExecutionOutcomeV1, a new variant of the versioned enum whose
fields mirror V0 except compute_usage is no longer #[borsh(skip)] — it now
survives borsh round-trips. This is preparatory for #8859, which wants to
treat compute_usage on the same footing as gas_burnt.

Borsh wire format:
- V0 unchanged: no tag prefix, byte 0 is the low byte of logs.len() and
  guaranteed to fall in [0, V0_MAX_FIRST_BYTE=100] by the max_number_logs
  runtime limit.
- V1 prefixed with V1_TAG = 101 (strictly > V0_MAX_FIRST_BYTE), then the
  V1 borsh body.

The deserializer disambiguates on byte 0: <= 100 is V0 (put the byte back
and decode), == 101 is V1, anything else errors out as an invalid version
tag. The V0 serializer rejects logs.len() > V0_MAX_FIRST_BYTE so that we
never silently emit bytes that a future reader would misclassify as V1+.

No production code emits V1 yet — gating the emission switchover behind a
ProtocolFeature is a follow-up, since changing the on-the-wire / on-disk
outcome format requires coordinated activation across the validator set.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@pugachAG pugachAG requested a review from wacban May 28, 2026 14:49
@pugachAG pugachAG marked this pull request as ready for review May 28, 2026 14:51
@pugachAG pugachAG requested a review from a team as a code owner May 28, 2026 14:51
@codecov

codecov Bot commented May 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.84496% with 26 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.84%. Comparing base (e40c49c) to head (789714c).

Files with missing lines Patch % Lines
core/primitives/src/transaction.rs 85.83% 13 Missing and 4 partials ⚠️
core/primitives/src/views.rs 0.00% 9 Missing ⚠️
Additional details and impacted files
@@                              Coverage Diff                              @@
##           refactor/execution-outcome-versioned-enum   #15816      +/-   ##
=============================================================================
- Coverage                                      69.84%   69.84%   -0.01%     
=============================================================================
  Files                                            942      942              
  Lines                                         200387   200512     +125     
  Branches                                      200387   200512     +125     
=============================================================================
+ Hits                                          139961   140045      +84     
- Misses                                         55722    55762      +40     
- Partials                                        4704     4705       +1     
Flag Coverage Δ
pytests-nightly 1.13% <0.00%> (-0.01%) ⬇️
unittests 69.23% <79.84%> (+<0.01%) ⬆️
unittests-nightly 69.52% <79.84%> (+<0.01%) ⬆️

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.

@pugachAG

Copy link
Copy Markdown
Contributor Author

closing in favor of #15822

@pugachAG pugachAG closed this Jun 10, 2026
pull Bot pushed a commit to See887777/nearcore that referenced this pull request Jun 10, 2026
…st (near#15822)

- Adds `ExecutionMetadata::V4` carrying a per-action
`Vec<AccountContract>` (one entry per action in the receipt; the
executed contract for `FunctionCall` actions, `AccountContract::None`
for everything else, ordered to match `receipt.actions`)
- Gated behind `ProtocolFeature::ExecutionMetadataV4`; pre-feature
outcomes still emit V3 with the same bytes as before
- Motivated by the need to surface the contract source for receipts
where the receiver account and the executed contract diverge — e.g.
global contracts and `UseGlobalContract` flows - so indexers / light
clients can correlate each action with its actual contract
- The first exploration of this added a `ExecutionOutcomeV1` variant on
the outcome struct itself (near#15814 introduced the versioned-enum
scaffolding, near#15816 added V1 with the byte-discriminant scheme). After
looking at it, `ExecutionMetadata` is already a borsh-native
discriminated enum, so adding `V4` there is dramatically simpler — no
custom serde tricks, no boundary constants, no struct migration across
~50 call sites.
- Splits the result types: per-action `ActionResult` carries a single
`executed_contract: AccountContract` (defaults to
`AccountContract::None`, set by `apply_action`'s `FunctionCall` arm);
receipt-level `ActionReceiptResult` owns `executed_contracts:
Vec<AccountContract>` and its `merge` appends one contract per action,
so the per-action invariant lives in one place
- `ActionReceiptResult::set_error` centralizes the failure path (record
the error, drop queued receipts / proposals / burnt balances); called
from both `merge`'s `Err` arm and the `LackBalanceForStorageStaking`
post-action storage check (which previously faked it by merging a
synthetic `ActionResult` — under the new merge that would push a stray
`AccountContract::None` onto `executed_contracts`)
- `ExecutionMetadataView` gains `contracts:
Option<Vec<AccountContract>>`, `serde(skip_serializing_if =
"Option::is_none")` so V1..V3 RPC bytes are byte-identical to before
- Closes near#14622

---------

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
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.

1 participant