Skip to content

fix(drive): strengthen execution proof result validation (tagged-outcome variant)#4207

Merged
QuantumExplorer merged 23 commits into
v4.1-devfrom
claude/tagged-proof-outcome
Jul 23, 2026
Merged

fix(drive): strengthen execution proof result validation (tagged-outcome variant)#4207
QuantumExplorer merged 23 commits into
v4.1-devfrom
claude/tagged-proof-outcome

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Jul 23, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Alternative design to #4155 — pick one, close the other. This branch contains everything on #4155 plus one commit (44a29c6) that replaces its two-entry-point verifier design with a tagged return type.

Both PRs solve the same problem: state-transition proofs for some transition families (balance top-ups, credit transfers/withdrawals, address funds movements, shields, no-history token operations) cannot be bound to the execution of one specific transition — they only authenticate the affected keys' state at the committed block — and callers must not mistake such snapshots for execution evidence.

  • fix(drive): strengthen execution proof result validation #4155 (design 1): two entry points — the strict verify_state_transition_was_executed_with_proof fails closed for those families, and verify_state_transition_affected_state_with_proof returns the verified snapshot. The chosen function is the guarantee.
  • This PR (design 2): one entry point returning (RootHash, StateTransitionProofOutcome), where the outcome is ExecutionProved(result) or AffectedState(result). The guarantee is part of the type; callers match on it. Since the classification is derivable from the transition type (plus the contract's keeps-history flags for tokens), there is no semantics parameter and no second entry point.

What was done?

  • StateTransitionProofOutcome in dpp's proof_result module (ExecutionProved / AffectedState, with result(), into_result(), is_execution_proved()).
  • Drive's v0 verifier sets an affected_state flag in the snapshot arms and tags the result once at the tail; the StateTransitionProofSemantics enum, the sibling entry point, and its platform-version gate are removed. Net −159 lines vs design 1.
  • FromProof (SDK wait path) exposes the inner result unchanged — same five broadcast flows work; callers that need execution evidence can match the outcome.
  • wasm-drive-verify: single verifyStateTransitionWasExecutedWithProof whose result gains an executionProved getter (the second export from design 1 is gone).
  • drive-abci strategy tests now assert the snapshot tag on real post-state proofs — strictly stronger than design 1's expect-error: the proof must verify and stay un-upgraded to execution evidence. The shield state-proof test flips the same way.
  • Drive verifier tests assert ExecutionProved on happy paths and AffectedState on the snapshot families; design 1's fail-closed reject tests merge into the snapshot assertions (they were the same proofs).

Trade-off to weigh: design 1 gives a function whose Ok always means "executed" (misuse = calling the wrong function); design 2 gives one function whose result type forces the distinction at every use site (misuse = writing .into_result() without thinking). Design 2 touches more call sites now but leaves a smaller, single-entry API surface.

How Has This Been Tested?

drive lib: 3,233 tests pass (67 in the verifier module, including tag assertions for both guarantees and the disabled-key regression test). dpp proof_result: 4 pass. drive-abci: all test targets compile; the shield state-proof test passes with the new snapshot-tag assertion. dash-sdk: 173 tests pass. Clippy clean on drive --all-features --all-targets; drive --no-default-features --features verify and wasm-drive-verify (wasm32) compile.

Breaking Changes

None consensus-wise. Rust API: the verifier's return type changes and the affected-state sibling from #4155 never ships (neither is released).

Checklist:

  • I have performed a self-review of my own code
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added APIs to wait for verified affected-state snapshots after broadcasting state transitions.
    • Proof results now indicate whether execution was directly proven or only the affected state was authenticated.
    • Added explicit execution-status information and error handling for proofs that do not prove execution.
  • Bug Fixes

    • Improved proof validation for identity creation and updates, including key and revision consistency checks.
    • Updated affected-state operations across transfers, tokens, contracts, and shielded transactions to use appropriate confirmation semantics.

QuantumExplorer and others added 20 commits July 20, 2026 22:40
…on-proof-binding

# Conflicts:
#	packages/swift-sdk/build_ios.sh
Resolve the tests-rs-workspace.yml conflict to the base side: the base
already carries the coverage-cleanup retry loop and its own timeout
tuning, so this PR no longer needs to ship workflow changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ransitions

verify_state_transition_was_executed_with_proof keeps failing closed for
the transition families whose proof cannot be bound to execution (balance
top-ups, credit transfers and withdrawals, address funds movements,
shields, no-history token burn/mint/transfer). A new sibling entry point,
verify_state_transition_affected_state_with_proof, serves the consumers
those errors broke: it behaves identically for execution-verifiable types
and, for the receipt-gated families, returns a verified snapshot of the
affected state instead — keys derived from the transition, values
authenticated as of the proof's block, explicitly documented as
height-pinned snapshots rather than execution evidence.

The SDK wait path (FromProof for StateTransitionProofResult) now uses the
affected-state entry point, restoring the address funds, top-up,
withdrawal, credit-transfer and no-history token transfer flows that
matched result variants nothing produced anymore. wasm-drive-verify gains
the matching verifyStateTransitionAffectedStateWithProof export.

Also relax the identity-update disabled-key check from exact equality
with the proof block's timestamp to at-or-before it: proofs may be
generated at a later block than the one that executed the update, and
disabled_at never changes afterwards, so retried or replayed proofs were
failing verification spuriously.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Alternative design to the two-entry-point split: a single
verify_state_transition_was_executed_with_proof returns
(RootHash, StateTransitionProofOutcome), where the outcome is
ExecutionProved(result) when the proof binds this specific transition's
execution and AffectedState(result) when it can only authenticate the
affected keys' state at the committed block (balance top-ups, credit
transfers and withdrawals, address funds movements, shields, no-history
token operations — the last decided by the contract's keeps-history
flags at verification time). The guarantee is part of the type, so a
snapshot cannot be mistaken for execution evidence and callers choose
explicitly by matching.

This replaces the StateTransitionProofSemantics parameter and the
verify_state_transition_affected_state_with_proof sibling entry point
(and its platform-version gate): the v0 body sets an affected_state
flag in the snapshot arms and wraps the result once at the tail.

Downstream:
- rs-drive-proof-verifier FromProof exposes the inner result unchanged;
  callers needing execution evidence match the outcome.
- wasm-drive-verify returns a single result object with an
  executionProved getter instead of a second export.
- drive-abci strategy tests assert the snapshot tag on real post-state
  proofs (stronger than the previous expect-error: the proof must parse
  AND stay un-upgraded to execution evidence); the shield state-proof
  test flips accordingly.
- drive verifier tests assert ExecutionProved on happy paths and
  AffectedState on the snapshot families; the former fail-closed reject
  tests merge into the snapshot assertions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5f559a34-2b06-4c23-aadd-a658d8c53fa3

📥 Commits

Reviewing files that changed from the base of the PR and between 552abfc and 257f640.

📒 Files selected for processing (44)
  • packages/js-evo-sdk/src/state-transitions/facade.ts
  • packages/rs-dpp/src/state_transition/proof_result.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/config_update/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/distribution/perpetual/block_based.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/freeze/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/mint/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/unfreeze/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/tests.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_update/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shield/tests.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shield_from_asset_lock/tests.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_transfer/tests.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_withdrawal/tests.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/unshield/tests.rs
  • packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs
  • packages/rs-drive-proof-verifier/src/proof.rs
  • packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/mod.rs
  • packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs
  • packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs
  • packages/rs-platform-wallet/src/wallet/shielded/operations.rs
  • packages/rs-sdk/src/error.rs
  • packages/rs-sdk/src/platform/tokens/transitions/burn.rs
  • packages/rs-sdk/src/platform/tokens/transitions/direct_purchase.rs
  • packages/rs-sdk/src/platform/tokens/transitions/freeze.rs
  • packages/rs-sdk/src/platform/tokens/transitions/mint.rs
  • packages/rs-sdk/src/platform/tokens/transitions/set_price_for_direct_purchase.rs
  • packages/rs-sdk/src/platform/tokens/transitions/transfer.rs
  • packages/rs-sdk/src/platform/tokens/transitions/unfreeze.rs
  • packages/rs-sdk/src/platform/transition/address_credit_withdrawal.rs
  • packages/rs-sdk/src/platform/transition/broadcast.rs
  • packages/rs-sdk/src/platform/transition/identity_create_from_shielded_pool.rs
  • packages/rs-sdk/src/platform/transition/top_up_address.rs
  • packages/rs-sdk/src/platform/transition/top_up_identity.rs
  • packages/rs-sdk/src/platform/transition/top_up_identity_from_addresses.rs
  • packages/rs-sdk/src/platform/transition/transfer.rs
  • packages/rs-sdk/src/platform/transition/transfer_address_funds.rs
  • packages/rs-sdk/src/platform/transition/transfer_to_addresses.rs
  • packages/rs-sdk/src/platform/transition/waitable.rs
  • packages/rs-sdk/src/platform/transition/withdraw_from_identity.rs
  • packages/wasm-drive-verify/src/state_transition/verify_state_transition_was_executed_with_proof.rs
  • packages/wasm-sdk/src/error.rs
  • packages/wasm-sdk/src/state_transitions/broadcast.rs
  • packages/wasm-sdk/src/state_transitions/contract.rs

📝 Walkthrough

Walkthrough

The PR introduces tagged execution-versus-affected-state proof outcomes, updates strict and affected-state wait semantics, adds WASM and JavaScript affected-state APIs, strengthens identity proof validation, and migrates affected-state SDK and wallet operations.

Changes

Proof outcome and verification

Layer / File(s) Summary
Tagged outcomes and verification classification
packages/rs-dpp/..., packages/rs-drive/..., packages/rs-drive-proof-verifier/...
Proof verification now returns ExecutionProved or AffectedState, with stronger identity key, revision, and disabled-key validation.
Strict and affected-state SDK waits
packages/rs-sdk/src/platform/transition/broadcast.rs, packages/rs-sdk/src/error.rs
Strict waits reject affected-state outcomes with ExecutionNotProved; affected-state waits accept both outcome types and convert the embedded proof result.
WASM and JavaScript APIs
packages/wasm-sdk/..., packages/wasm-drive-verify/..., packages/js-evo-sdk/...
New affected-state wait methods are exposed and proof verification results include an execution_proved flag.
Affected-state operation integrations
packages/rs-sdk/src/platform/..., packages/rs-platform-wallet/...
Token, identity, address, contract, and shielded flows use affected-state wait APIs where their proofs authenticate snapshots.
Proof consumer tests
packages/rs-drive-abci/...
Tests adapt to tagged outcomes and assert affected-state classification for snapshot-only transition families.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SDK
  participant Drive
  participant ProofVerifier
  Client->>SDK: broadcastAndWaitForAffectedState
  SDK->>Drive: broadcast and wait for outcome
  Drive->>ProofVerifier: verify proof
  ProofVerifier-->>Drive: ExecutionProved or AffectedState
  Drive-->>SDK: verified affected-state result
  SDK-->>Client: proof result
Loading

Possibly related PRs

  • dashpay/platform#3753: Adds the shielded funding orchestration later updated to use affected-state waiting.
  • dashpay/platform#3814: Modifies proof verification semantics for unshield and shielded-withdrawal transitions.
  • dashpay/platform#4155: Strengthens execution-proof validation related to the new outcome distinction.

Suggested reviewers: lklimek, shumkov, llbartekll

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/tagged-proof-outcome

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.26655% with 75 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.52%. Comparing base (9bfcf6c) to head (257f640).
⚠️ Report is 31 commits behind head on v4.1-dev.

Files with missing lines Patch % Lines
...state_transition_was_executed_with_proof/v0/mod.rs 88.48% 66 Missing ⚠️
...ckages/rs-dpp/src/state_transition/proof_result.rs 36.36% 7 Missing ⚠️
packages/rs-drive-proof-verifier/src/proof.rs 0.00% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           v4.1-dev    #4207     +/-   ##
===========================================
  Coverage     87.51%   87.52%             
===========================================
  Files          2667     2667             
  Lines        336972   338070   +1098     
===========================================
+ Hits         294903   295890    +987     
- Misses        42069    42180    +111     
Components Coverage Δ
dpp 88.49% <36.36%> (+0.01%) ⬆️
drive 86.31% <88.50%> (+0.06%) ⬆️
drive-abci 89.49% <100.00%> (-0.09%) ⬇️
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 92.88% <ø> (-0.02%) ⬇️
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 49.79% <0.00%> (ø)
🚀 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.

Copy link
Copy Markdown
Member Author

Codex Security review — execution-proof outcome semantics

Verdict: use #4207 as the foundation, but do not treat it as a complete security fix in its current form. The tagged-outcome design is architecturally better than closed PR #4155, but the security guarantee is still lost before production SDK callers can enforce it, and several transition arms receive the wrong tag.

I reviewed the effective landing tree 96644a9589b24e4bd65952c4ab7ceee3e13003bb (base 552abfc6243634034ebcbae63a6944ff1b570c7f, PR head 44a29c60639ec3796df74d318397f1d359bc722d) and compared it directly with #4155 head e8702dd0f05bc86448b0e8973956b5989abdc475.

Why #4207 is the better design

  • StateTransitionProofOutcome::{ExecutionProved, AffectedState} makes proof strength part of the returned value.
  • This is easier to preserve through Rust and foreign-language boundaries than fix(drive): strengthen execution proof result validation #4155's two parallel verifier entry points.
  • It creates one central vocabulary for strict execution confirmation versus height-pinned state reconciliation.
  • fix(drive): strengthen execution proof result validation #4155 was not safer at the production SDK boundary: its FromProof implementation called verify_state_transition_affected_state_with_proof() and returned a bare StateTransitionProofResult, so strict SDK callers still could not distinguish the two guarantees.

Main blocker: the new tag is erased

packages/rs-drive-proof-verifier/src/proof.rs:1502 currently returns:

Ok((Some(outcome.into_result()), mtd.clone(), proof.clone()))

That conversion deliberately discards whether Drive returned ExecutionProved or AffectedState. The generic SDK wait path consequently receives only StateTransitionProofResult and converts it into Ok through T::try_from(result).

This conflicts with the PR description's statement that callers can match on the outcome: the production FromProof/SDK path does not expose the outcome to those callers. Direct wasm-drive-verify is a useful negative control because it preserves executionProved, but the ordinary Rust SDK, WASM SDK, JavaScript, FFI, and wallet flows do not.

Findings

The review produced 10 reportable findings: 6 Medium and 4 Low, all with high static confidence. There is no High/Critical finding, consensus-state forgery, validator termination, or chain-stall path in this change.

Severity Finding Root location Why the proof is insufficient
Medium SDK erases AffectedState packages/rs-drive-proof-verifier/src/proof.rs:1502 into_result() removes the guarantee before strict wait callers receive it.
Medium IdentityCreditTransferToAddresses verifier v0/mod.rs:1044-1074 Proves current identity balance/revision and address balances, but not requested amounts, nonce, or transition identity.
Medium IdentityTopUpFromAddresses verifier v0/mod.rs:1140-1175 Proves current identity/address values, but not requested amounts, input nonce predicates, or the submitted request.
Medium No-history, ungrouped token Freeze verifier v0/mod.rs:653-675 frozen=true may have been produced by an earlier Freeze; a distinct redundant Freeze can be rejected while the snapshot still passes.
Medium No-history token DirectPurchase verifier v0/mod.rs:746-762 For a repeat buyer, an existing token balance is reusable across purchases with different counts, agreed prices, and nonces.
Medium ShieldFromAssetLock without surplus verifier v0/mod.rs:1656-1702 The proof contains only the consumed outpoint. Different owner-signed shield requests using that outpoint have the same query/result.
Low DataContractUpdate verifier v0/mod.rs:111-134 Equality with the current contract body does not prove this update's signature, nonce, or occurrence; an invalid equal-version update is a clear counterexample.
Low IdentityCreateFromShieldedPool verifier v0/mod.rs:1706-1908 Spent nullifiers plus the resulting identity/key map do not bind the complete Orchard request, denomination context, fallback address, or signatures.
Low ShieldFromAssetLock with surplus verifier v0/mod.rs:1549-1654 Strict verification authenticates only outpoint consumption and current surplus-address state, not which Orchard retry executed.
Low No-history, ungrouped SetPriceForDirectPurchase verifier v0/mod.rs:765-820 The current schedule can predate this request; acting identity, nonce, signature, and request occurrence are not proven.

All nine operation-specific branches leave the shared affected_state flag false and are therefore wrapped as ExecutionProved at v0/mod.rs:1912-1918.

Two additional classifier candidates were validated but not promoted after impact calibration:

  • IdentityUpdate: false confirmation is limited to already-satisfied/no-op key state; no unauthorized key mutation was demonstrated.
  • No-history Unfreeze: already-unfrozen state can be misattributed, but no new capability or asset effect was demonstrated.

Realistic attack boundary

The attacker is a Byzantine or compromised DAPI/proof-serving node selected by the client. It cannot forge GroveDB state, a Tenderdash quorum signature, the victim's signature, balances, or consensus execution. It can, however:

  1. observe the submitted signed transition;
  2. withhold it or observe that consensus rejected it;
  3. select a genuine proof for current state produced by an earlier or competing request; and
  4. rely on the classifier or SDK adapter to overstate that proof as execution of this exact request.

The resulting impact is false client-side completion: wallet activity, accounting, nonce/retry state, audit state, or a dependent business workflow can advance for a request that did not execute. The no-surplus shield case has the strongest external-settlement story because a counterparty may rely on consumption of a shared asset-lock outpoint as payment confirmation.

Recommended fix sequence

  1. Immediate classifier containment: mark all nine snapshot-only branches above as AffectedState. Prefer an exhaustive operation classifier over a mutable default Boolean so new variants cannot silently inherit ExecutionProved.
  2. Preserve the type end to end: make FromProof return StateTransitionProofOutcome, and carry it through rs-sdk, WASM, JavaScript, FFI, Swift, and wallet adapters.
  3. Separate public semantics: existing methods named wait_for_response / broadcast_and_wait should be strict and reject AffectedState with a typed error. Expose snapshot reconciliation through an explicitly named API such as wait_for_affected_state.
  4. Remove or constrain lossy conversion: avoid into_result() on execution-confirmation paths. If retained, make it clearly snapshot-oriented or difficult to call accidentally.
  5. Regression tests: for every affected family, construct two distinct requests with the same proof query and assert that request A's valid state proof cannot strictly confirm request B.
  6. Long-term request binding: where exact occurrence evidence is required, add a committed receipt or transition-hash binding rather than inferring causality from current state.

The classifier and end-to-end type-propagation changes should be direct code fixes and should not require a protocol-version upgrade. A new committed receipt/proof format likely would require versioning and coordinated activation.

Merge recommendation

I would keep #4207 rather than revive #4155, but request the classifier and end-to-end tag-preservation changes before considering the execution-proof issue closed. Otherwise the PR establishes the right abstraction while production callers continue receiving effectively the same untagged success result.

Validation was performed through full diff inventory, independent discovery, semantic deduplication, centralized static validation, focused negative-case construction, and source-to-sink attack-path analysis. Exact-revision source checkers and regression patches were prepared and apply/format checked. Bounded Cargo runs stopped during cold native dependency compilation before the focused test binaries executed, so this review does not claim a completed runtime or live-DAPI reproduction.

QuantumExplorer and others added 2 commits July 23, 2026 12:20
Replace the mutable affected_state flag with an exhaustive classifier
(state_transition_proof_binds_execution): every transition family — and
for tokens, the contract's keeps-history configuration and group usage —
is matched explicitly, so a new variant fails to compile until it is
classified and nothing silently inherits ExecutionProved.

Reclassify nine snapshot-only arms that were wrongly tagged as
execution evidence (per the Codex security review): data contract
update, identity credit transfer to addresses, identity top-up from
addresses, shield from asset lock (both surplus branches), identity
create from shielded pool, and the ungrouped no-history token freeze,
unfreeze, direct purchase and set-price operations. Their proofs
authenticate current state (contract body, identity/address balances,
frozen flags, pricing schedules, consumed outpoints) without binding
the request's amounts, nonces, signature, or occurrence.

Strategy tests for top-up-from-addresses and credit-transfer-to-
addresses now assert the snapshot tag on real post-state proofs; the
data contract update happy-path test flips the same way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The FromProof implementation for broadcast waits erased the
StateTransitionProofOutcome tag before SDK callers could see it, so
strict callers still received effectively untagged success results.

- FromProof<BroadcastStateTransitionRequest> now targets
  StateTransitionProofOutcome, carrying the tag through verification.
- The public wait APIs (wait_for_response, broadcast_and_wait, and
  their _with_metadata forms) are now STRICT: an AffectedState outcome
  returns the new typed Error::ExecutionNotProved instead of a success
  the caller could mistake for execution confirmation. The error is
  non-retriable — no other node can upgrade a snapshot into evidence.
- New wait_for_affected_state / broadcast_and_wait_for_affected_state
  APIs (plus _with_metadata forms) accept snapshot outcomes explicitly,
  documented as height-pinned state, never execution evidence.
- Every snapshot-family flow moved to the affected-state APIs: identity
  top-up, credit withdrawal, credit transfer (plain and to-addresses),
  top-up from addresses, address funds transfer / funding / withdrawal,
  identity create from shielded pool, the wallet's shield and
  fund-from-asset-lock waits, and the data contract update wait (via a
  type dispatch in Waitable). Token operation flows use the snapshot
  APIs because bindability depends on the contract's keeps-history
  configuration, which the SDK cannot know statically.
- wasm-sdk exposes waitForAffectedState and
  broadcastAndWaitForAffectedState (mirrored in the evo-sdk facade) and
  maps the new error to its own ExecutionNotProved kind; FFI flows
  inherit the correct semantics through the rs-sdk helpers unchanged.
- Regression tests: a classifier table test pins the snapshot-only
  families in drive, and an rs-sdk unit test pins the strict wait's
  rejection of AffectedState outcomes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@QuantumExplorer

Copy link
Copy Markdown
Member Author

Both requested work streams are implemented and pushed (44a29c6f4857e0).

1. Classifier containment (all 10 findings addressed). The mutable affected_state boolean is gone, replaced by state_transition_proof_binds_execution — an exhaustive match over every transition family (and, for tokens, the contract's keeps-history configuration and group usage). A new variant fails to compile until classified, so nothing can silently inherit ExecutionProved. All nine flagged branches are reclassified as AffectedState: data contract update, identity credit transfer to addresses, identity top-up from addresses, both shield-from-asset-lock branches, identity create from shielded pool, and the ungrouped no-history token freeze, direct purchase, and set-price operations. The unpromoted candidates got a decision each: no-history ungrouped unfreeze is also AffectedState (same evidence gap as freeze; inconsistent tags between the pair would be indefensible), while identity update stays ExecutionProved per your calibration (revision + exact key additions/disabling timestamps are bound).

2. End-to-end tag preservation. FromProof<BroadcastStateTransitionRequest> now targets StateTransitionProofOutcome — the lossy into_result() at proof.rs:1502 is gone. Per your recommendation on public semantics: wait_for_response / broadcast_and_wait (and _with_metadata forms) are now strict — an AffectedState outcome returns the new typed, non-retriable Error::ExecutionNotProved — and new wait_for_affected_state / broadcast_and_wait_for_affected_state APIs accept snapshots explicitly. Every snapshot-family flow was moved to the snapshot APIs: identity top-up/withdrawal/transfer (plain and address variants), the address funds family, identity-create-from-shielded-pool, the wallet's shield and fund-from-asset-lock waits, and contract update (via type dispatch in Waitable); token flows use the snapshot APIs since bindability depends on contract config the SDK can't know statically. wasm-sdk exposes waitForAffectedState / broadcastAndWaitForAffectedState (mirrored in evo-sdk) and maps the error to its own ExecutionNotProved kind; Swift/Kotlin inherit through the rs-sdk helpers with no FFI signature changes.

Regression tests: a classifier table test pins the snapshot-only families in drive; an rs-sdk unit test pins the strict wait's rejection; strategy tests assert the snapshot tag against real post-state proofs for the address-family transitions; and the flipped data-contract-update happy-path test pins that family with a real proof.

Deliberately deferred: per-family two-request regression tests beyond the above (the strategy tests cover the address family end-to-end; the rest need bespoke proof fixtures), and the long-term committed-receipt binding, which needs a versioned proof format and coordinated activation as you note.

Validation: drive 3,234 lib tests pass (68 in the verifier module), dash-sdk 174, platform-wallet 493; drive-abci test targets compile; wasm-sdk (wasm32), rs-sdk-ffi, platform-wallet-ffi compile; clippy clean on dash-sdk and platform-wallet; the verify-feature drive build compiles.

…fier

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@QuantumExplorer
QuantumExplorer merged commit 31c69cf into v4.1-dev Jul 23, 2026
13 of 15 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/tagged-proof-outcome branch July 23, 2026 05:55
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