Skip to content

Commit 6bf6d29

Browse files
authored
bch(g2): ratchet gate-logic SIM KAT — arms staged-migration rig-free (#577)
Fenced, additive, rig-free known-answer test for the BCH G2 staged 1-by-1 miner-migration gate. Pins the canonical 60%-by-WORK version-switch ACCEPT gate that bch::check_share enforces inline (src/impl/bch/share_check.hpp:1774): if (new_ver_weight * 100 < total_weight * 60) throw "switch without enough hash power upgraded"; via a verbatim replica of the live tail-guard (same non-circular localisation btc/DGB use; guard stays inline, no lifted SSOT). Arms the staged-migration rows without a SHA256d bitaxe rig. Properties pinned (15 asserts, all green): - 60%-by-WORK boundary at the exact crossing (59 hold / 60-100 pass / 0 hold) - mint-cannot-outrun-accept (#288): 95%-by-COUNT under heterogeneous hashrate can NOT outrun the 60%-by-work gate -> a minted V36 boundary share its peers would reject is prevented - work-weighted NOT flat-count (#290/#326 regression guard): gate diverges from a head-count in both directions; #326 dropped the 95%-flat-count punish - monotonic staged migration: 5 equal-work miners, tally advances monotone, accept gate crosses at stage 3/5 (60%) and stays on - downgrade asymmetry: V35-after-V36 (AutoRatchet deactivation) is un-gated Test-only, bch-tree-local: pure int main()+assert over <core/pack_types.hpp> uint288, no consensus / shared-base / CMake-shared surface. Registered in the bch test CMakeLists foreach and BOTH build.yml COIN_BCH --target allowlists. Co-authored-by: frstrtr <frstrtr@users.noreply.github.com>
1 parent d64157f commit 6bf6d29

3 files changed

Lines changed: 147 additions & 2 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ jobs:
333333
bch_embedded_getwork_test bch_embedded_seam_workview_test \
334334
bch_embedded_block_broadcast_test bch_coin_node_seam_test \
335335
bch_asert_kat_test \
336-
bch_g1_oracle_byte_parity_test bch_g0_canonical_pin_test \
336+
bch_g1_oracle_byte_parity_test bch_g0_canonical_pin_test bch_g2_ratchet_gate_kat_test \
337337
-j8
338338
339339
- name: Run BCH tests
@@ -420,7 +420,7 @@ jobs:
420420
bch_embedded_getwork_test bch_embedded_seam_workview_test \
421421
bch_embedded_block_broadcast_test bch_coin_node_seam_test \
422422
bch_asert_kat_test \
423-
bch_g1_oracle_byte_parity_test bch_g0_canonical_pin_test \
423+
bch_g1_oracle_byte_parity_test bch_g0_canonical_pin_test bch_g2_ratchet_gate_kat_test \
424424
-j8
425425
426426
- name: Run BCH tests under sanitizers

src/impl/bch/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ if(BUILD_TESTING)
2222
asert_kat_test # M5: ASERT DAA known-answer vs BCHN @89a591f gold properties + mainnet anchor (pure)
2323
g1_oracle_byte_parity_test # G1: oracle byte-parity vs p2pool-merged-v36 bitcoincash[_testnet].py (pure)
2424
g0_canonical_pin_test # G0: canonical-pin vs p2poolBCH @6603b79 baseline + v35->v36 transition rule (pure)
25+
g2_ratchet_gate_kat_test # G2: 60%-by-WORK version-switch accept gate + mint-cannot-outrun-accept (pure)
2526
)
2627
foreach(t IN LISTS BCH_ABLA_TESTS)
2728
add_executable(bch_${t} ${t}.cpp)
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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

Comments
 (0)