Skip to content

Support Dash Core v24 wire formats: CbTx v4, special tx types 10-12, ProRegTx v3, version 2 asset-unlock txid #1047

Closed
6 of 6 issues completed

Description

@PastaPastaPasta

What Core v24 changes on the wire

Dash Core develop (the v24 line; dashpay/dash#7437 and dashpay/dash#7639 merged Sep 18-21) introduces the following once DEPLOYMENT_V24 activates. None of it is live on mainnet or testnet yet; testnet activation is the next step, so parsers need to be ready before then.

  1. CbTx version 4 (src/evo/cbtx.h:32-68). A new field merkleRootAssetUnlocks (uint256) follows creditPoolBalance. From the activation block on, every coinbase must be version 4 (src/evo/cbtx.cpp:45-51: version ≥ 4 is required when v24 is active, rejected before). The field is the merkle root over the instance hashes of the block's version-2 asset unlocks, all-zero when there are none.
  2. Version 2 Asset Unlock txid (src/primitives/transaction.cpp:76-88, src/primitives/transaction.h:47-53). Payload version byte 2, serialization identical to v1, but txid = SHA256d(serialized tx with the trailing 132 bytes zeroed) — requestedHeight (4) + quorumHash (32) + quorumSig (96). The hash of the full serialization is the instance hash, used by the new inv type MSG_ASSET_UNLOCK (34, protocol 70242) and by the CbTx root above. Re-signed instances of one withdrawal therefore share a txid and their outputs are spent by that stable txid. Platform emits v1 today and will switch to v2 after activation (Emit version 2 Asset Unlock transactions once Core v24 activates (stable txid + InstantSend-locked withdrawals) platform#4908).
  3. Three new special transaction types (src/primitives/transaction.h:39-41): TRANSACTION_PROVIDER_DISSOLVE = 10 (ProDisTx), TRANSACTION_PROVIDER_UPDATE_SHARE = 11 (ProUpShareTx), TRANSACTION_PROVIDER_UPDATE_SHARED_REGISTRAR = 12 (ProUpSharedRegTx). Payloads in src/evo/providertx.h (CProDisTx: nVersion u16, proTxHash, actorIndex, sig_count u8, sigs[]; CProUpShareTx: nVersion, proTxHash, shareIndex, scriptReward, inputsHash, vchSig; CProUpSharedRegTx: nVersion, proTxHash, pubKeyOperator (basic BLS), keyIDVoting, inputsHash, sig_count u8, sigs[]). Spec: docs: add Decentralized Masternode Shares DIP dips#187.
  4. ProRegTx version 3 (ExtAddr) becomes valid (ProTxVersion::GetMax, src/evo/providertx.h). Its layout after nOperatorReward differs from v1/v2 (src/evo/providertx.h:162-226): nested netInfo (extended-address format instead of a single service address), then payouts_count u8 + payouts[] ({scriptPayout, reward u16}, DIP-26 multi-party payouts), shares_count u8 + shares[] ({amount i64, scriptRefund, scriptReward, keyIDOwner}), joinSigs[shares_count] (var-bytes each), nEarlyPeriodBlocks u32, nEarlyPenalty i64, then inputsHash; for Evo nodes platformNodeID follows without the P2P/HTTP port fields. scriptPayout is not serialized in v3. Same ExtAddr treatment applies to ProUpServTx/ProUpRegTx v3 where applicable.

What breaks in this crate today (dev @ 2026-09-22)

  • Block decoding fails from the activation block. TransactionType::consensus_decode (dash/src/blockdata/transaction/special_transaction/mod.rs:422-459) reads the payload length prefix and discards it, then decodes the typed payload directly from the stream. CoinbasePayload::consensus_decode (coinbase.rs) stops after asset_locked_amount for any version ≥ 3, so a v4 coinbase leaves 32 unconsumed bytes and the next transaction in the block is decoded from the wrong offset. Any consumer that deserializes a full block (Block), including Platform's CoreRPC::get_block and dash-spv, is affected. Re-encoding a decoded v4 coinbase also drops the field, so its hash no longer matches the block merkle root.
  • Unknown tx types are a hard error. TransactionType::try_from (mod.rs:370-386) returns UnknownSpecialTransactionType for 10/11/12, so any block or transaction containing a ProDisTx/ProUpShareTx/ProUpSharedRegTx fails to decode.
  • ProRegTx v3 is not parsed. ProviderRegistrationPayload::consensus_decode (provider_registration.rs) has one layout for all versions (single service_address, script_payout, ports for HPMNs). A v3 payload mis-decodes and desynchronizes the stream. (fix(rpc-json)!: resolve Core 23 nested platform addresses (breaking field/accessor renames) #808 handled the nested address format in rpc-json only.)
  • Transaction::txid() is wrong for v2 unlocks. dash/src/blockdata/transaction/mod.rs:199-224 hashes the payload verbatim. Every wallet/SPV path that derives outpoints from a received withdrawal (key-wallet, the fix(key-wallet): discover DashPay asset unlock receipts #1004 asset-unlock receipt discovery, dash-spv UTXO tracking) will compute a txid Core does not recognise, so v2 withdrawal outputs cannot be spent and instance re-signs look like new transactions.

Suggested scope

  1. CbTx v4 decode/encode (+ size).
  2. Payload types 10/11/12 with real payload structs (or at minimum a raw-bytes fallback so unknown special types never fail block decoding).
  3. ProRegTx/ProUpServTx/ProUpRegTx v3 layouts (extended addresses, DIP-26 payouts, collateral shares, join sigs, early-exit terms; Evo v3 has no port fields).
  4. Transaction::txid(): for tx type 9 with payload version ≥ 2, zero the trailing 132 bytes before hashing; expose the instance hash separately. Add the DIP-0027 v2 test vectors from Core's evo_assetlocks_tests. Callers that use txid() as a signing hash must move to the instance hash in the same release: Platform builds the withdrawal vote extension (the message the quorum signs) from tx.txid() (rs-drive-abci/src/platform_types/withdrawal/unsigned_withdrawal_txs/v0/mod.rs:164), and Core's message hash covers requestedHeight/quorumHash, so after this change a v2 unlock signed via txid() fails Core verification (Emit version 2 Asset Unlock transactions once Core v24 activates (stable txid + InstantSend-locked withdrawals) platform#4908 item 3). Call this out in the changelog as a behaviour change of txid(). This also changes Block::compute_merkle_root() / check_merkle_root() (they hash txid()), which is the desired result: Core's block merkle root commits to the stable txid.
  5. Inv type 34 (MSG_ASSET_UNLOCK) in the p2p inventory enum; a getdata for it is answered with a tx message.

Core references: src/evo/cbtx.h, src/evo/providertx.h, src/evo/assetlocktx.h, src/primitives/transaction.{h,cpp}, src/protocol.h:527, test/functional/feature_asset_locks.py, doc/release-notes-7437.md, doc/release-notes-7639.md.

Activity

  1. self-assigned this
    on Oct 7, 2026
  2. shumkov commented on Oct 11, 2026

    @shumkov
    Collaborator

    @ZocoLini two things on the V24 decoding work, so we don't duplicate anything:

    1. dash: parse ProRegTx v3 layout #1132 (ProRegTx v3): have you started? There's no branch or PR yet. It's on Platform's critical path: Platform v5.0 must decode V24 blocks before testnet activates V24, and for that release line we'll backport the per-type parsers onto its current pin (feat(dash)!: decode and encode CbTx v4 merkleRootAssetUnlocks #1137, feat(dash)!: decode unknown special transaction types as raw payloads #1136, feat(dash)!: decode and encode ProUpServTx and ProUpRegTx version 3 #1138, dash: parse ProRegTx v3 layout #1132). Today DAPI also refuses to broadcast a valid ProTx v3, because it decodes before forwarding. If you haven't started, we can take dash: parse ProRegTx v3 layout #1132 on top of your feat(dash)!: decode and encode ProUpServTx and ProUpRegTx version 3 #1138 branch, reusing MasternodePayoutShare and the extended netInfo. Your call.

    2. A general guard for "known type, unknown layout", the case feat(dash)!: decode unknown special transaction types as raw payloads #1136 lists as out of scope and the "at minimum a raw-bytes fallback" this issue asks for. TransactionType::consensus_decode drops the payload length prefix, so any layout we don't know misaligns the rest of the block. The idea is to do what Core's GetTxPayload does: read exactly the bytes the prefix announces and accept the typed payload only if it consumes all of them, its version is one the decoder knows, and it re-encodes to the same bytes. Otherwise keep the bytes raw, extending feat(dash)!: decode unknown special transaction types as raw payloads #1136's raw variant to known types, with tx_type() still reporting the real type. Blocks then always decode and txids stay right whatever Core changes next. It doesn't replace dash: parse ProRegTx v3 layout #1132/feat(dash)!: decode and encode ProUpServTx and ProUpRegTx version 3 #1138: wallets still need typed fields.

    We have a reviewed draft spec for (2) and would open a sub-issue for it. Some points to settle with you first: renaming UnknownPayloadType while it's unreleased, the key-wallet-ffi contract for typed pointers, and re-parsing payloads persisted raw. Happy to implement it, or leave it to you if you'd rather.

  3. ZocoLini commented on Oct 11, 2026

    @ZocoLini
    Collaborator

    @shumkov
    1 I have done some work to fix #1132, but I am waiting for Kevin to re-review #1138, he is missing again.
    2 I am okay with renaming. I dont know right now what changes I would need to do in key-wallet-ffi, its public API its mostly unused tbh. Open an issue indicating that we need to implement something similat to what GetTxPayload does, I will take care of doing it as soon as possible

  4. ZocoLini commented on Oct 11, 2026

    @ZocoLini
    Collaborator

    @shumkov I dismissed Kevins changes requested state, he is missing and I implemented what he was asking for. Pr is merged, I am closing #1132 as soon as posisble with a new PR to address the issue

  5. ZocoLini commented on Oct 11, 2026

    @ZocoLini
    Collaborator

    @shumkov PR merge, issue closed, let me know if you have any problems with it, I am also working on a PR for the other issue you previously mentioned, I will let you know when it is open

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions