Skip to content

Fix sentry node country map cohort and GeoIP source accuracy #31

@bigpoppa-sys

Description

@bigpoppa-sys

Summary

The public /mnstats mapData country distribution appears to be materially wrong versus the current Chainz masternode/IP dataset, and the displayed percentages can exceed 100% because the backend country map and frontend denominator use different cohorts.

Evidence

At the time of checking on 2026-05-08:

  • https://sysnode.info/mnstats reports mn_stats.enabled = 1,944, mn_stats.total = 2,243.
  • The published mapData country counts sum to 2,163, not 1,944.
  • The top Sysnode country rows are:
    • IRN: 644 (33.1% when divided by enabled nodes)
    • DEU: 613
    • USA: 246
    • FIN: 220
    • CYP: 204
  • The first 8 Sysnode country percentages add up to about 106%, which confirms the denominator/cohort mismatch.

The backend builds mapData in services/masternodeTracker.js by iterating every masternode_list row and geolocating node.address with the local geoip-country package. It does not filter to status === 'ENABLED', while the frontend CountryList divides each country count by mn_stats.enabled.

Chainz comparison

The current Chainz endpoint used by their page (/explorer/masternodes.data.dws?coin=sys&mn=&fmt.js) reports 2,243 nodes across 2,168 servers and shows Germany / United Kingdom / Poland leading, matching their UI screenshot.

Examples of the same large IP ranges:

  • 151.244.*: Sysnode appears to classify this range into Iran-heavy counts; Chainz classifies 255 nodes mostly as GB (245 GB, 6 IR, 4 US).
  • 31.56.*: Chainz classifies 253 nodes mostly as GB (245 GB, 4 IR, 3 DE, 1 NL).
  • 143.20.*: Chainz classifies 207 nodes mostly as PL (201 PL, 6 GB).
  • 5.180.*: Chainz classifies 204 nodes mostly as PL (193 PL, 11 DE).
  • 31.58.*: Chainz classifies 133 nodes mostly as DE (118 DE, 9 US, 3 NL, 2 IR, 1 PL).

These ranges line up closely with Sysnode’s inflated Iran / USA / Cyprus counts, suggesting the bundled GeoIP database is stale or inaccurate for the current hosting provider allocations.

Impact

The Home and Network pages can display a misleading geographic distribution. In the current data, Iran appears as the leading country on Sysnode (644 nodes / 33.1%), while Chainz shows Iran around 15 nodes / 1%.

This also creates mathematically invalid percentages because mapData is counted over a different cohort than the enabled-node denominator.

Suggested fixes

  1. Decide what the location chart is meant to represent: enabled sentry nodes only, or all registered masternodes.
  2. Make the backend mapData cohort match the denominator exposed to the frontend. If the UI says enabled nodes, only count node.status === 'ENABLED' and expose the map denominator explicitly.
  3. Replace or refresh the GeoIP source. Options:
    • update/switch from the bundled geoip-country database to a maintained GeoLite2/IP2Location-style dataset with a repeatable update process,
    • cache Chainz-style IP info if that is accepted as the comparison source,
    • expose unknown instead of forcing stale country assignments when confidence/source age is poor.
  4. Add a regression test that verifies sum(mapData[*].masternodes) <= enabled for an enabled-only map, or that the response exposes and the UI uses the correct map-specific denominator.

Safe reproduction

curl -fsSL https://sysnode.info/mnstats | node -e 'let s="";process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{const j=JSON.parse(s); const entries=Object.entries(j.mapData||{}); const mapTotal=entries.reduce((sum,[,v])=>sum+Number(v.masternodes||0),0); const enabled=Number(String(j.stats?.mn_stats?.enabled||0).replace(/,/g,"")); console.log({enabled,mapTotal,top:entries.sort((a,b)=>(b[1].masternodes||0)-(a[1].masternodes||0)).slice(0,8).map(([cc,v])=>[cc,v.masternodes])});})'
curl -fsSL 'https://chainz.cryptoid.info/explorer/masternodes.data.dws?coin=sys&mn=&fmt.js' | node -e 'let s="";process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{const rows=JSON.parse(s); const t={}; let nodes=0; for (const r of rows){ const cc=r.cc||"???"; t[cc]??={servers:0,nodes:0}; t[cc].servers++; t[cc].nodes+=Number(r.nb||0); nodes+=Number(r.nb||0);} console.log({servers:rows.length,nodes,top:Object.entries(t).sort((a,b)=>b[1].servers-a[1].servers).slice(0,12)});})'

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