Skip to content

fix: preserve raw nTxType bytes on pre-DIP-0002 transactions#726

Merged
QuantumExplorer merged 1 commit into
v0.42-devfrom
claude/recursing-leakey-fd3cad
May 4, 2026
Merged

fix: preserve raw nTxType bytes on pre-DIP-0002 transactions#726
QuantumExplorer merged 1 commit into
v0.42-devfrom
claude/recursing-leakey-fd3cad

Conversation

@QuantumExplorer

Copy link
Copy Markdown
Member

Summary

Follow-up to #675. That PR made version == 0 transactions decode as Classic so they wouldn't be rejected on UnknownSpecialTransactionType, but it dropped the original on-wire nTxType u16. After decode, consensus_encode() and txid() re-emitted self.tx_type() as 0, so the malformed mainnet transaction no longer round-tripped and its txid changed after decode.

This PR introduces a new variant pair that preserves the raw u16:

  • TransactionType::ClassicalWithNonStandardVersionTypeBytes(u16)
  • TransactionPayload::ClassicalWithNonStandardVersionTypeBytesPayloadType(u16)

The pseudo-payload variant carries the raw bytes through the existing Option<TransactionPayload> slot on Transaction, so no new struct field is added. tx_type() derives the TransactionType variant from it via the existing from_optional_payload path.

Encoder / txid / sighash

  • (self.tx_type() as u16) is replaced with self.tx_type().to_u16() (had to drop #[repr(u16)] since the enum now has a tuple variant).
  • The encoder, txid(), and the sighash path skip the payload-section block when the payload is the new variant — pre-DIP-0002 transactions have no payload section on the wire (not even a length prefix), so emitting one would change the bytes.

Decoder

For version == 0:

  • type bytes == 0Classic (existing behavior preserved)
  • type bytes != 0ClassicalWithNonStandardVersionTypeBytes(raw) (new path)

Wallet routing

TransactionRouter::classify_transaction treats the new variant as logically Classic — falls through to the standard / coinbase / coinjoin classification.

Test plan

  • New unit test test_pre_dip2_classical_with_non_standard_version_type_bytes_roundtrip:
    • decodes a tx with version=0, nTxType=0x002A and asserts the variant is preserved
    • asserts byte-exact serialize round-trip
    • asserts txid() == Txid::hash(tx_bytes)
    • asserts the txid differs from the same tx with type bytes zeroed (proves we don't collapse different on-chain txs to one id)
  • Existing test_transaction_version still passes (version=0, type=0 still maps to plain Classic)
  • cargo test --workspace --lib — 532 dashcore + 461 key-wallet + others all pass
  • cargo clippy --workspace --all-features --all-targets -- -D warnings clean
  • cargo fmt --check clean

🤖 Generated with Claude Code

PR #675 made version=0 transactions decode as Classic, but it dropped
the original on-wire `nTxType` u16. After decode, `consensus_encode`/
`txid` re-emitted `tx_type()` as 0, breaking byte-exact round-trip and
changing the txid for the malformed mainnet transaction.

Add a `TransactionType::ClassicalWithNonStandardVersionTypeBytes(u16)`
variant (and a matching pseudo-payload `TransactionPayload::
ClassicalWithNonStandardVersionTypeBytesPayloadType(u16)`) that carry
the raw u16 read from the wire. Encoding, txid, and sighash now emit
those bytes verbatim, and the encoder skips the payload section since
pre-DIP-0002 transactions have no payload section on the wire (not even
a length prefix).

`#[repr(u16)]` is dropped from `TransactionType` since the enum is no
longer field-less; replaced `(... as u16)` casts with a new `to_u16()`
method.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@QuantumExplorer has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 33 minutes and 6 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4a57eb1f-a2e6-4173-beac-c3f254b2b93b

📥 Commits

Reviewing files that changed from the base of the PR and between 95377d5 and 68f5593.

📒 Files selected for processing (6)
  • dash/src/blockdata/transaction/mod.rs
  • dash/src/blockdata/transaction/special_transaction/asset_unlock/unqualified_asset_unlock.rs
  • dash/src/blockdata/transaction/special_transaction/mod.rs
  • dash/src/crypto/sighash.rs
  • key-wallet/src/psbt/map/global.rs
  • key-wallet/src/transaction_checking/transaction_router/mod.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/recursing-leakey-fd3cad

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
Review rate limit: 0/1 reviews remaining, refill in 33 minutes and 6 seconds.

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

@codecov

codecov Bot commented May 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.26087% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.86%. Comparing base (7dc1225) to head (68f5593).
⚠️ Report is 1 commits behind head on v0.42-dev.

Files with missing lines Patch % Lines
...c/blockdata/transaction/special_transaction/mod.rs 51.42% 17 Missing ⚠️
...src/transaction_checking/transaction_router/mod.rs 78.57% 3 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##           v0.42-dev     #726      +/-   ##
=============================================
+ Coverage      70.84%   70.86%   +0.02%     
=============================================
  Files            319      319              
  Lines          68184    68243      +59     
=============================================
+ Hits           48305    48361      +56     
- Misses         19879    19882       +3     
Flag Coverage Δ
core 75.82% <77.33%> (+0.01%) ⬆️
ffi 45.49% <ø> (ø)
rpc 20.00% <ø> (ø)
spv 87.24% <ø> (+0.05%) ⬆️
wallet 69.61% <82.35%> (+<0.01%) ⬆️
Files with missing lines Coverage Δ
dash/src/blockdata/transaction/mod.rs 86.76% <100.00%> (+0.58%) ⬆️
...ansaction/asset_unlock/unqualified_asset_unlock.rs 70.19% <100.00%> (ø)
dash/src/crypto/sighash.rs 65.61% <100.00%> (ø)
key-wallet/src/psbt/map/global.rs 51.26% <100.00%> (+0.62%) ⬆️
...src/transaction_checking/transaction_router/mod.rs 88.80% <78.57%> (-0.46%) ⬇️
...c/blockdata/transaction/special_transaction/mod.rs 38.16% <51.42%> (+2.64%) ⬆️

... and 3 files with indirect coverage changes

@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.

Self Reviewed

@QuantumExplorer
QuantumExplorer merged commit d67cc03 into v0.42-dev May 4, 2026
39 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/recursing-leakey-fd3cad branch May 4, 2026 20:29
QuantumExplorer added a commit to dashpay/platform that referenced this pull request May 7, 2026
…ck tx builders

Same root cause as the prior commit: rust-dashcore's tightened decoder
(PR dashpay/rust-dashcore#726, in the v0.42-dev bump) refuses to consume
a special-transaction payload when the outer Transaction's version is
< 3 with a non-zero nTxType, so any AssetLock transaction built with
version 0 round-trips with trailing payload bytes and fails decode.

The earlier commit only fixed the dpp test fixture. Three other places
build AssetLock transactions the same broken way and surface in CI as
"data not consumed entirely" failures across drive-abci strategy tests
(address_funding_from_asset_lock + address_credit_withdrawal flows):

- packages/strategy-tests/src/transitions.rs (two builders used by the
  drive-abci strategy_tests harness for fixed and dynamic-amount asset
  locks)
- packages/rs-dpp/.../validate_asset_lock_transaction_structure/v0/mod.rs
  (test-only make_asset_lock_transaction helper)

Switch each outer Transaction.version from 0 to 3. The inner
AssetLockPayload.version (a separate u8 field) is left alone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants