Skip to content

feat(dash)!: keep special payloads raw when their layout is unknown - #1154

Merged
ZocoLini merged 6 commits into
devfrom
feat/raw-payload-fallback
Oct 12, 2026
Merged

ZocoLini merged 6 commits into
devfrom
feat/raw-payload-fallback

Conversation

@ZocoLini

@ZocoLini ZocoLini commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Part of #1047.

A special payload of a known type in a layout we don't know (a newer version) was decoded with the old layout: the length prefix was read and dropped, so the rest of the block misaligned. #1136 covered unknown types only.

Changes

  • Every special payload is read as exactly the bytes its length prefix announces, as Core's GetTxPayload does. The typed payload is kept only if it decodes from all the bytes and re-encodes to them, otherwise the bytes are kept raw. Blocks always decode and txids stay right.
  • UnknownPayloadType is renamed RawPayloadType, as it now also holds payloads of known types. get_type() reports the known type.
  • TransactionType::consensus_decode alone decides between the typed and the raw payload. A Transaction deserialized through serde or bincode passes a raw payload back to it, so payloads persisted raw by an earlier version decode typed once their layout is known. Its bincode Decode is hand-written for that, with the derived layout.

Breaking: UnknownPayloadType is renamed, and a payload that fails to decode is kept raw instead of failing the transaction.

Testing

  • Unit: a ProUpRevTx payload with the known layout decodes typed, with a trailing byte or truncated stays raw, keeps its type, re-encodes to the same bytes and leaves the next transaction aligned; a raw payload of a known layout deserializes typed through JSON, binary serde and bincode.
  • cargo test -p dashcore -p key-wallet -p key-wallet-ffi -p key-wallet-manager -p dashcore-rpc-json, clippy -D warnings.

🤖 Generated with Claude Code

PR Hygiene · e760a6a

  • Bots — coderabbitai ✓
  • Self-review — post /self-reviewed
  • Build failed
  • Approvals
    • files with no dedicated owner (dash/src/blockdata/transaction/mod.rs, dash/src/blockdata/transaction/special_transaction/mod.rs, key-wallet-ffi/src/special_payload.rs) — QuantumExplorer or xdustinface
    • key-wallet (key-wallet/src/transaction_checking/transaction_router/tests/classification.rs) — QuantumExplorer or xdustinface

When every merge requirement is met, the PR Hygiene check passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.

Summary by CodeRabbit

  • Bug Fixes
    • Special transactions with unrecognized types or payload layouts now retain their original payload data when decoded and re-encoded.
    • Valid payloads for known transaction types are recognized during decoding, including after transactions are saved and reloaded. Truncated, unsupported, or otherwise invalid payload layouts remain preserved as raw data.
    • Transactions with unrecognized special payloads continue to be classified as standard transactions.

@ZocoLini ZocoLini self-assigned this Oct 12, 2026
@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: dashpay/rust-dashcore/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5c4d8e2c-8519-49fd-9d31-1d3218f3e623

📥 Commits

Reviewing files that changed from the base of the PR and between 70bc91e and e760a6a.


📒 Files selected for processing (3)
  • dash/src/blockdata/transaction/mod.rs
  • dash/src/blockdata/transaction/special_transaction/mod.rs
  • key-wallet-ffi/src/special_payload.rs

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.



📝 Walkthrough

Walkthrough

Special transaction decoding retains payload bytes when the layout is unsupported or invalid. Transaction deserialization attempts to decode raw payloads as typed payloads. Tests cover valid and malformed layouts, persisted payloads, type zero, and wallet classification.

Changes

Special transaction payload decoding

Layer / File(s) Summary
Raw payload model and consensus decoding
dash/src/blockdata/transaction/special_transaction/mod.rs
RawPayloadType represents unrecognized transaction types and known types with unsupported layouts. Consensus decoding retains payload bytes when decoding fails or does not reproduce the original bytes.
Transaction deserialization and validation
dash/src/blockdata/transaction/mod.rs, key-wallet/src/transaction_checking/transaction_router/tests/classification.rs, key-wallet-ffi/src/special_payload.rs
Serde and bincode deserialization attempt to decode raw payloads as typed payloads. Tests cover valid and malformed layouts, persisted payload conversion, and type zero. Wallet classification uses the renamed variant, and FFI documentation allows all provider pointers to be null for raw payloads.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Suggested reviewers: kwvg


Merge Risk: ⚪ Minimal · up to e760a

Special payloads with unfamiliar layouts are now kept as raw bytes, so blocks still decode and transaction IDs stay correct. No actionable merge-blocking risk remains. The only breaking change is the rename of the payload variant, which is intentional.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 76.47% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 17 functions across 4 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the primary change: retaining special payloads as raw bytes when their layout is unknown. The breaking-change marker is appropriate because the public enum va…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR

🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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 commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — coderabbitai not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Oct 12, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Oct 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Bots are done — your move: post /self-reviewed.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed and removed waiting-bots Waiting for the review bots to report on this head labels Oct 12, 2026
@codecov

codecov Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.55172% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.60%. Comparing base (2102cac) to head (e760a6a).

Files with missing lines Patch % Lines
...c/blockdata/transaction/special_transaction/mod.rs 91.89% 3 Missing ⚠️
dash/src/blockdata/transaction/mod.rs 98.73% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #1154      +/-   ##
==========================================
+ Coverage   78.52%   78.60%   +0.07%     
==========================================
  Files         302      302              
  Lines       80413    80505      +92     
==========================================
+ Hits        63147    63279     +132     
+ Misses      17266    17226      -40     
Flag Coverage Δ
core 77.10% <96.55%> (+0.29%) ⬆️
ffi 51.24% <ø> (+0.14%) ⬆️
rpc 49.36% <ø> (ø)
spv 92.61% <ø> (-0.07%) ⬇️
wallet 82.12% <ø> (ø)
Files with missing lines Coverage Δ
key-wallet-ffi/src/special_payload.rs 92.60% <ø> (ø)
dash/src/blockdata/transaction/mod.rs 89.73% <98.73%> (+1.31%) ⬆️
...c/blockdata/transaction/special_transaction/mod.rs 51.90% <91.89%> (+6.96%) ⬆️

... and 26 files with indirect coverage changes

coderabbitai[bot]
coderabbitai Bot previously approved these changes Oct 12, 2026
@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Bots are done — your move: post /self-reviewed.
Full checklist in the description.

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

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — coderabbitai not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added waiting-bots Waiting for the review bots to report on this head and removed waiting-self-review Waiting for the author to post /self-reviewed labels Oct 12, 2026

@shumkov shumkov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks right to me, and it matches what we had in mind on #1047. Re-decoding raw payloads on load is a nice touch we hadn't thought of. A few things, none blocking:

  • No test exercises the re-encode check: every case here fails on consumption, not on re-encoding. Two decoders lose information and would reach it. read_fixed_bitset ignores the padding bits of the last byte (consensus/encode.rs:1077), and LLMQType::from(u8) maps an unknown type to LlmqtypeUnknown, which re-encodes as 0 (sml/llmq_type/mod.rs:590). A quorum commitment with a padding bit set, or with an unknown LLMQ type, should stay raw and re-encode to its original bytes.
  • key-wallet-ffi/src/special_payload.rs:319-325 documents "Non-null iff payload_type == 1" (and 2-4). A raw ProRegTx now has payload_type == 1 with every pointer null. Worth rewording, even if the API is barely used.

A note for Platform, not for this PR: the change alters which instant asset-lock proof bytes Platform accepts, because a wrong payload length prefix used to be ignored. Platform will take it with a compatibility shim in its bump; we track that on our side.

Comment thread dash/src/blockdata/transaction/special_transaction/mod.rs Outdated
Comment thread dash/src/blockdata/transaction/special_transaction/mod.rs Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Bots are done — your move: address shumkov left a review thread unresolved, then post /self-reviewed.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed bot-review-skipped A required review bot did not report; it was skipped by the window or by a person. and removed waiting-bots Waiting for the review bots to report on this head labels Oct 12, 2026
@github-actions github-actions Bot added the merge-conflict The PR conflicts with the target branch. label Oct 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR has merge conflicts with the base branch. Please rebase or merge the base branch into your branch to resolve them.

ZocoLini and others added 3 commits October 12, 2026 08:28
# Conflicts:
#	dash/src/blockdata/transaction/special_transaction/mod.rs
…erently

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ZocoLini and others added 2 commits October 12, 2026 08:58
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ZocoLini

Copy link
Copy Markdown
Collaborator Author

@shumkov all comments addressed, I am merging as soon as CI finishes

@github-actions github-actions Bot removed the merge-conflict The PR conflicts with the target branch. label Oct 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Waiting for bot review — coderabbitai not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added waiting-bots Waiting for the review bots to report on this head and removed waiting-self-review Waiting for the author to post /self-reviewed bot-review-skipped A required review bot did not report; it was skipped by the window or by a person. labels Oct 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Bots are done — your move: post /self-reviewed.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed and removed waiting-bots Waiting for the review bots to report on this head labels Oct 12, 2026
@ZocoLini
ZocoLini merged commit 2232580 into dev Oct 12, 2026
63 of 66 checks passed
@ZocoLini
ZocoLini deleted the feat/raw-payload-fallback branch October 12, 2026 17:54
shumkov pushed a commit that referenced this pull request Oct 13, 2026
…1154)

* feat(dash)!: keep special payloads raw when their layout is unknown

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

* test(dash): cover a known payload kept raw because it re-encodes differently

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

* fix(dash): keep a payload raw instead of panicking when its encoder fails

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

* fix(dash): report a raw payload of type 0 as Unknown, not Classic

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

* docs(key-wallet-ffi): a raw provider payload leaves every pointer null

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 2232580)
shumkov added a commit that referenced this pull request Oct 14, 2026
`Transaction::deserialize_ignoring_asset_lock_payload_length` decodes an
asset lock as this crate did before special payloads were read strictly
through their length prefix (#1154): the prefix must be a minimal
CompactSize, its value is ignored, and the payload must end the bytes.
Any other transaction goes to `deserialize`.

Dash Platform needs it for instant asset lock proofs. Neither the
InstantSend lock (checked against the re-encoded txid) nor the transition
signature covers the prefix, and Platform releases built on earlier pins
accepted and charged for proofs whose prefix differs from the payload
length. Platform v5.0.0-rc.1 restored that with a byte-level workaround on
its side (dashpay/platform#5403); this gives it a named, frozen decoder.

The function is standalone rather than a mode of `Decodable for
Transaction`, and writes the payload layout out instead of calling
`AssetLockPayload::consensus_decode`, so a later transaction rule or a new
asset lock payload layout cannot change what it accepts. It makes the
same finite-reader calls as the transaction decoder.

`dash/tests/legacy_asset_lock.rs` pins absolute results (fixed bytes,
txids, error variants): 1/3/5/9-byte prefixes, versions 4 and 0x7fff,
the witness form (including a witness on one of two inputs) and the
zero-input form, a long payload read after a short prefix, a payload
without credit outputs, payload versions 0/2/3/255 with appended fields
counted by the prefix or not, and the refusals (non-minimal prefix or
credit-output count, truncation, trailing bytes, segwit flags 0 and 2,
a classic transaction). A coinbase, a ProRegTx, an unknown type and
type 8 below version 3 are checked against `deserialize`, and a seeded
round-trip property covers mutated inputs.

Each of these changes to the function turns tests red: delegating
everything to `deserialize` (22 of 46), dropping the segwit rule (12),
checking `any` instead of `all` witnesses empty (2), accepting only
version 3 (2), comparing the version unsigned (1), refusing empty credit
outputs (2), dropping the trailing-bytes check (6). A differential run
against Platform's workaround over 16M seeded mutations, on dev and on
7302d0a, found no Ok/Err, decoded-transaction or error mismatch.

Adapted for this branch: the `AssetLockPayload` import no longer sits
next to the `AssetUnlockPayload` import from #1157, which this branch does
not have, and the tests decode hex with `hex`, since `hex-conservative` is
not a dependency here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 28c1a04)
shumkov added a commit that referenced this pull request Oct 14, 2026
…1167)

* feat(dash): decode asset locks ignoring their payload length prefix

`Transaction::deserialize_ignoring_asset_lock_payload_length` decodes an
asset lock as this crate did before special payloads were read strictly
through their length prefix (#1154): the prefix must be a minimal
CompactSize, its value is ignored, and the payload must end the bytes.
Any other transaction goes to `deserialize`.

Dash Platform needs it for instant asset lock proofs. Neither the
InstantSend lock (checked against the re-encoded txid) nor the transition
signature covers the prefix, and Platform releases built on earlier pins
accepted and charged for proofs whose prefix differs from the payload
length. Platform v5.0.0-rc.1 restored that with a byte-level workaround on
its side (dashpay/platform#5403); this gives it a named, frozen decoder.

The function is standalone rather than a mode of `Decodable for
Transaction`, and writes the payload layout out instead of calling
`AssetLockPayload::consensus_decode`, so a later transaction rule or a new
asset lock payload layout cannot change what it accepts. It makes the
same finite-reader calls as the transaction decoder.

`dash/tests/legacy_asset_lock.rs` pins absolute results (fixed bytes,
txids, error variants): 1/3/5/9-byte prefixes, versions 4 and 0x7fff,
the witness form (including a witness on one of two inputs) and the
zero-input form, a long payload read after a short prefix, a payload
without credit outputs, payload versions 0/2/3/255 with appended fields
counted by the prefix or not, and the refusals (non-minimal prefix or
credit-output count, truncation, trailing bytes, segwit flags 0 and 2,
a classic transaction). A coinbase, a ProRegTx, an unknown type and
type 8 below version 3 are checked against `deserialize`, and a seeded
round-trip property covers mutated inputs.

Each of these changes to the function turns tests red: delegating
everything to `deserialize` (22 of 46), dropping the segwit rule (12),
checking `any` instead of `all` witnesses empty (2), accepting only
version 3 (2), comparing the version unsigned (1), refusing empty credit
outputs (2), dropping the trailing-bytes check (6). A differential run
against Platform's workaround over 16M seeded mutations, on dev and on
7302d0a, found no Ok/Err, decoded-transaction or error mismatch.

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

* refactor(dash): move the legacy asset lock decoder into its own module

`Transaction::deserialize_ignoring_asset_lock_payload_length` becomes
`Transaction::deserialize_with_legacy_asset_lock_framing` and moves to
`dash/src/blockdata/transaction/legacy_asset_lock.rs`. The transaction
module keeps only the `mod` line.

The name and the doc comments now say plainly that this is a legacy
decoder whose framing must never change. It is neither `#[deprecated]`
nor behind a cargo feature: Dash Platform has to call it for as long as
it must agree with its older binaries and replay its chain, so there is
nothing to migrate to, and a feature would only allow building Platform
without it.

The function body is byte-for-byte the one it replaces. The 46 tests in
`dash/tests/legacy_asset_lock.rs` pass with only the name changed.

No red/green step applies: nothing behaves differently.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-self-review Waiting for the author to post /self-reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants