Skip to content

fix(drive-abci)!: refuse bytes after a state transition (PV14) - #5011

Merged
QuantumExplorer merged 1 commit into
v4.2-devfrom
claude/exciting-heyrovsky-7a7b5d
Sep 26, 2026
Merged

QuantumExplorer merged 1 commit into
v4.2-devfrom
claude/exciting-heyrovsky-7a7b5d

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

check_tx and block processing decode every raw state transition with decode_raw_state_transitions. Generation 0 of that method calls StateTransition::deserialize_from_bytes_untrusted_in_version, which ignores bytes left over after the value. So a transition with any bytes after it decodes, validates and executes exactly like the transition alone.

Identity nonces still stop the same transition from executing twice. But every such variant has its own Tenderdash transaction hash. The mempool's duplicate check compares hashes, so it treats each variant as a different transaction. Clients that track a transition by the hash of its bytes can also see a transaction hash they never sent.

#4931 added exact decoders to dpp, but no consensus path called them yet.

What was done?

  • dpp: added StateTransition::deserialize_from_bytes_untrusted_exact_in_version next to deserialize_from_bytes_untrusted_in_version. Both now go through one private helper that applies the value depth limit and the active version check. The exact one decodes with feat(dpp): exact and untagged untrusted state transition decode via the derive's consumed byte count #4931's deserialize_from_bytes_untrusted_exact, so leftover bytes are a PlatformDeserializationError ("N bytes left over after the value"). The existing function behaves exactly as before.
  • drive-abci: added generation 1 of decode_raw_state_transitions. It is a copy of generation 0 that decodes exactly. Leftover bytes are handled like any other parse failure: an InvalidEncoding carrying SerializedObjectParsingError (10002). check_tx returns that as its error, and block processing records it as an unpaid consensus error. No new error code.
  • Generation 1 also turns StateTransitionIsNotActiveError into an InvalidEncoding carrying StateTransitionNotActiveError (10603). That is the same arm #4993 adds to generation 0; see "Dependency on fix(drive-abci): a transition whose version is not active is not a decode failure #4993" below.
  • DRIVE_ABCI_METHOD_VERSIONS_V10 (protocol version 14, unreleased, amended in place) now selects generation 1. The dispatcher's known_versions is [0, 1]. Protocol versions 1 to 13 keep generation 0.
  • Added item 46 to the protocol version 14 notes in packages/rs-platform-version/src/version/v14.rs.
  • Generation 0's tests are pinned to PlatformVersion::get(13). Its code is unchanged.

Before / after

Take a signed DashPay contract create raw and append one byte (raw ++ [0x00]):

Before (and still at protocol version 13) After, at protocol version 14
check_tx valid, fee estimated refused: SerializedObjectParsingError (10002), "Parsing of serialized object failed due to: unable to deserialize dpp::state_transition::StateTransition: 1 bytes left over after the value"
block processing SuccessfulExecution: contract created, fee charged UnpaidConsensusError(SerializedObjectParsingError): nothing written, nothing charged

The exact bytes raw are accepted at both protocol versions.

Honest clients are unaffected

No client encoder adds bytes after a transition:

  • rs-dpp's derive serializes with bincode encode_to_vec.
  • rs-sdk puts serialize_to_bytes() into BroadcastStateTransitionRequest.
  • rs-dapi forwards those bytes to Tenderdash unchanged.
  • wasm-sdk, wasm-dpp2 and the legacy js-dash-sdk (toBuffer()) use the same encoding.
  • The Swift and Kotlin SDKs never build transition bytes themselves. They broadcast through the FFI, which calls rs-sdk.

Dependency on #4993

#4993 adds the not-active arm to generation 0. Generation 1 already has it, so behaviour is the same whichever PR merges first. Both PRs edit the tests of decode_raw_state_transitions/v0/mod.rs close together (this one pins them to protocol version 13), so the second one to merge will have a small text conflict there.

How Has This Been Tested?

  • dpp unit tests: the exact in-version decoder accepts the exact bytes and refuses 1 and 100 leftover bytes, which the loose decoder ignores. It still refuses a transition that is not active, with the not-active error.
  • drive-abci decode_raw_state_transitions v1 unit tests: exact bytes decode; 1 and 100 leftover bytes are refused; a transition followed by a second one is refused; the size limit is still checked first; a transition that is not active is reported as StateTransitionNotActiveError.
  • A dispatcher test runs the exact bytes, +1 and +100 through both protocol version 13 (all three decode as the transition) and protocol version 14 (only the exact bytes decode).
  • check_tx tests run a signed DashPay contract create through check_tx and process_raw_state_transitions:
    • protocol version 14: the exact bytes execute; with 1 or 100 bytes appended, both paths refuse it with SerializedObjectParsingError, the check result has no fee, and the identity's balance is unchanged.
    • protocol version 13: the exact bytes and both padded variants are accepted and executed.

Run locally:

  • cargo test -p drive-abci --lib -- execution::check_tx state_transition_processing: 60 passed, 1 ignored (already #[ignore] on the base branch)
  • cargo test -p drive-abci --lib -- abci::handler::check_tx: 5 passed
  • cargo test -p dpp --all-features --lib -- serialization state_transition::tests: 306 passed; cargo test -p dpp --all-features --test untrusted_decode: 8 passed
  • cargo test -p platform-version: passed
  • cargo clippy -p dpp -p drive-abci --all-features --all-targets -- -D warnings and cargo fmt --all -- --check: clean

In-place changes to shipped generations

None that change behaviour. StateTransition::deserialize_from_bytes_untrusted_in_version, which generation 0 calls at every shipped protocol version, now delegates to a helper shared with the exact decoder. It runs the same decoder, depth limit and active version check in the same order, so its output is identical. Generation 0 of decode_raw_state_transitions has only its tests changed, pinned to protocol version 13.

Breaking Changes

Consensus change at protocol version 14 only: a raw state transition with bytes after the encoded transition is now refused, unpaid, instead of executed. Protocol versions 13 and earlier are unchanged.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • 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
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

PR Hygiene · 9e29b15

  • Bots — coderabbitai not yet · thepastaclaw not yet — /skip-bots proceeds without the ones not yet reported
  • Self-review — post /self-reviewed once the bots are done
  • Within your 5 open PRs
  • Build running
  • Approvals — you own every area touched; none needed

When every box is checked the PR Hygiene check passes and this can merge.

decode_raw_state_transitions v0 decodes with
StateTransition::deserialize_from_bytes_untrusted_in_version, which
ignores bytes left over after the value, so a transition with bytes
appended executed as the original under another transaction hash.

v1 decodes with the new
StateTransition::deserialize_from_bytes_untrusted_exact_in_version:
left over bytes are an InvalidEncoding (SerializedObjectParsingError),
refused unpaid in check_tx and block processing. It also reports a
transition whose version is not active as StateTransitionNotActiveError
(the arm #4993 adds to v0). Only DRIVE_ABCI_METHOD_VERSIONS_V10
(protocol version 14) selects v1; v0's tests are pinned to 13.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 26, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 33 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: dashpay/platform/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: ca349b16-92f5-4938-bb63-ed14481de120

📥 Commits

Reviewing files that changed from the base of the PR and between 0621670 and 9e29b15.

📒 Files selected for processing (7)
  • packages/rs-dpp/src/state_transition/mod.rs
  • packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs
  • packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/decode_raw_state_transitions/mod.rs
  • packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/decode_raw_state_transitions/v0/mod.rs
  • packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/decode_raw_state_transitions/v1/mod.rs
  • packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v10.rs
  • packages/rs-platform-version/src/version/v14.rs

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.

@github-actions github-actions Bot added this to the v4.2.0 milestone Sep 26, 2026
@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Sep 26, 2026

@QuantumExplorer QuantumExplorer left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

approved

@QuantumExplorer
QuantumExplorer merged commit d23f444 into v4.2-dev Sep 26, 2026
15 of 16 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/exciting-heyrovsky-7a7b5d branch September 26, 2026 02:17
@thepastaclaw

thepastaclaw commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

🕓 Queued for automated review — 2nd in line, estimated start in ~25 min (commit 9e29b15)
Estimated review time once started: ~40 min (two-phase automated review; median of recent runs).

  • Request priority review — click to move this review to the front of the queue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-bots Waiting for the review bots to report on this head

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants