From b2c00b866a57dee253f7d129793f798a45bba086 Mon Sep 17 00:00:00 2001 From: frstrtr Date: Fri, 26 Jun 2026 05:59:54 +0000 Subject: [PATCH] test(web): pin charter-#3 crossing-banner de-allowlist in regression KAT (SAFE-ADDITIVE) #496 replaced the hardcoded {LITECOIN,DOGECOIN} version_signaling allowlist with a single DASH (static v16) exclusion so the V35->V36 crossing banner surfaces on every v36-ratcheting coin. Pin that behaviour: - BTC + DGB (coins the old allowlist would have suppressed) surface the live crossing state and vote tally from the sharechain stats hook. - DASH stays suppressed (empty) even with full vote data wired -- no real V35->V36 transition exists on a static-v16 chain. - a ratcheting coin with <10 shares returns empty until there is real crossing state, never a misleading transition. Test-only, additive: 13 -> 17 cases, full suite green. --- test/test_web_honesty_regression.cpp | 81 ++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/test/test_web_honesty_regression.cpp b/test/test_web_honesty_regression.cpp index e642154da..3251dfbbf 100644 --- a/test/test_web_honesty_regression.cpp +++ b/test/test_web_honesty_regression.cpp @@ -316,3 +316,84 @@ TEST(WebHonestyRegression, V36StatusActiveLatchTracksLiveShareVersion) { EXPECT_TRUE(active["auto_ratchet"]["v36_active"].get()) << "v36_active must latch true once the live ratchet reaches V36"; } + +// --- charter #3: crossing-banner coin coverage (de-allowlist, #496) ---------- +// The V35->V36 crossing banner must surface on EVERY v36-ratcheting coin, not +// just LTC/DOGE. Before #496 a hardcoded {LITECOIN,DOGECOIN} allowlist gated +// rest_version_signaling(), so a BTC/DGB/BCH node mid-cross showed NO crossing +// banner -- the dashboard hid the very state the operator most needs to see +// during the upgrade. #496 replaced the allowlist with a single DASH (static +// v16, non-ratcheting) exclusion: every other coin derives the banner from real +// vote data and falls through to an empty result only when there is no real +// crossing state yet. These pins lock that in. + +// A coin the OLD allowlist would have SUPPRESSED (BTC) must still surface the +// live crossing state from real vote data. +TEST(WebHonestyRegression, VersionSignalingSurfacesOnRatchetingBtcNotAllowlistSuppressed) { + MiningInterface mi(/*testnet=*/true, /*node=*/nullptr, Blockchain::BITCOIN); + + // 100 shares: still producing V35, but 70% are VOTING V36. + mi.set_sharechain_stats_fn([] { + return json{ + {"total_shares", 100}, + {"chain_height", 8640}, + {"chain_length", 8640}, + {"shares_by_version", {{"35", 100}}}, + {"shares_by_desired_version", {{"35", 30}, {"36", 70}}}, + }; + }); + + json r = mi.rest_version_signaling(); + ASSERT_FALSE(r.empty()) + << "BTC is v36-ratcheting -- the pre-#496 {LTC,DOGE} allowlist must no " + "longer suppress its crossing banner"; + EXPECT_EQ(r["target_version"].get(), 36); + EXPECT_EQ(r["overall_v36_votes"].get(), 70) + << "vote tally must come from the live sharechain stats hook"; + EXPECT_DOUBLE_EQ(r["overall_v36_vote_pct"].get(), 70.0); +} + +// DGB (scrypt, also v36-ratcheting) is likewise no longer suppressed. +TEST(WebHonestyRegression, VersionSignalingSurfacesOnRatchetingDgb) { + MiningInterface mi(/*testnet=*/true, /*node=*/nullptr, Blockchain::DIGIBYTE); + mi.set_sharechain_stats_fn([] { + return json{ + {"total_shares", 50}, + {"shares_by_version", {{"35", 50}}}, + {"shares_by_desired_version", {{"36", 50}}}, + }; + }); + json r = mi.rest_version_signaling(); + ASSERT_FALSE(r.empty()) << "DGB is v36-ratcheting -- banner must not be suppressed"; + EXPECT_EQ(r["target_version"].get(), 36); + EXPECT_DOUBLE_EQ(r["overall_v36_vote_pct"].get(), 100.0); +} + +// DASH is non-ratcheting (static v16) -- the ONE coin #496 keeps excluded. Even +// with full vote data wired, its crossing banner stays hidden: surfacing a +// V35->V36 transition on a chain that has no such transition would be a lie. +TEST(WebHonestyRegression, VersionSignalingSuppressedOnNonRatchetingDash) { + MiningInterface mi(/*testnet=*/true, /*node=*/nullptr, Blockchain::DASH); + mi.set_sharechain_stats_fn([] { + return json{ + {"total_shares", 100}, + {"shares_by_version", {{"35", 100}}}, + {"shares_by_desired_version", {{"36", 80}}}, + }; + }); + EXPECT_TRUE(mi.rest_version_signaling().empty()) + << "Dash is static v16 (non-ratcheting) -- no V35->V36 crossing exists, so " + "the banner must stay hidden regardless of wired vote data"; +} + +// Honest-empty: a ratcheting coin with too little chain (<10 shares) has no real +// crossing state yet -- the banner stays hidden until there IS truth to show, +// rather than rendering a misleading transition. +TEST(WebHonestyRegression, VersionSignalingEmptyUntilRealCrossingState) { + MiningInterface mi(/*testnet=*/true, /*node=*/nullptr, Blockchain::BITCOIN); + mi.set_sharechain_stats_fn([] { + return json{{"total_shares", 5}, {"shares_by_desired_version", {{"36", 5}}}}; + }); + EXPECT_TRUE(mi.rest_version_signaling().empty()) + << "fewer than 10 shares -- no real crossing state, so no banner (honest)"; +}