Repository navigation
fix(dash)!: bound partial merkle trees by Dash's block size - #1307
Conversation
A Dash block holds up to 33,333 transactions, its 2 MB size limit over the 60 bytes of the smallest transaction. Dash Core's `CPartialMerkleTree::ExtractMatches` allows that many (`nTransactions > MaxBlockSize() / 60`). `extract_matches` still uses Bitcoin's BIP141 bound, `MAX_BLOCK_WEIGHT / MIN_TRANSACTION_WEIGHT` = 16,666, so it refuses the proof of any larger Dash block. That includes the coinbase proof of a masternode list diff at that block, which `coinbase_merkle_root` reports as `InvalidCoinbaseProof`, and that refusal bans the peer. This test fails on dev: 33,333 transactions are refused with `TooManyTransactions`. The next commit fixes it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
`PartialMerkleTree::extract_matches` refused a tree of more than `MAX_BLOCK_WEIGHT / MIN_TRANSACTION_WEIGHT` = 16,666 transactions. That is Bitcoin's BIP141 bound. A Dash block holds up to 2 MB over the 60 bytes of the smallest transaction, so up to 33,333 transactions, and Dash Core's `CPartialMerkleTree::ExtractMatches` allows exactly that (`MaxBlockSize() / 60`, src/merkleblock.cpp). So a valid Dash block above 16,666 transactions could not be proven. A masternode list diff at that block failed its coinbase proof with `InvalidCoinbaseProof`. That refusal bans the peer, so every honest peer asked for that list was banned in turn, and masternode sync stopped. Any miner can build such a block. Raising the bound adds no work. The traversal is bounded by the hashes and flag bits the proof carries, and the depth of the tree is log2 of the count. The BIP141 constants stay public in `blockdata::constants`. Nothing in the crate uses them any more. Test would have caught this in CI: `a_partial_merkle_tree_spans_as_many_transactions_as_a_dash_block_holds`. ✖ before the fix (33,333 transactions refused with `TooManyTransactions`), ✔ after. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
|
Waiting for bot review — coderabbitai not yet. Wait for the missing reviews, or a writer can post |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #1307 +/- ##
==========================================
+ Coverage 82.13% 82.16% +0.03%
==========================================
Files 290 290
Lines 76201 76323 +122
==========================================
+ Hits 62588 62713 +125
+ Misses 13613 13610 -3
|
xdustinface
left a comment
There was a problem hiding this comment.
Code
- Put Dash's block size in
blockdata::constantsand dropMAX_BLOCK_WEIGHTandMIN_TRANSACTION_WEIGHT.
After this PR nothing in the workspace uses those two constants. They still describe Bitcoin's BIP141 limit, the one that caused this bug, while Dash's real limit sits as a bare2_000_000inmerkle_tree/block.rs. Removing them costs nothing extra, becausedevalready carries unreleased breakingdashcorechanges sincev0.45.0(#400, #1288). Replace both withpub const MAX_BLOCK_SIZE: u32 = 2_000_000;(Core'sMAX_DIP0001_BLOCK_SIZE) inconstants.rsand deriveMAX_BLOCK_TRANSACTIONSfrom it. The whole workspace still compiles with that change.
|
Bots are done — your move: address xdustinface requested changes, then post |
…s block size `MAX_BLOCK_WEIGHT` and `MIN_TRANSACTION_WEIGHT` described Bitcoin's BIP141 limit, the bound that refused large Dash blocks' partial merkle trees. Nothing in the workspace uses them any more, while Dash's block size sat as a bare `2_000_000` in `merkle_tree/block.rs`. `blockdata::constants::MAX_BLOCK_SIZE` is now Dash Core's `MAX_DIP0001_BLOCK_SIZE`, 2,000,000 bytes, and the partial merkle tree bound is derived from it. The bound stays at 33,333 transactions. BREAKING CHANGE: `blockdata::constants::MAX_BLOCK_WEIGHT` and `MIN_TRANSACTION_WEIGHT` are removed. Use `MAX_BLOCK_SIZE`, Dash's limit in bytes. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
Waiting for bot review — coderabbitai not yet. Wait for the missing reviews, or a writer can post |
|
Bots are done — your move: address xdustinface requested changes, then post |
|
/self-reviewed |
|
Ready for review — files with no dedicated owner: QuantumExplorer or ZocoLini or xdustinface · re-review or resolve: xdustinface. |
Follow-up to #1200. This gap was listed in the handoff comment on #1205 (#1205 (comment)): "
extract_matchesuses Bitcoin's 16,666-transaction bound where a Dash block holds about 33,333".Problem
PartialMerkleTree::extract_matchesrefused a tree of more thanMAX_BLOCK_WEIGHT / MIN_TRANSACTION_WEIGHT= 4,000,000 / 240 = 16,666 transactions. That is Bitcoin's BIP141 bound. A Dash block holds up to 2 MB over the 60 bytes of the smallest transaction. Dash Core'sCPartialMerkleTree::ExtractMatchesrefuses onlynTransactions > MaxBlockSize() / 60= 33,333 (src/merkleblock.cpp, src/consensus/consensus.h).Since #1200, every masternode list diff proves its coinbase with this tree. For a valid Dash block of 16,667–33,333 transactions,
MnListDiff::coinbase_merkle_rootfails withInvalidCoinbaseProof. That is one of the two refusalsFeedError::blames_peerholds against the peer. So the honest peer is banned, then the next peer asked for that list, and masternode sync stops. Any miner can build such a block on purpose, with about 1 MB of 60-byte transactions.#400 (SegWit/Taproot removal) left
MAX_BLOCK_WEIGHTandMIN_TRANSACTION_WEIGHTinblockdata::constants. This was their only use.Fix
extract_matchesbounds the count by a privateMAX_BLOCK_TRANSACTIONS = MAX_BLOCK_SIZE / 60, Dash Core's bound.blockdata::constants::MAX_BLOCK_SIZEis new: it is Dash Core'sMAX_DIP0001_BLOCK_SIZE, 2,000,000 bytes. It replaces the BIP141MAX_BLOCK_WEIGHTandMIN_TRANSACTION_WEIGHT, which nothing in the workspace used any more.The other checks are unchanged:
The traversal stays bounded by the hashes and flag bits the proof carries. The depth is log2 of the count, so the higher bound adds no work. A security review found no new forgery, malleability, depth-confusion or DoS surface.
Rejected: a dependency on base-sdk's
dash-primitivesforMAX_DIP0001_BLOCK_SIZE, which adds a crate for one constant.Red → green
a_partial_merkle_tree_spans_as_many_transactions_as_a_dash_block_holds(dash/src/merkle_tree/block.rs) was committed before the fix:assertion failed: tree(33_333).extract_matches(&mut vec![], &mut vec![]).is_ok()TooManyTransactionsBreaking
blockdata::constants::MAX_BLOCK_WEIGHTandMIN_TRANSACTION_WEIGHTare removed. UseMAX_BLOCK_SIZE, Dash's limit in bytes. dev already carries breakingdashcorechanges since v0.45.0.Checks (local)
At 1ca9924 (head), every check below passed in full:
dashd_masternode11/11 anddashd_sync38/38. The 8 tests dev already ignores (3#[ignore], 5ignoredoctests) were skipped.At f5b94f7, before the constants change:
cargo fmt --all --check, and typos 1.40.0 on the changed files.cargo +1.89 check --workspace --all-features.cargo clippy --workspace --all-targets --all-features -- -D warnings.RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps.cargo test --workspace --all-features --no-fail-fastwith the dashd regtest env: everything passed, includingdashd_masternode11/11, except one test indashd_sync.dashd_syncran while another session's dashd suite and builds held the machine at a load average of about 25.test_coinbase_matures_when_a_later_block_touches_the_walletfailed ("the newest coinbase is immature").test_coinbase_matures_without_later_wallet_activityandtest_runtime_add_with_tip_advance_during_rescanfailed.--test-threads=4.#[ignore], 5ignoredoctests).Notes
PartialMerkleTree::from_partsexpands the flag bytes into aVec<bool>before any bound check.🤖 Generated with Claude Code
PR Hygiene ·
1ca9924dash/src/blockdata/constants.rs,dash/src/merkle_tree/block.rs) — QuantumExplorer or ZocoLini or xdustinfaceWhen every merge requirement is met, the
PR Hygienecheck passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.Summary by CodeRabbit