Skip to content

fix: compute SML entry hash with SER_GETHASH semantics in dash#798

Open
xdustinface wants to merge 1 commit into
devfrom
fix/sml-entry-hash-gethash
Open

fix: compute SML entry hash with SER_GETHASH semantics in dash#798
xdustinface wants to merge 1 commit into
devfrom
fix/sml-entry-hash-gethash

Conversation

@xdustinface

@xdustinface xdustinface commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

MasternodeListEntry::calculate_entry_hash hashed the full network consensus serialization, including the leading version. Dash Core's CSimplifiedMNListEntry::CalcHash uses CHashWriter(SER_GETHASH, ...), and SER_GETHASH does not set SER_NETWORK, so the SER_NETWORK-gated leading nVersion is omitted from the hash pre-image. The two contexts therefore diverged for every entry.

Extract the post-version body of the wire encoder into a shared consensus_encode_body helper. consensus_encode writes version then the body (byte-identical to before), while calculate_entry_hash hashes the body alone, matching Core's CalcHash. Verified against Core for both a version 1 and a version 2 Evo entry.

Also fix SocketAddr decoding to use to_ipv4_mapped instead of to_ipv4, so an all-zero (::) service address round-trips to the same 16 bytes instead of acquiring an ::ffff: mapped prefix. The prefix corruption changed the wire bytes and thus the entry hash for masternodes with an unset service.

Summary by CodeRabbit

  • Bug Fixes

    • Preserve IPv6 address format during consensus decoding to avoid unintended IPv4-mapping of all-zero addresses.
    • Correct masternode entry hash computation to match expected network compatibility and hashing preimage.
  • Tests

    • Added regression tests validating IPv6 address round-trip and masternode list hash/encoding compatibility.

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ca2d9d82-59a6-46c2-96b6-a2f78a782e8f

📥 Commits

Reviewing files that changed from the base of the PR and between 458894f and 09a142f.

📒 Files selected for processing (3)
  • dash/src/sml/address.rs
  • dash/src/sml/masternode_list_entry/hash.rs
  • dash/src/sml/masternode_list_entry/mod.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • dash/src/sml/masternode_list_entry/hash.rs
  • dash/src/sml/address.rs
  • dash/src/sml/masternode_list_entry/mod.rs

📝 Walkthrough

Walkthrough

The PR splits MasternodeListEntry consensus encoding into version and body parts, updates entry hashing to use the body-only preimage, and changes SocketAddr decoding to recognize IPv4-mapped IPv6 values while preserving round-trip bytes.

Changes

Consensus Encoding and Hashing Alignment

Layer / File(s) Summary
MasternodeListEntry encoding refactor: version and body separation
dash/src/sml/masternode_list_entry/mod.rs
consensus_encode now writes version first and delegates the remaining fields to consensus_encode_body, which encodes the version-dependent body fields.
Hash calculation: use body encoding as preimage
dash/src/sml/masternode_list_entry/hash.rs
calculate_entry_hash now hashes the body-only consensus encoding, and the new fixture-based test checks the resulting hashes against Dash Core values.
SocketAddr decoding: IPv4-mapped IPv6 detection
dash/src/sml/address.rs
Consensus decoding now uses Ipv6Addr::to_ipv4_mapped() when deciding whether a 16-byte value should become SocketAddr::V4 or remain SocketAddr::V6; a regression test verifies exact byte preservation for an all-zero payload.

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • dashpay/rust-dashcore#797: Also changes MasternodeListEntry consensus encoding behavior in dash/src/sml/masternode_list_entry/mod.rs, with direct impact on serialized bytes and downstream hashing.

Suggested reviewers

  • QuantumExplorer
  • ZocoLini

Poem

🐰 I nibbled the bytes, then hopped away,
Version and body learned their roles today.
Hashes now match with a cleaner stride,
IPv6 keeps its form inside.
Hop hop—consensus shines!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately reflects the main change: fixing masternode list entry hash computation to use SER_GETHASH semantics, which is the central objective of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sml-entry-hash-gethash

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
dash/src/sml/masternode_list_entry/hash.rs (1)

6-10: 💤 Low value

Consider returning Result or documenting infallibility.

The expect("encoding failed") violates the guideline to avoid expect() in library code. However, encoding to a Vec<u8> is practically infallible since Vec's Write impl only fails on allocation exhaustion (which would panic anyway).

Two options:

  1. Keep as-is but add a comment explaining why failure is impossible
  2. Change signature to return Result<sha256d::Hash, std::io::Error> for API consistency

Given that this is a public method and allocation failure would abort regardless, option 1 is reasonable here.

📝 Suggested documentation
 impl MasternodeListEntry {
     pub fn calculate_entry_hash(&self) -> sha256d::Hash {
+        // Vec<u8> write is infallible (barring OOM which aborts), so expect is safe here.
         let mut writer = Vec::new();
         self.consensus_encode_body(&mut writer).expect("encoding failed");
         sha256d::Hash::hash(&writer)
     }
 }

As per coding guidelines: "Avoid unwrap() and expect() in library code; use proper error types".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dash/src/sml/masternode_list_entry/hash.rs` around lines 6 - 10, The method
calculate_entry_hash currently calls self.consensus_encode_body(&mut
writer).expect("encoding failed"), which uses expect in library code; replace
this by keeping the current behavior but add a concise comment explaining why
encoding to a Vec<u8> is effectively infallible (the Write impl for Vec only
fails on allocation exhaustion which would abort) and that returning a Result
was considered but omitted for API stability; reference the calculate_entry_hash
and consensus_encode_body symbols and state that sha256d::Hash::hash(&writer)
remains unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@dash/src/sml/masternode_list_entry/hash.rs`:
- Around line 6-10: The method calculate_entry_hash currently calls
self.consensus_encode_body(&mut writer).expect("encoding failed"), which uses
expect in library code; replace this by keeping the current behavior but add a
concise comment explaining why encoding to a Vec<u8> is effectively infallible
(the Write impl for Vec only fails on allocation exhaustion which would abort)
and that returning a Result was considered but omitted for API stability;
reference the calculate_entry_hash and consensus_encode_body symbols and state
that sha256d::Hash::hash(&writer) remains unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fa716e82-9a61-4a6f-9dd8-5f0fb0b7d223

📥 Commits

Reviewing files that changed from the base of the PR and between a132945 and 458894f.

📒 Files selected for processing (3)
  • dash/src/sml/address.rs
  • dash/src/sml/masternode_list_entry/hash.rs
  • dash/src/sml/masternode_list_entry/mod.rs

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 2, 2026
@codecov

codecov Bot commented Jun 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 72.67%. Comparing base (5b9796a) to head (09a142f).

Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #798      +/-   ##
==========================================
+ Coverage   72.61%   72.67%   +0.05%     
==========================================
  Files         323      323              
  Lines       71747    71786      +39     
==========================================
+ Hits        52097    52167      +70     
+ Misses      19650    19619      -31     
Flag Coverage Δ
core 76.79% <100.00%> (+0.03%) ⬆️
ffi 45.44% <ø> (+0.01%) ⬆️
rpc 20.00% <ø> (ø)
spv 90.35% <ø> (+0.18%) ⬆️
wallet 71.29% <ø> (ø)
Files with missing lines Coverage Δ
dash/src/sml/address.rs 100.00% <100.00%> (ø)
dash/src/sml/masternode_list_entry/hash.rs 100.00% <100.00%> (ø)
dash/src/sml/masternode_list_entry/mod.rs 62.31% <100.00%> (+2.31%) ⬆️

... and 5 files with indirect coverage changes

@github-actions github-actions Bot added the ready-for-review CodeRabbit has approved this PR label Jun 2, 2026
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

This PR has merge conflicts with the base branch. Please rebase or merge the base branch into your branch to resolve them.

@github-actions github-actions Bot added the merge-conflict The PR conflicts with the target branch. label Jun 5, 2026
`MasternodeListEntry::calculate_entry_hash` hashed the full network consensus serialization, including the leading `version`. Dash Core's `CSimplifiedMNListEntry::CalcHash` uses `CHashWriter(SER_GETHASH, ...)`, and `SER_GETHASH` does not set `SER_NETWORK`, so the `SER_NETWORK`-gated leading `nVersion` is omitted from the hash pre-image. The two contexts therefore diverged for every entry.

Extract the post-`version` body of the wire encoder into a shared `consensus_encode_body` helper. `consensus_encode` writes `version` then the body (byte-identical to before), while `calculate_entry_hash` hashes the body alone, matching Core's `CalcHash`. Verified against Core for both a `version` 1 and a `version` 2 Evo entry.

Also fix `SocketAddr` decoding to use `to_ipv4_mapped` instead of `to_ipv4`, so an all-zero (`::`) service address round-trips to the same 16 bytes instead of acquiring an `::ffff:` mapped prefix. The prefix corruption changed the wire bytes and thus the entry hash for masternodes with an unset service.
@xdustinface xdustinface force-pushed the fix/sml-entry-hash-gethash branch from 458894f to 09a142f Compare June 5, 2026 14:13
@github-actions github-actions Bot removed merge-conflict The PR conflicts with the target branch. ready-for-review CodeRabbit has approved this PR labels Jun 5, 2026
@github-actions github-actions Bot added the ready-for-review CodeRabbit has approved this PR label Jun 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-review CodeRabbit has approved this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant