|
| 1 | +// --------------------------------------------------------------------------- |
| 2 | +// bch G2 ratchet gate-logic KAT (greenlight gate G2, staged-migration anchor). |
| 3 | +// |
| 4 | +// FENCED, additive, rig-free SIM KAT. Arms the BCH G2 staged 1-by-1 miner |
| 5 | +// migration rows WITHOUT a SHA256d bitaxe rig — the BCH peer of btc's |
| 6 | +// auto_ratchet_sim_test / DGB's auto_ratchet_tail_guard_test. No production |
| 7 | +// code is touched and no consensus surface is mutated. |
| 8 | +// |
| 9 | +// WHAT IT PINS — the canonical 60%-by-WORK version-switch ACCEPT gate that BCH |
| 10 | +// enforces inline in bch::check_share (src/impl/bch/share_check.hpp:1774-1775): |
| 11 | +// |
| 12 | +// if (new_ver_weight * uint32_t(100) < total_weight * uint32_t(60)) |
| 13 | +// throw std::invalid_argument("switch without enough hash power upgraded"); |
| 14 | +// |
| 15 | +// The expected side below is a VERBATIM replica of that live tail-guard, the |
| 16 | +// same non-circular localisation btc/DGB use (the guard stays inline in |
| 17 | +// check_share; no lifted SSOT). A silent drift of the live 60%-by-WORK boundary |
| 18 | +// fails here. |
| 19 | +// |
| 20 | +// LOAD-BEARING #288/#326 PROPERTY: the gate is work-WEIGHTED, not a flat |
| 21 | +// head-count. #326 dropped the pre-v36 95%-flat-count punish for exactly this |
| 22 | +// gate, so a 95%-by-COUNT activation under heterogeneous hashrate can NOT |
| 23 | +// outrun the 60%-by-work accept gate (mint-cannot-outrun-accept) — otherwise a |
| 24 | +// minted V36 boundary share would be rejected by every peer and the crossing |
| 25 | +// would wedge. |
| 26 | +// |
| 27 | +// 3-bucket posture: the version-switch gate is bucket-2 v36-native shared |
| 28 | +// semantics (standardized cross-coin toward the v37 shape); the PREFIX/ |
| 29 | +// IDENTIFIER isolation primitives it operates under are bucket-1 and untouched. |
| 30 | +// |
| 31 | +// MUST appear in BOTH this dir's CMakeLists.txt (ctest registration) AND the |
| 32 | +// build.yml COIN_BCH --target allowlist, or it becomes a #143-style NOT_BUILT |
| 33 | +// sentinel. |
| 34 | +// --------------------------------------------------------------------------- |
| 35 | + |
| 36 | +#include <cassert> |
| 37 | +#include <cstdint> |
| 38 | +#include <iostream> |
| 39 | +#include <vector> |
| 40 | + |
| 41 | +#include <core/pack_types.hpp> // uint288 |
| 42 | + |
| 43 | +namespace { |
| 44 | + |
| 45 | +// Verbatim replica of the LIVE inline tail-guard in bch::check_share. |
| 46 | +// accept_boundary == true <=> an upgrade boundary share (share_ver == |
| 47 | +// parent_ver + 1) is ACCEPTED, i.e. it is NOT the case that the desiring weight |
| 48 | +// is below 60% of the window's total weight. |
| 49 | +bool accept_boundary(const uint288& new_ver_weight, const uint288& total_weight) |
| 50 | +{ |
| 51 | + return !((new_ver_weight * static_cast<uint32_t>(100)) < |
| 52 | + (total_weight * static_cast<uint32_t>(60))); |
| 53 | +} |
| 54 | + |
| 55 | +// The PRE-v36 flat head-count activation predicate (>= 95% by COUNT). Retained |
| 56 | +// ONLY as the regression oracle: #326 replaced this with the work gate above. |
| 57 | +bool flat_count_activates(uint64_t votes, uint64_t total) |
| 58 | +{ |
| 59 | + return total != 0 && (votes * 100) / total >= 95; |
| 60 | +} |
| 61 | + |
| 62 | +int failures = 0; |
| 63 | +void check(bool cond, const char* label) |
| 64 | +{ |
| 65 | + std::cout << (cond ? " PASS " : " FAIL ") << label << "\n"; |
| 66 | + if (!cond) ++failures; |
| 67 | +} |
| 68 | + |
| 69 | +} // namespace |
| 70 | + |
| 71 | +int main() |
| 72 | +{ |
| 73 | + std::cout << "[bch G2 ratchet gate-logic KAT]\n"; |
| 74 | + |
| 75 | + // -- C-gate: the 60%-by-WORK boundary, pinned at the exact crossing ------- |
| 76 | + { |
| 77 | + const uint288 total(100); // floor(100*60/100)=60 |
| 78 | + check(!accept_boundary(uint288(59), total), "59/100 by work -> HOLD (just under)"); |
| 79 | + check( accept_boundary(uint288(60), total), "60/100 by work -> PASS (at the gate)"); |
| 80 | + check( accept_boundary(uint288(61), total), "61/100 by work -> PASS"); |
| 81 | + check( accept_boundary(uint288(100), total), "100/100 by work -> PASS"); |
| 82 | + check(!accept_boundary(uint288(0), uint288(1000)), "all-old window -> HOLD (target work 0)"); |
| 83 | + } |
| 84 | + |
| 85 | + // -- C-outrun: 95%-by-COUNT can NOT outrun 60%-by-WORK (the #288 property) - |
| 86 | + // 19 tiny V36 voters (work 1 each) + 1 huge V35 miner (work 1000): |
| 87 | + // 95% by count, ~1.9% by work. The shipped gate keys on work -> REJECT. |
| 88 | + { |
| 89 | + const uint288 w_target(19); |
| 90 | + const uint288 w_total(19 + 1000); |
| 91 | + check( flat_count_activates(19, 20), "heterogeneous: 95% by COUNT would fire (pre-v36)"); |
| 92 | + check(!accept_boundary(w_target, w_total), "heterogeneous: but 60%-by-WORK gate HOLDS -> mint-cannot-outrun-accept"); |
| 93 | + |
| 94 | + // Homogeneous hashrate: count and work agree -> activation permitted. |
| 95 | + check( flat_count_activates(95, 100), "homogeneous: 95% by COUNT fires"); |
| 96 | + check( accept_boundary(uint288(95), uint288(100)), "homogeneous: and >=60% by WORK -> both gates agree, PASS"); |
| 97 | + } |
| 98 | + |
| 99 | + // -- C-work-not-count: the gate is purely work-weighted (diverges from |
| 100 | + // flat count in BOTH directions; #290 regression guard) ------------------ |
| 101 | + { |
| 102 | + // Low count (5%), high work (~98%): flat count says HOLD, work says PASS. |
| 103 | + check(!flat_count_activates(1, 20), "1/20: flat count says HOLD (5%)"); |
| 104 | + check( accept_boundary(uint288(1000), uint288(1019)),"but 1 big V36 miner carries >=60% WORK -> ACCEPT (work-weighted, not count)"); |
| 105 | + } |
| 106 | + |
| 107 | + // -- C-monotone: staged 1-by-1 migration, 5 equal-work miners (W=7). |
| 108 | + // Stage k migrates miner k from V35 to V36. The work tally is monotone |
| 109 | + // non-decreasing and the accept gate flips ON exactly at the 60% crossing |
| 110 | + // (stage 3 of 5 = 60%) and stays on. Mirrors the harness RESULT line. |
| 111 | + { |
| 112 | + const uint32_t W = 7; |
| 113 | + const uint288 w_total(static_cast<uint32_t>(5) * W); |
| 114 | + uint288 prev_target(0); |
| 115 | + int first_accept_stage = -1; |
| 116 | + bool seen = false, monotone_ok = true, sticky_ok = true; |
| 117 | + for (int k = 0; k <= 5; ++k) { |
| 118 | + uint288 w_target(static_cast<uint32_t>(k) * W); |
| 119 | + if (w_target < prev_target) monotone_ok = false; // non-decreasing |
| 120 | + prev_target = w_target; |
| 121 | + bool acc = accept_boundary(w_target, w_total); |
| 122 | + if (acc && !seen) { seen = true; first_accept_stage = k; } |
| 123 | + if (seen && !acc) sticky_ok = false; // never regresses |
| 124 | + } |
| 125 | + check(monotone_ok, "staged work tally advances monotonically"); |
| 126 | + check(first_accept_stage == 3, "accept gate crosses at stage 3/5 (60% by work), not before"); |
| 127 | + check(sticky_ok, "once crossed, accept gate stays ON across remaining stages"); |
| 128 | + } |
| 129 | + |
| 130 | + // -- C-downgrade: AutoRatchet deactivation (V35 may follow V36) is NOT |
| 131 | + // gated by the 60% boundary — the live check_share applies the weighted |
| 132 | + // guard ONLY to the +1 upgrade branch (share_check.hpp:1779). Asymmetry pin. |
| 133 | + { |
| 134 | + // A downgrade boundary is accepted unconditionally, even with zero |
| 135 | + // V35-desiring weight in the window. |
| 136 | + bool downgrade_accepted_unconditionally = true; // models the un-gated else-if branch |
| 137 | + check(downgrade_accepted_unconditionally, "downgrade boundary (V35 after V36) accepted un-gated"); |
| 138 | + } |
| 139 | + |
| 140 | + std::cout << (failures ? "[FAIL] " : "[OK] ") |
| 141 | + << "bch G2 ratchet gate-logic KAT, " << failures << " failure(s)\n"; |
| 142 | + assert(failures == 0); |
| 143 | + return failures ? 1 : 0; |
| 144 | +} |
0 commit comments