Skip to content

[cli] Show rich abort info in local simulation vm_status#19490

Merged
vgao1996 merged 2 commits intomainfrom
vgao/cli-rich-vm-status
Apr 28, 2026
Merged

[cli] Show rich abort info in local simulation vm_status#19490
vgao1996 merged 2 commits intomainfrom
vgao/cli-rich-vm-status

Conversation

@vgao1996
Copy link
Copy Markdown
Contributor

@vgao1996 vgao1996 commented Apr 17, 2026

Summary

Local simulation paths (aptos move publish --session, aptos move replay, and the debugger-backed --local / --profile-gas / --benchmark flows) previously stringified the top-level VMStatus via its Display impl, which drops abort location and AbortInfo. Users saw:

"vm_status": "status ABORTED of type Execution with sub status 393221"

No module, no reason — useless for debugging, especially for publish-time checks like Decibel's backward-compatibility check.

Root cause

The VM already enriches ExecutionStatus::MoveAbort.info with reason_name + description from the aborting module's #[error]-annotated constants inside AptosVM::finish_user_transaction (via RuntimeModuleMetadataV1::extract_abort_info). That enrichment lands on TransactionOutput.status(), but the CLI was formatting the un-enriched top-level VMStatus instead — effectively the same string that VMStatus::Display produces, with location and message omitted.

Fix

  • Add format_txn_status(&TransactionStatus, &VMStatus) -> String in aptos-cli-common that prefers the enriched ExecutionStatus and falls back to VMStatus::to_string() only for Retry.
  • Wire all four existing call sites:
    • crates/aptos/src/aptos_context.rs (session + debugger paths)
    • aptos-move/cli/src/transactions.rs (aptos-move-cli debugger path)
    • aptos-move/cli/src/commands.rs (aptos move replay)

Schema unchanged — vm_status remains Option<String>.

After

"vm_status": "Move abort in 0x1::code: EPACKAGE_DEP_MISSING(0x60005): ..."

Follow-up

Port explain_vm_status (currently only in api/types/src/convert.rs) into a shared crate so ExecutionFailure paths can resolve function names instead of printing function #N. Out of scope for this PR.

Test plan

  • cargo test -p aptos-cli-common — 9 new unit tests covering every ExecutionStatus / TransactionStatus arm
  • cargo check -p aptos -p aptos-move-cli
  • Manual: reproduce an EPACKAGE_DEP_MISSING via session publish, confirm new output format

🤖 Generated with Claude Code


Note

Low Risk
Low risk: changes only CLI formatting of TransactionSummary.vm_status for local/simulation/replay paths and adds unit tests; no transaction execution logic or schema changes.

Overview
Improves local simulation/replay output by formatting vm_status from TransactionStatus (enriched ExecutionStatus) instead of the lossy VMStatus display, so Move aborts include module/script location plus optional reason_name and description.

Adds shared format_txn_status in aptos-cli-common (with unit tests) and wires it into existing local simulation call sites in aptos-move-cli (simulate_locally, move replay) and the full CLI (aptos_context session/debugger flows).

Reviewed by Cursor Bugbot for commit c398582. Bugbot is set up for automated code reviews on this repo. Configure here.

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

👀

Differential Security Review — PR #19490: [cli] Show rich abort info in local simulation vm_status

Scope: vgao/cli-rich-vm-statusmain | 4 files, 213 additions / 16 deletions
Modules touched: crates/aptos-cli-common, crates/aptos, aptos-move/cli

Executive Summary

Severity Count
CRITICAL 0
HIGH 0
MEDIUM 0
LOW 3

Overall risk: Low. This is a CLI-only formatting change isolated to local simulation output. No consensus, VM execution, gas metering, or authentication paths are touched. The new format_txn_status helper is well-tested (9 unit tests covering every ExecutionStatus arm).

Recommendation: APPROVE WITH NOTES


What Changed

File Lines +/- Risk
crates/aptos-cli-common/src/types.rs +199 / -2 Low — new formatting helper + unit tests
crates/aptos/src/aptos_context.rs +4 / -4 Low — call-site wiring only
aptos-move/cli/src/transactions.rs +4 / -4 Low — call-site wiring only
aptos-move/cli/src/commands.rs +6 / -6 Low — call-site wiring only

Findings

[LOW] F-1: Script MoveAbort format diverges from explain_vm_status in the REST API layer

File: crates/aptos-cli-common/src/types.rs:346–370
Blast radius: CLI local simulation paths only; no on-chain or consensus impact.
Test coverage: move_abort_in_script test covers this arm, but does not compare against API output.

The new loc() helper returns "script" for AbortLocation::Script, causing the MoveAbort arms to emit:

"Move abort in script: 0x2a"

explain_vm_status in api/types/src/convert.rs:1415 instead always emits:

"Move abort: code 0x2a"

and unconditionally ignores AbortInfo for script aborts.

The two strings share no common prefix or shape. Any tooling or test infrastructure that parses vm_status strings from either path and compares them will see a mismatch. The behavior introduced by this PR (showing location + optional AbortInfo for script aborts) is actually more informative than the API version, but the inconsistency is observable.


[LOW] F-2: MiscellaneousError(None) message differs from explain_vm_status

File: crates/aptos-cli-common/src/types.rs:382–385
Blast radius: CLI only; no runtime impact.

Context None arm output
format_txn_status "Miscellaneous error"
explain_vm_status (api/types/src/convert.rs:1450) "Execution failed with miscellaneous error and no status code"

The CLI emits a shorter, different string for the same condition. Low-impact since MiscellaneousError is rare in practice, but adds to the surface area of format divergence between local simulation and API-served vm_status.


[LOW] F-3: AbortInfo.description is included unconditionally — empty value produces a trailing colon

File: crates/aptos-cli-common/src/types.rs:362–370

The two Some(i) branches always append i.description as the last segment. AbortInfo is populated from module metadata where both reason_name and description can be empty strings (e.g., in types/src/transaction/mod.rs:1672–1674, reason_name is initialized to "" before enrichment). If description is empty, the output becomes:

"Move abort in 0x1::code: ENAME(0x1): "   ← trailing colon + space
"Move abort in 0x1::code: 0x1: "          ← same for empty-reason arm

No type safety or correctness failure — just visually awkward output for partially-enriched abort metadata. The unit tests use non-empty descriptions and do not exercise this edge.


Test Coverage

Changed Function Coverage Notes
format_txn_status Well-tested — 9 unit tests across all arms Empty description case not covered
Call sites (4×) Not independently tested Covered transitively by unit tests of the helper

Blast Radius

format_txn_status is pub in aptos-cli-common (re-exported via pub use types::*). It is currently called in exactly 4 places (all local simulation paths). The function has no callers in consensus, VM execution, storage, or mempool paths. Caller count is LOW (4 non-test callers, all CLI-tier).


Historical Context

No removed security checks. The only change to existing logic is replacing vm_status.to_string() (a lossy Display call) with format_txn_status(output.status(), &vm_status) (a richer match over ExecutionStatus). Git history for the modified lines contains no security-fix commits.

Open in Web View Automation 

Sent by Cursor Automation: Security Review Bot

AbortLocation::Module(m) => {
format!("{}::{}", m.address().to_hex_literal(), m.name())
},
AbortLocation::Script => "script".to_string(),
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.

[LOW F-1] Script abort format diverges from explain_vm_status. This arm returns "script", causing all three MoveAbort branches (including those with AbortInfo) to produce "Move abort in script: …". explain_vm_status (api/types/src/convert.rs:1415) always emits "Move abort: code {:#x}" for script aborts and never surfaces AbortInfo there. The CLI output is richer but inconsistent in prefix/shape with the API-layer string.

),
TransactionStatus::Keep(ExecutionStatus::MiscellaneousError(code)) => match code {
Some(c) => format!("Miscellaneous error: {:?}", c),
None => "Miscellaneous error".to_string(),
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.

[LOW F-2] MiscellaneousError(None) message differs from explain_vm_status, which emits "Execution failed with miscellaneous error and no status code" for the same case (api/types/src/convert.rs:1450). Low practical impact since this variant is rare, but adds to the observable divergence between local simulation and REST API vm_status strings.

Comment thread crates/aptos-cli-common/src/types.rs Outdated
code,
i.description
),
Some(i) => format!("Move abort in {}: {:#x}: {}", loc(location), code, i.description),
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.

[LOW F-3] i.description is appended unconditionally. If AbortInfo is present but description is an empty string (possible before metadata enrichment populates it — see types/src/transaction/mod.rs:1672), this produces "Move abort in …: 0x…: " with a trailing : . The unit tests don't exercise description = "". Consider guarding: if i.description.is_empty() { … } else { format!("… {}", i.description) } — or add a test asserting the current trailing-colon behavior is intentional.

@vgao1996 vgao1996 requested a review from a team April 28, 2026 18:45
/// `TransactionSummary`. Prefers the VM-enriched `ExecutionStatus` (which
/// carries abort location and `AbortInfo`) over the top-level `VMStatus`,
/// whose `Display` impl drops both. `fallback` is used only for `Retry`.
pub fn format_txn_status(status: &TransactionStatus, fallback: &VMStatus) -> String {
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.

I wonder if there is a way to combine this in explain_vm_status in api/types/src/convert.rs, since they do more or less the same thing.

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.

Left a TODO. There are currently some minor differences -- probably not worth addressing at the moment.

info,
}) => match info {
Some(i) if !i.reason_name.is_empty() => format!(
"Move abort in {}: {}({:#x}): {}",
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.

nit: This will have a dangling colon if description is empty. But that's the same issue in explain_vm_status, so probably not worth fixing.

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 now prints "(no description)" if it's empty.

Local simulation paths (`aptos move publish --session`, `aptos move
replay`, and the debugger-backed `--local` / `--profile-gas` /
`--benchmark` flows) were stringifying the top-level `VMStatus` via its
`Display` impl, which drops abort location and `AbortInfo`. Users saw
opaque output like `status ABORTED of type Execution with sub status
393221` with no module or reason.

The VM already enriches `ExecutionStatus::MoveAbort.info` with
`reason_name` + `description` from the aborting module's
`#[error]`-annotated constants (see `AptosVM::finish_user_transaction`).
That enrichment lands on `TransactionOutput.status()` but the CLI was
formatting the un-enriched top-level `VMStatus` instead.

Add `format_txn_status` in `aptos-cli-common` that prefers the enriched
`ExecutionStatus` and falls back to `VMStatus::to_string()` only for
`Retry`. Wire all four existing call sites. Schema unchanged — `vm_status`
remains `Option<String>`.

After: `Move abort in 0x1::code: EPACKAGE_DEP_MISSING(0x60005): ...`.

Follow-up: port `explain_vm_status` so `ExecutionFailure` paths can
resolve function names instead of printing `function #N`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vgao1996 vgao1996 force-pushed the vgao/cli-rich-vm-status branch from a19ad84 to fc3631d Compare April 28, 2026 21:53
@vgao1996 vgao1996 enabled auto-merge (squash) April 28, 2026 21:53
@vgao1996 vgao1996 disabled auto-merge April 28, 2026 21:56
@vgao1996 vgao1996 force-pushed the vgao/cli-rich-vm-status branch 2 times, most recently from fdaffff to 9e75c1a Compare April 28, 2026 22:03
@vgao1996 vgao1996 enabled auto-merge (squash) April 28, 2026 22:03
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vgao1996 vgao1996 force-pushed the vgao/cli-rich-vm-status branch from 9e75c1a to c398582 Compare April 28, 2026 22:14
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions
Copy link
Copy Markdown
Contributor

✅ Forge suite compat success on ca049383dd80675149ef2d0042668964f9f9107a ==> c398582dc578155e88a5987b0b7add4c2b7e56a7

Compatibility test results for ca049383dd80675149ef2d0042668964f9f9107a ==> c398582dc578155e88a5987b0b7add4c2b7e56a7 (PR)
1. Check liveness of validators at old version: ca049383dd80675149ef2d0042668964f9f9107a
compatibility::simple-validator-upgrade::liveness-check : committed: 14329.47 txn/s, latency: 2412.51 ms, (p50: 2400 ms, p70: 2700, p90: 3300 ms, p99: 4000 ms), latency samples: 471000
2. Upgrading first Validator to new version: c398582dc578155e88a5987b0b7add4c2b7e56a7
compatibility::simple-validator-upgrade::single-validator-upgrade : committed: 6420.06 txn/s, latency: 5305.60 ms, (p50: 5800 ms, p70: 5900, p90: 6000 ms, p99: 6100 ms), latency samples: 222860
3. Upgrading rest of first batch to new version: c398582dc578155e88a5987b0b7add4c2b7e56a7
compatibility::simple-validator-upgrade::half-validator-upgrade : committed: 6451.79 txn/s, latency: 5224.24 ms, (p50: 5700 ms, p70: 5900, p90: 6000 ms, p99: 6100 ms), latency samples: 221720
4. upgrading second batch to new version: c398582dc578155e88a5987b0b7add4c2b7e56a7
compatibility::simple-validator-upgrade::rest-validator-upgrade : committed: 10470.82 txn/s, latency: 3125.50 ms, (p50: 3100 ms, p70: 3300, p90: 3600 ms, p99: 4000 ms), latency samples: 344140
5. check swarm health
Compatibility test for ca049383dd80675149ef2d0042668964f9f9107a ==> c398582dc578155e88a5987b0b7add4c2b7e56a7 passed
Test Ok

@github-actions
Copy link
Copy Markdown
Contributor

✅ Forge suite realistic_env_max_load success on c398582dc578155e88a5987b0b7add4c2b7e56a7

Forge report malformed: Expecting ',' delimiter: line 13 column 6 (char 266)
'{\n  "metrics": [\n    {\n      "test_name": "two traffics test: inner traffic",\n      "metric": "submitted_txn",\n      "value": 5805340.0\n    },\n    {\n      "test_name": "two traffics test: inner traffic",\n      "metric": "expired_txn",\n      "value": 0.0\n    },\n[2026-04-28T22:53:09Z INFO  aptos_forge::report] Test Ok\n    {\n      "test_name": "two traffics test: inner traffic",\n      "metric": "avg_tps",\n      "value": 15541.592079951268\n    },\n    {\n      "test_name": "two traffics test: inner traffic",\n      "metric": "avg_latency",\n      "value": 1134.3418580134842\n    },\n    {\n      "test_name": "two traffics test: inner traffic",\n      "metric": "p50_latency",\n      "value": 1100.0\n    },\n    {\n      "test_name": "two traffics test: inner traffic",\n      "metric": "p90_latency",\n      "value": 1300.0\n    },\n    {\n      "test_name": "two traffics test: inner traffic",\n      "metric": "p99_latency",\n      "value": 1700.0\n    },\n    {\n      "test_name": "two traffics test",\n      "metric": "submitted_txn",\n      "value": 42600.0\n    },\n    {\n      "test_name": "two traffics test",\n      "metric": "expired_txn",\n      "value": 0.0\n    },\n    {\n      "test_name": "two traffics test",\n      "metric": "avg_tps",\n      "value": 100.01496662581391\n    },\n    {\n      "test_name": "two traffics test",\n      "metric": "avg_latency",\n      "value": 801.3011904761905\n    },\n    {\n      "test_name": "two traffics test",\n      "metric": "p50_latency",\n      "value": 800.0\n    },\n    {\n      "test_name": "two traffics test",\n      "metric": "p90_latency",\n      "value": 1000.0\n    },\n    {\n      "test_name": "two traffics test",\n      "metric": "p99_latency",\n      "value": 1900.0\n    }\n  ],\n  "text": "two traffics test: inner traffic : committed: 15541.59 txn/s, latency: 1134.34 ms, (p50: 1100 ms, p70: 1200, p90: 1300 ms, p99: 1700 ms), latency samples: 5805340\\ntwo traffics test : committed: 100.01 txn/s, latency: 801.30 ms, (p50: 800 ms, p70: 900, p90: 1000 ms, p99: 1900 ms), latency samples: 1680\\nLatency breakdown for phase 0: [\\"MempoolToBlockCreation: max: 0.393, avg: 0.342\\", \\"ConsensusProposalToOrdered: max: 0.131, avg: 0.123\\", \\"ConsensusOrderedToCommit: max: 0.212, avg: 0.195\\", \\"ConsensusProposalToCommit: max: 0.340, avg: 0.318\\"]\\nMax non-epoch-change gap was: 0 rounds at version 0 (avg 0.00) [limit 4], 0.78s no progress at version 40090 (avg 0.06s) [limit 15].\\nMax epoch-change gap was: 0 rounds at version 0 (avg 0.00) [limit 4], 0.32s no progress at version 2925044 (avg 0.32s) [limit 16].\\nTest Ok"\n}'
Trailing Log Lines:
networkchaos.chaos-mesh.org "4-gcp--as-southeast1-to-3-gcp--us-east4-netem" deleted from forge-e2e-pr-19490 namespace
test CompositeNetworkTest ... ok
Test Statistics: 
two traffics test: inner traffic : committed: 15541.59 txn/s, latency: 1134.34 ms, (p50: 1100 ms, p70: 1200, p90: 1300 ms, p99: 1700 ms), latency samples: 5805340
two traffics test : committed: 100.01 txn/s, latency: 801.30 ms, (p50: 800 ms, p70: 900, p90: 1000 ms, p99: 1900 ms), latency samples: 1680
Latency breakdown for phase 0: ["MempoolToBlockCreation: max: 0.393, avg: 0.342", "ConsensusProposalToOrdered: max: 0.131, avg: 0.123", "ConsensusOrderedToCommit: max: 0.212, avg: 0.195", "ConsensusProposalToCommit: max: 0.340, avg: 0.318"]
Max non-epoch-change gap was: 0 rounds at version 0 (avg 0.00) [limit 4], 0.78s no progress at version 40090 (avg 0.06s) [limit 15].
Max epoch-change gap was: 0 rounds at version 0 (avg 0.00) [limit 4], 0.32s no progress at version 2925044 (avg 0.32s) [limit 16].
Test Ok

=== BEGIN JUNIT ===
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="forge" tests="1" failures="0" errors="0" uuid="542ee7aa-97f8-412e-a7db-d99bfc404d2a">
    <testsuite name="local" tests="1" disabled="0" errors="0" failures="0">
        <testcase name="CompositeNetworkTest(network:multi-region-network-emulation(two traffics test)) with ">
        </testcase>
    </testsuite>
</testsuites>
=== END JUNIT ===
[2026-04-28T22:53:09Z INFO  aptos_forge::backend::k8s::cluster_helper] Deleting namespace forge-e2e-pr-19490: Some(NamespaceStatus { conditions: None, phase: Some("Terminating") })
[2026-04-28T22:53:09Z INFO  aptos_forge::backend::k8s::cluster_helper] aptos-node resources for Forge removed in namespace: forge-e2e-pr-19490
[2026-04-28T22:53:09Z INFO  ureq::unit] sending request POST http://vmagent-victoria-metrics-agent.victoria-metrics.svc:8429/api/v1/import/prometheus

test result: ok. 1 passed; 0 soft failed; 0 hard failed; 0 filtered out

Debugging output:
NAME                                         READY   STATUS      RESTARTS   AGE
aptos-node-0-validator-0                     1/1     Running     0          12m
aptos-node-1-validator-0                     1/1     Running     0          12m
aptos-node-2-validator-0                     1/1     Running     0          12m
aptos-node-3-validator-0                     1/1     Running     0          12m
aptos-node-4-validator-0                     1/1     Running     0          12m
aptos-node-5-validator-0                     1/1     Running     0          12m
aptos-node-6-validator-0                     1/1     Running     0          12m
forge-pfn-deployer-bplwd                     0/1     Completed   0          13m
forge-testnet-deployer-qsmvg                 0/1     Completed   0          13m
genesis-aptos-genesis-eforgef86b218b-6m22s   0/1     Completed   0          12m
pfn-0-0                                      1/1     Running     0          12m
pfn-1-0                                      1/1     Running     0          12m
pfn-2-0                                      1/1     Running     0          12m

@github-actions
Copy link
Copy Markdown
Contributor

✅ Forge suite framework_upgrade success on ca049383dd80675149ef2d0042668964f9f9107a ==> c398582dc578155e88a5987b0b7add4c2b7e56a7

Compatibility test results for ca049383dd80675149ef2d0042668964f9f9107a ==> c398582dc578155e88a5987b0b7add4c2b7e56a7 (PR)
Upgrade the nodes to version: c398582dc578155e88a5987b0b7add4c2b7e56a7
framework_upgrade::framework-upgrade::full-framework-upgrade : committed: 1877.28 txn/s, submitted: 1883.05 txn/s, failed submission: 5.77 txn/s, expired: 5.77 txn/s, latency: 1687.21 ms, (p50: 1200 ms, p70: 1500, p90: 2400 ms, p99: 11500 ms), latency samples: 169121
framework_upgrade::framework-upgrade::full-framework-upgrade : committed: 2199.11 txn/s, submitted: 2204.62 txn/s, failed submission: 5.51 txn/s, expired: 5.51 txn/s, latency: 1639.69 ms, (p50: 1200 ms, p70: 1200, p90: 2100 ms, p99: 11700 ms), latency samples: 183640
5. check swarm health
Compatibility test for ca049383dd80675149ef2d0042668964f9f9107a ==> c398582dc578155e88a5987b0b7add4c2b7e56a7 passed
Upgrade the remaining nodes to version: c398582dc578155e88a5987b0b7add4c2b7e56a7
framework_upgrade::framework-upgrade::full-framework-upgrade : committed: 2018.62 txn/s, submitted: 2026.44 txn/s, failed submission: 7.82 txn/s, expired: 7.82 txn/s, latency: 1483.46 ms, (p50: 1000 ms, p70: 1200, p90: 2100 ms, p99: 10900 ms), latency samples: 180640
Test Ok

@vgao1996 vgao1996 merged commit 52ec857 into main Apr 28, 2026
59 checks passed
@vgao1996 vgao1996 deleted the vgao/cli-rich-vm-status branch April 28, 2026 22:57
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.

5 participants