Repository navigation
feat(dash)!: keep special payloads raw when their layout is unknown - #1154
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to 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 |
|
|
Waiting for bot review — coderabbitai not yet. Wait for the missing reviews, or a writer can post |
|
Bots are done — your move: post |
Codecov Report❌ Patch coverage is
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
|
d02f422 to
70bc91e
Compare
|
Bots are done — your move: post |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70bc91e to
5ed0a9f
Compare
|
Waiting for bot review — coderabbitai not yet. Wait for the missing reviews, or a writer can post |
shumkov
left a comment
There was a problem hiding this comment.
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_bitsetignores the padding bits of the last byte (consensus/encode.rs:1077), andLLMQType::from(u8)maps an unknown type toLlmqtypeUnknown, 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-325documents "Non-null iffpayload_type == 1" (and 2-4). A raw ProRegTx now haspayload_type == 1with 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.
|
Bots are done — your move: address shumkov left a review thread unresolved, then post |
|
This PR has merge conflicts with the base branch. Please rebase or merge the base branch into your branch to resolve them. |
# 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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@shumkov all comments addressed, I am merging as soon as CI finishes |
|
Waiting for bot review — coderabbitai not yet. Wait for the missing reviews, or a writer can post |
|
Bots are done — your move: post |
…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)
`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)
…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>
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
GetTxPayloaddoes. 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.UnknownPayloadTypeis renamedRawPayloadType, as it now also holds payloads of known types.get_type()reports the known type.TransactionType::consensus_decodealone decides between the typed and the raw payload. ATransactiondeserialized 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 bincodeDecodeis hand-written for that, with the derived layout.Breaking:
UnknownPayloadTypeis renamed, and a payload that fails to decode is kept raw instead of failing the transaction.Testing
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/self-revieweddash/src/blockdata/transaction/mod.rs,dash/src/blockdata/transaction/special_transaction/mod.rs,key-wallet-ffi/src/special_payload.rs) — QuantumExplorer or xdustinfacekey-wallet(key-wallet/src/transaction_checking/transaction_router/tests/classification.rs) — QuantumExplorer or xdustinfaceWhen every merge requirement is met, the
PR Hygienecheck passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.Summary by CodeRabbit