Skip to content

feat: validate masternode list merkle root#799

Draft
xdustinface wants to merge 1 commit into
fix/sml-entry-hash-gethashfrom
feat/validate-mn-list-root
Draft

feat: validate masternode list merkle root#799
xdustinface wants to merge 1 commit into
fix/sml-entry-hash-gethashfrom
feat/validate-mn-list-root

Conversation

@xdustinface
Copy link
Copy Markdown
Collaborator

…t in dash

Recompute the masternode-list Merkle root over the fully assembled list right after it is built from an MnListDiff and hard-reject any diff whose recomputed root does not match the merkleRootMNList committed in the diff's coinbase cbTx. The list is never advanced on mismatch.

MasternodeList::validate_mn_list_root recomputes via the existing calculate_masternodes_merkle_root machinery (full list sorted by raw proRegTxHash, SER_GETHASH entry hash) and compares it to the coinbase root extracted by coinbase_mn_list_root. It replaces the previously-unused has_valid_mn_list_root, which only compared against a stored root rather than recomputing. It is invoked from both production build paths: MasternodeList::try_from_with_block_hash_lookup (from_diff) and MasternodeList::apply_diff, so the new SmlError::MasternodeListRootMismatch (carrying expected coinbase root, recomputed root, and block height/hash) propagates through MasternodeListEngine::apply_diff and feed_qr_info up to dash-spv sync without being swallowed. from_diff no longer trusts the bogus merkle_hashes.first() value and instead computes the root via the builder.

The historical capture fixtures predate root validation and commit coinbase roots over full mainnet lists their captured new_masternodes subset does not reproduce. The entry hash and Merkle ordering were verified byte-identical to Dash Core's CSimplifiedMNListEntry::CalcHash and CSimplifiedMNList::CalcMerkleRoot over all 3147 entries of the genesis fixture, so the discrepancy is in the fixture data, not the logic. Test-only helpers rewrite each fixture diff's coinbase root to the value the assembled list produces so the unrelated quorum and chainlock tests stay meaningful, and new tests assert the hard-reject path.

Based on:

Recompute the masternode-list Merkle root over the fully assembled list right after it is built from an `MnListDiff` and hard-reject any diff whose recomputed root does not match the `merkleRootMNList` committed in the diff's coinbase `cbTx`. The list is never advanced on mismatch.

`MasternodeList::validate_mn_list_root` recomputes via the existing `calculate_masternodes_merkle_root` machinery (full list sorted by raw `proRegTxHash`, `SER_GETHASH` entry hash) and compares it to the coinbase root extracted by `coinbase_mn_list_root`. It replaces the previously-unused `has_valid_mn_list_root`, which only compared against a stored root rather than recomputing. It is invoked from both production build paths: `MasternodeList::try_from_with_block_hash_lookup` (`from_diff`) and `MasternodeList::apply_diff`, so the new `SmlError::MasternodeListRootMismatch` (carrying expected coinbase root, recomputed root, and block height/hash) propagates through `MasternodeListEngine::apply_diff` and `feed_qr_info` up to `dash-spv` sync without being swallowed. `from_diff` no longer trusts the bogus `merkle_hashes.first()` value and instead computes the root via the builder.

The historical capture fixtures predate root validation and commit coinbase roots over full mainnet lists their captured `new_masternodes` subset does not reproduce. The entry hash and Merkle ordering were verified byte-identical to Dash Core's `CSimplifiedMNListEntry::CalcHash` and `CSimplifiedMNList::CalcMerkleRoot` over all 3147 entries of the genesis fixture, so the discrepancy is in the fixture data, not the logic. Test-only helpers rewrite each fixture diff's coinbase root to the value the assembled list produces so the unrelated quorum and chainlock tests stay meaningful, and new tests assert the hard-reject path.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cc027d2e-2938-402a-8b9a-c5348eb8a81e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/validate-mn-list-root

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@xdustinface xdustinface changed the title feat: validate masternode list merkle root against coinbase commitmen… feat: validate masternode list merkle root Jun 2, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 2, 2026

Codecov Report

❌ Patch coverage is 98.47716% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.62%. Comparing base (458894f) to head (9f435e9).

Files with missing lines Patch % Lines
dash/src/sml/masternode_list_engine/mod.rs 97.59% 2 Missing ⚠️
dash/src/sml/masternode_list/merkle_roots.rs 97.43% 1 Missing ⚠️
Additional details and impacted files
@@                      Coverage Diff                       @@
##           fix/sml-entry-hash-gethash     #799      +/-   ##
==============================================================
- Coverage                       72.70%   72.62%   -0.09%     
==============================================================
  Files                             322      322              
  Lines                           71402    71200     -202     
==============================================================
- Hits                            51913    51706     -207     
- Misses                          19489    19494       +5     
Flag Coverage Δ
core 76.68% <98.47%> (+0.10%) ⬆️
ffi 46.42% <ø> (ø)
rpc 20.00% <ø> (ø)
spv 90.15% <ø> (-0.19%) ⬇️
wallet 71.29% <ø> (ø)
Files with missing lines Coverage Δ
dash/src/sml/error.rs 0.00% <ø> (ø)
dash/src/sml/masternode_list/apply_diff.rs 95.70% <100.00%> (+1.21%) ⬆️
dash/src/sml/masternode_list/from_diff.rs 95.12% <100.00%> (+0.83%) ⬆️
dash/src/sml/masternode_list/merkle_roots.rs 83.01% <97.43%> (+18.63%) ⬆️
dash/src/sml/masternode_list_engine/mod.rs 83.18% <97.59%> (+0.80%) ⬆️

... and 12 files with indirect coverage changes

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant