Skip to content

withdrawals (v22) asset-unlock limit is enforced per block, not per 576-block window as DIP-0027 / release notes state #7660

Open

Description

@DashBot-0001

Expected behavior

Under the withdrawals (v22) fork, asset-unlock (withdrawal) transactions should be limited to 2000 DASH per 576 latest blocks, as stated in DIP-0027 § Withdrawal limits, the v22.0.0 release notes ("Flat 2000 Dash per 576 latest blocks") and PR #6369's description ("2000 dash per last 576 blocks. That's all.").

Actual behavior

The limit is enforced per block: after a 2000 DASH unlock is mined, further unlocks of up to 2000 DASH are accepted and mined in each following block. Consecutive blocks unlocked 2000 + 1 + 2000 + 2000 + 1999 DASH in regtest on the unmodified v23.1.8 binary.

Summary

CCreditPoolManager::ConstructCreditPool computes the DEPLOYMENT_WITHDRAWALS limit as

} else if (DeploymentActiveAt(*block_index, ..., Consensus::DEPLOYMENT_WITHDRAWALS)) {
    currentLimit = std::min(currentLimit, LimitAmountV22);   // 2000 DASH
}

without subtracting latelyUnlocked (the sliding-window total that the pre-v22 branch and the DEPLOYMENT_V24
branch both subtract). Since sessionUnlocked is per block, the effective rule under withdrawals is
2000 DASH per block, i.e. up to 2000 × 576 per window (bounded only by the pool balance).

The documented rule is per window:

  • DIP-0027 § Withdrawal limits: "The withdrawal would result in more than a 2000 DASH reduction in the credit pool
    over the past 576 blocks" (pre-v23/v24 paragraph).
  • v22.0.0 release notes: "Flat 2000 Dash per 576 latest blocks."
  • PR feat: increased withdrawal limits to flat 2000 from v22 #6369 description: "2000 dash per last 576 blocks. That's all."

Why the functional test does not catch it

feature_asset_locks.py::test_withdrawals_fork mines a 2000 DASH unlock, then in the next block sends a 1 DASH unlock
and calls ensure_tx_is_not_mined(txid). That helper loops over self.nodes calling gettransaction; only node 0
imported the withdrawal key, so node 1 raises Invalid or non-wallet transaction id, which the helper treats as
"not mined". The assertion therefore passes regardless of whether the tx was mined. (The v24 section added in
#6662 uses assert txid not in node.getblock(tip)['tx'], which is a real check.)

Reproduction (regtest, published v23.1.8 x86_64 binary, unmodified consensus code)

Patch test_withdrawals_fork to replace the vacuous check with a real one and probe consecutive blocks
(test/config.ini pointed at the release binaries, no rebuild):

# replaces: create_assetunlock(522, COIN) / send_tx / generate / ensure_tx_is_not_mined
self.log.info(f"PROBE-PRE pool={self.get_credit_pool_balance()} height={node.getblockcount()}")
for idx, amt in ((522, COIN), (523, 2000 * COIN), (524, 2000 * COIN), (525, 1999 * COIN)):
    tx = self.create_assetunlock(idx, amt, pubkey)
    txid = self.send_tx(tx)
    self.generate(node, 1)
    block = node.getblock(node.getbestblockhash())
    self.log.info(f"PROBE-{idx} amount={amt} mined={txid in block['tx']} height={node.getblockcount()} pool={self.get_credit_pool_balance()}")

Log:

post-withdrawals height: 599 credit: 904076738936
... index 521 (2000 DASH) mined at height 644 ...
PROBE-PRE pool=708894078618 height=645
PROBE-522 amount=100000000    mempool_accepted=True mined=True height=646 pool=708898628561
PROBE-523 amount=200000000000 mempool_accepted=True mined=True height=647 pool=509003178504
PROBE-524 amount=200000000000 mempool_accepted=True mined=True height=648 pool=309107728447
PROBE-525 amount=199900000000 mempool_accepted=True mined=True height=649 pool=109312278390

Binary: dashcore-23.1.8-x86_64-linux-gnu.tar.gz (GPG: Good signature, Pasta). Only the test file was changed:
the ensure_tx_is_not_mined call after index 522 was replaced by txid in node.getblock(tip)['tx'] probes for
indices 522-525 in consecutive blocks.

Expected per DIP-0027: after 2000 DASH is unlocked in block N, a 1 DASH unlock in block N+1 is rejected
(failed-creditpool-unlock-too-much) until the window clears.
Observed: 1 + 2000 + 2000 + 1999 DASH mined in blocks 646-649 immediately after the 2000 DASH unlock in block 644 (8000 DASH in 6 blocks); every tx was accepted to mempool and mined.

Mainnet status

withdrawals is buried on mainnet (since 2201472) and testnet; v24 is not active on either network (latest release v23.1.8, getblockchaininfo lists no v24 softfork). So the limit
that DIP-0027 describes as the guard "to avoid a catastrophic failure if Platform is compromised" is currently
per block, not per window. The v24 branch already has the intended shape (LimitAmountV24 - latelyUnlocked).

Suggested fix

Either apply the window subtraction in the withdrawals branch (consensus change — needs a fork, so probably
not), or at minimum document the actual v22 semantics in DIP-0027 and fix the test so it asserts what it claims.
Flagging mainly so the v24 activation is understood as the point where the documented limit starts to hold.

Mainnet status

withdrawals is buried on mainnet (since 2201472) and testnet; v24 is not active on either network (latest release v23.1.8, getblockchaininfo lists no v24 softfork). So the limit
that DIP-0027 describes as the guard "to avoid a catastrophic failure if Platform is compromised" is currently
per block, not per window. The v24 branch already has the intended shape (LimitAmountV24 - latelyUnlocked).

System information

dashcore-23.1.8-x86_64-linux-gnu.tar.gz from the GitHub release (GPG signature verified), Linux x86_64, regtest via test/functional/feature_asset_locks.py with test/config.ini pointed at the release binaries. Same code path is present on develop (src/evo/creditpool.cpp, DEPLOYMENT_WITHDRAWALS branch).

Suggested fix

Either apply the window subtraction in the withdrawals branch (consensus change — needs a fork, so probably
not), or at minimum document the actual v22 semantics in DIP-0027 and fix the test so it asserts what it claims.
Flagging mainly so the v24 activation is understood as the point where the documented limit starts to hold.

Posted with assistance from Claude (Anthropic).

Activity

  1. added a commit that references this issue on Sep 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    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