Repository navigation
Support Dash Core v24 wire formats: CbTx v4, special tx types 10-12, ProRegTx v3, version 2 asset-unlock txid #1047
Copy link
Copy link
Closed
6 / 66 of 6 issues completedClosed
6 / 66 of 6 issues completed
Copy link
Description
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.
- CbTx version 4 (
src/evo/cbtx.h:32-68). A new fieldmerkleRootAssetUnlocks(uint256) followscreditPoolBalance. 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. - Version 2 Asset Unlock txid (
src/primitives/transaction.cpp:76-88,src/primitives/transaction.h:47-53). Payload version byte2, serialization identical to v1, buttxid = 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 typeMSG_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). - 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 insrc/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. - ProRegTx version 3 (ExtAddr) becomes valid (
ProTxVersion::GetMax,src/evo/providertx.h). Its layout afternOperatorRewarddiffers from v1/v2 (src/evo/providertx.h:162-226): nestednetInfo(extended-address format instead of a single service address), thenpayouts_countu8 +payouts[]({scriptPayout, reward u16}, DIP-26 multi-party payouts),shares_countu8 +shares[]({amount i64, scriptRefund, scriptReward, keyIDOwner}),joinSigs[shares_count](var-bytes each),nEarlyPeriodBlocksu32,nEarlyPenaltyi64, theninputsHash; for Evo nodesplatformNodeIDfollows without the P2P/HTTP port fields.scriptPayoutis 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 afterasset_locked_amountfor 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'sCoreRPC::get_blockand 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) returnsUnknownSpecialTransactionTypefor 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 (singleservice_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 inrpc-jsononly.) Transaction::txid()is wrong for v2 unlocks.dash/src/blockdata/transaction/mod.rs:199-224hashes 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
- CbTx v4 decode/encode (+ size).
- Payload types 10/11/12 with real payload structs (or at minimum a raw-bytes fallback so unknown special types never fail block decoding).
- ProRegTx/ProUpServTx/ProUpRegTx v3 layouts (extended addresses, DIP-26 payouts, collateral shares, join sigs, early-exit terms; Evo v3 has no port fields).
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'sevo_assetlocks_tests. Callers that usetxid()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) fromtx.txid()(rs-drive-abci/src/platform_types/withdrawal/unsigned_withdrawal_txs/v0/mod.rs:164), and Core's message hash coversrequestedHeight/quorumHash, so after this change a v2 unlock signed viatxid()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 oftxid(). This also changesBlock::compute_merkle_root()/check_merkle_root()(they hashtxid()), which is the desired result: Core's block merkle root commits to the stable txid.- Inv type 34 (
MSG_ASSET_UNLOCK) in the p2p inventory enum; agetdatafor it is answered with atxmessage.
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.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
shumkov commentedon Oct 11, 2026
@ZocoLini two things on the V24 decoding work, so we don't duplicate anything:
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, reusingMasternodePayoutShareand the extendednetInfo. Your call.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_decodedrops the payload length prefix, so any layout we don't know misaligns the rest of the block. The idea is to do what Core'sGetTxPayloaddoes: 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, withtx_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
UnknownPayloadTypewhile 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.ZocoLini commentedon Oct 11, 2026
@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
ZocoLini commentedon Oct 11, 2026
@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
ZocoLini commentedon Oct 11, 2026
@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
test(dash): decode real Core v24 vectors from a devnet
test(dash): decode real Core v24 vectors from a devnet (#1158)