Repository navigation
Description
Summary
dashpay/dash#7639 (DIP-0027 amendment, spec dashpay/dips#189) is merged into Core develop and ships with the v24 hard fork. Once DEPLOYMENT_V24 is active, Core accepts version 2 Asset Unlock transactions: same serialization as v1, but the txid is computed with the quorum signing fields zeroed, so every re-signed instance of one withdrawal has one stable txid, and Core InstantSend-locks the unlock as soon as it is minable. That is what makes Platform→Core withdrawals respendable immediately.
Core keeps accepting v1 unlocks after activation, so v24 activating on testnet does not break withdrawals as Platform emits them today (rs-dpp/src/withdrawal/document_try_into_asset_unlock_base_transaction_info/v1/mod.rs:71-78 hard-codes version: 1). But none of the v2 behaviour is exercised until Platform emits v2. dash#7639 lists this as a known follow-up ("Platform-side emitter PR: payload version byte + deterministic v24 gate on core_chain_locked_height"); this issue tracks it so it is scheduled before v24 activates on testnet.
What Core expects from a v2 unlock
- Payload version byte
2; every other payload byte identical to v1 (index,fee,requestedHeight,quorumHash,quorumSig). - Before v24 is active Core rejects v2 with
bad-assetunlocktx-version-2(src/evo/assetlocktx.cpp:207), so the version must be chosen deterministically from Core fork state, not from a node-local flag. - txid =
SHA256d(serialized tx with the trailing 132 bytes zeroed):requestedHeight(4) +quorumHash(32) +quorumSig(96), i.e.ASSET_UNLOCK_QUORUM_INFO_SIZEinsrc/primitives/transaction.h:47; seeComputeStableUnlockHashinsrc/primitives/transaction.cpp:76-88. The full-serialization hash is the "instance hash" (CTransaction::GetInstanceHash), committed to by the coinbase (CbTxv4merkleRootAssetUnlocks), relayed as inv typeMSG_ASSET_UNLOCK(34) to peers ≥ 70242. - The message the quorum signs is unchanged:
SerializeHash(tx with quorumSig zeroed), which still coversrequestedHeightandquorumHash(src/evo/assetlocktx.cpp:220-228). That equals today'sTransaction::txid()of the unsigned tx, but it will not equaltxid()for a v2 unlock once rust-dashcore implements the v2 rule (item 2). See item 3. - One claimant per withdrawal index in the Core mempool: a second instance under a different txid is rejected as
assetunlock-stale-instanceunless itsrequestedHeightis higher. A re-sign of a v2 instance keeps the txid, so it is an in-place refresh. - Core signs the IS lock only when the unlock is minable in the next block, which includes "the mempool's pending withdrawal total fits the credit pool's
currentLimit". Platform pooling above Core's limit therefore delays locks; that is the coupling feat(consensus): relative net asset-unlock limit for v24 and getcreditpoolinfo dash#7712 and the closed feat(drive)!: withdrawal limit mirrors Core's credit pool and counts withdrawals in flight #4906 are about.
What needs to change
- Version selection: emit
version: 2once v24 is active at the Core height the block commits to. The gate must be deterministic across validators, so derive it from the block header'score_chain_locked_height(e.g.getdeploymentinfo <hash of that height>→deployments.v24.active, or an equivalent already-queried fork-state source), never from each node's local Core tip.dashcore-rpcmay needgetdeploymentinfosupport; there is noget_deployment_infoin rust-dashcore today. - rust-dashcore
Transaction::txid()must implement the v2 rule (tracked separately on rust-dashcore); otherwise anything in Platform that derives an outpoint or txid from the built transaction (append_signatures_and_broadcast_withdrawal_transactions/v0/mod.rs:85logs it; SDK/wallet code derives withdrawal output outpoints) disagrees with Core. - Sign the full-serialization hash, not
txid()(must land together with the rust-dashcore bump). The vote extension the quorum signs is built fromtx.txid()(rs-drive-abci/src/platform_types/withdrawal/unsigned_withdrawal_txs/v0/mod.rs:164,tx_to_extend_vote_extension). After rust-dashcore#1047,txid()of a v2 unlock zeroesrequestedHeight/quorumHash, so it no longer equals Core's message hash and every v2 withdrawal signature fails Core verification. Switch the extension to the full-serialization (instance) hash; for v1 the two are identical, so this is a no-op before v2 is emitted. (The order matters: emitting v2 on the current pin would still sign correctly, but wallets would compute wrong txids; bumping the pin without this change breaks signing.) - Re-sign path: an expired v2 instance re-signed with a new
requestedHeight/quorum keeps the same txid. Make sure nothing in the withdrawal document lifecycle assumes the re-signed transaction has a new id. - Never rewrite a signed withdrawal's untied bytes. The v2 txid is stable only while outputs, fee, index and payload version stay fixed across re-signs. Today this holds (
rebroadcast_expired_withdrawal_documentsv2 only resetstransactionSignHeightand moves the same untied transaction back to the queue), but nothing enforces it. A future migration that recomputes fees or output scripts for queued/expired withdrawals would silently invalidate every child spend. Make it a stated invariant with a test. Untied transactions built as v1 before the gate flips stay v1 when re-signed; that is valid. - Optional —
Minedstatus and IS lock.update_broadcasted_withdrawal_statusesonly acts onChainlocked; aMined-but-not-yet-chainlocked unlock pastcore_expiration_blocks(48) is marked EXPIRED and re-signed (harmless — Core rejects already-mined indexes — but wasted work). Optionally surfacegetassetunlockstatuses' newinstantlockfield so SDKs can show withdrawals as locked before COMPLETE (needs the optional field indashcore-rpc'sAssetUnlockStatusResult). - Tests: regtest activates v24 at genesis and devnets after their start time, so a dashmate local network on Core
developcan exercise this end to end:getassetunlockstatusesreportsinstantlock: truefor pooled indexes,getrawtransactionshowsinstanceHash, and a forced expiry + re-sign yields the same txid. dash#7639'sfeature_asset_locks.py(test_asset_locks_v2_*) shows the expected sequences.
Related
- feat: version 2 asset unlocks with stable txids and InstantSend locks (DIP-0027 amendment, v24) dash#7639, docs(dip27): version 2 Asset Unlock transactions with stable txids and InstantSend locks dips#189, feat(consensus): relative net asset-unlock limit for v24 and getcreditpoolinfo dash#7712 (v24 withdrawal limit), feat(drive)!: withdrawal limit mirrors Core's credit pool and counts withdrawals in flight #4906 (closed; Platform keeps PV14, the Core/Platform pair is to be tested to breaking on regtest/testnet before V24 parameters are set).
- Ecosystem parsers for v2 txids / CbTx v4: rust-dashcore, dashcore-lib, DashSync, dashj (issues filed separately).
Activity