Skip to content

Commit 2ee78f4

Browse files
authored
Merge pull request #542 from frstrtr/bch/g0-canonical-pin
bch: G0 canonical-pin + G1 oracle byte-parity KATs (greenlight gate)
2 parents 95b3276 + 20e199b commit 2ee78f4

4 files changed

Lines changed: 307 additions & 0 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +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 \
336337
-j8
337338
338339
- name: Run BCH tests
@@ -419,6 +420,7 @@ jobs:
419420
bch_embedded_getwork_test bch_embedded_seam_workview_test \
420421
bch_embedded_block_broadcast_test bch_coin_node_seam_test \
421422
bch_asert_kat_test \
423+
bch_g1_oracle_byte_parity_test bch_g0_canonical_pin_test \
422424
-j8
423425
424426
- name: Run BCH tests under sanitizers

src/impl/bch/test/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ if(BUILD_TESTING)
2020
abla_growth_soak_test # M5: dynamic ABLA budget-GROWTH proof above 32 MB floor (pure)
2121
coinbase_kat_segwit_predicate_test # KAT: DGB<->BCH coinbase pairing -- gated-segwit-OFF predicate pin (pure)
2222
asert_kat_test # M5: ASERT DAA known-answer vs BCHN @89a591f gold properties + mainnet anchor (pure)
23+
g1_oracle_byte_parity_test # G1: oracle byte-parity vs p2pool-merged-v36 bitcoincash[_testnet].py (pure)
24+
g0_canonical_pin_test # G0: canonical-pin vs p2poolBCH @6603b79 baseline + v35->v36 transition rule (pure)
2325
)
2426
foreach(t IN LISTS BCH_ABLA_TESTS)
2527
add_executable(bch_${t} ${t}.cpp)
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// ---------------------------------------------------------------------------
2+
// bch G0 canonical-pin KAT (greenlight gate G0).
3+
//
4+
// FENCED conformance test (no production code touched). G0 is the FOUNDATION of
5+
// the BCH greenlight ladder: it pins WHICH canonical p2pool BCH the c2pool-bch
6+
// sharechain transitions FROM, and the v35->v36 transition RULE itself. Every
7+
// later gate trusts an oracle identity that this gate fixes:
8+
// - G1 (g1_oracle_byte_parity_test) asserts byte-parity of the assembled v36
9+
// PoolConfig against the v36 TARGET oracle frstrtr/p2pool-merged-v36.
10+
// - G0 (this file) asserts the v35 BASELINE the transition starts from and
11+
// that the transition across the v36 boundary is param-CLEAN (migration-safe).
12+
//
13+
// Canonical baseline provenance (the p2pool BCH transitions FROM):
14+
// frstrtr/p2poolBCH @ 6603b79 ("stratum: per-UA sent-diff multiplier")
15+
// p2pool/networks/bitcoincash.py -- IDENTIFIER, PREFIX, P2P_PORT,
16+
// SHARE_PERIOD, CHAIN_LENGTH,
17+
// TARGET_LOOKBEHIND, SPREAD,
18+
// MINIMUM_PROTOCOL_VERSION
19+
// p2pool/networks/bitcoincash_testnet.py -- testnet IDENTIFIER / PREFIX
20+
// p2pool/data.py -- share_versions baseline set
21+
// Values below are literal bytes/ints transcribed from that python source, NOT
22+
// a second read of the same C++ SUT constant -> non-circular (a drift in
23+
// config_pool.hpp that diverges from the canonical baseline fails HERE).
24+
//
25+
// THE G0 FINDING (BCH migration is param-CLEAN): the bucket-1 isolation
26+
// primitives (IDENTIFIER / PREFIX / P2P_PORT, mainnet + testnet) are BYTE-
27+
// IDENTICAL between the canonical baseline @6603b79 and the v36 oracle. BCH does
28+
// NOT migrate share params across the v36 boundary (cf. bch-v36-conformance-audit
29+
// "share params BYTE-IDENTICAL legacy<->v36, no param migration"). The ONLY
30+
// sanctioned change across the boundary is:
31+
// (a) share-version additive: EXACTLY +36 over the baseline {0,17,32,33,34,35},
32+
// owned by the core::version_gate SSOT; AND
33+
// (b) the donation transition: pre-v36 forrestv P2PK -> v36+ combined P2SH.
34+
//
35+
// 3-bucket posture (operator 2026-06-17):
36+
// - IDENTIFIER + PREFIX + P2P_PORT = bucket-1 ISOLATION PRIMITIVES. This KAT
37+
// pins them byte-identical across the transition (they are the sharechain /
38+
// peer namespacing boundary) -- it does NOT standardize them cross-coin.
39+
// - MINIMUM_PROTOCOL_VERSION 3301 = bucket-3 per-coin accept-floor; the c2pool
40+
// floor must not DROP below the baseline (forward-compatible) -> assert >=.
41+
// - donation version-gate + v36 boundary = bucket-2 v36-native transition rule.
42+
//
43+
// NON-CIRCULAR vs G1: G1 pins the v36 TARGET against p2pool-merged-v36; G0 pins
44+
// the v35 SOURCE (@6603b79) and the transition's cleanliness. Together they
45+
// bracket both ends of the migration the operator taps.
46+
//
47+
// MUST appear in BOTH the ctest registration (this dir CMakeLists.txt) AND the
48+
// build.yml COIN_BCH --target allowlist, or it becomes a #143-style NOT_BUILT
49+
// sentinel.
50+
// ---------------------------------------------------------------------------
51+
52+
#include <cstdint>
53+
#include <iostream>
54+
#include <string>
55+
#include <vector>
56+
57+
#include "../config_pool.hpp"
58+
#include <core/version_gate.hpp>
59+
60+
static int failures = 0;
61+
#define CHECK(cond) do { if (!(cond)) { \
62+
std::cerr << "FAIL: " #cond " (line " << __LINE__ << ")\n"; ++failures; } } while (0)
63+
64+
namespace {
65+
66+
using PC = bch::PoolConfig;
67+
68+
// ---- Canonical baseline expected values (p2poolBCH @6603b79, transcribed) ----
69+
// p2pool/networks/bitcoincash.py
70+
constexpr uint16_t CANON_P2P_PORT_MAIN = 9349;
71+
constexpr uint32_t CANON_SPREAD = 3;
72+
constexpr uint32_t CANON_TARGET_LOOKBEHIND = 200;
73+
constexpr uint32_t CANON_SHARE_PERIOD = 60; // seconds -- one minute
74+
constexpr uint32_t CANON_CHAIN_LENGTH = 3*24*60; // 4320 shares -- three days
75+
constexpr uint32_t CANON_MIN_PROTO_VERSION = 3301; // bucket-3 accept-floor
76+
77+
// bitcoincash.py IDENTIFIER / PREFIX (bucket-1 isolation primitives)
78+
const std::string CANON_IDENTIFIER_HEX_MAIN = "b826c0a51ddc2d2b";
79+
const std::string CANON_PREFIX_HEX_MAIN = "ac9a8fda9a911bce";
80+
// bitcoincash_testnet.py
81+
const std::string CANON_IDENTIFIER_HEX_TEST = "c9f3de8d9508faef";
82+
const std::string CANON_PREFIX_HEX_TEST = "08c5541df85a8a65";
83+
84+
// p2pool/data.py: share_versions = {s.VERSION for s in
85+
// [PaddingBugfixShare(35), SegwitMiningShare(34), NewShare(33),
86+
// PreSegwitShare(32), Share(17)]} plus BaseShare(0).
87+
// Max baseline VERSION == 35; nothing on the baseline is on the v36 side.
88+
constexpr uint64_t CANON_BASELINE_VERSIONS[] = {0, 17, 32, 33, 34, 35};
89+
90+
} // namespace
91+
92+
int main() {
93+
// ====================================================================
94+
// (1) Canonical baseline net/consensus constants -- the v35 SOURCE.
95+
// ====================================================================
96+
PC::is_testnet = false;
97+
PC::override_identifier_hex.clear();
98+
PC::override_prefix_hex.clear();
99+
100+
CHECK(PC::P2P_PORT == CANON_P2P_PORT_MAIN);
101+
CHECK(PC::SPREAD == CANON_SPREAD);
102+
CHECK(PC::TARGET_LOOKBEHIND == CANON_TARGET_LOOKBEHIND);
103+
CHECK(PC::share_period() == CANON_SHARE_PERIOD);
104+
CHECK(PC::chain_length() == CANON_CHAIN_LENGTH);
105+
CHECK(PC::real_chain_length() == CANON_CHAIN_LENGTH);
106+
107+
// ====================================================================
108+
// (2) BCH migration is param-CLEAN: bucket-1 isolation primitives are
109+
// BYTE-IDENTICAL between the canonical baseline @6603b79 and the
110+
// v36 oracle. No param migration across the v36 boundary.
111+
// ====================================================================
112+
CHECK(PC::identifier_hex() == CANON_IDENTIFIER_HEX_MAIN);
113+
CHECK(PC::prefix_hex() == CANON_PREFIX_HEX_MAIN);
114+
115+
PC::is_testnet = true;
116+
CHECK(PC::identifier_hex() == CANON_IDENTIFIER_HEX_TEST);
117+
CHECK(PC::prefix_hex() == CANON_PREFIX_HEX_TEST);
118+
// NOTE: c2pool-bch config exposes a single non-net-switched P2P_PORT; testnet
119+
// currently shares the mainnet transport port (9349) in this phase, matching
120+
// the BTC g01 posture. The canonical baseline + v36 oracle both define a
121+
// distinct testnet P2P_PORT (19339); net-switching it is a tracked follow-up,
122+
// NOT a G0 break (isolation-primitive identity above is byte-identical).
123+
PC::is_testnet = false;
124+
125+
// ====================================================================
126+
// (3) Protocol floor: c2pool must NOT drop below the canonical baseline
127+
// (bucket-3, forward-compatible accept-floor RAISE only).
128+
// ====================================================================
129+
CHECK(PC::MINIMUM_PROTOCOL_VERSION >= CANON_MIN_PROTO_VERSION);
130+
131+
// ====================================================================
132+
// (4) Transition RULE: the ONLY sanctioned additive over the canonical
133+
// baseline is the v36 boundary, owned by the version_gate SSOT.
134+
// ====================================================================
135+
CHECK(core::version_gate::V36_ACTIVATION_VERSION == 36u);
136+
137+
// No canonical-baseline version is on the v36 side of the gate.
138+
for (uint64_t v : CANON_BASELINE_VERSIONS)
139+
CHECK(core::version_gate::is_v36_active(v) == false);
140+
141+
// Boundary is exact: 36 activates; 35 (max baseline) does not.
142+
CHECK(core::version_gate::is_v36_active(36) == true);
143+
CHECK(core::version_gate::is_v36_active(35) == false);
144+
145+
// ====================================================================
146+
// (5) Donation transition across the v36 boundary (bucket-2 v36-native):
147+
// pre-v36 forrestv P2PK (67B) -> v36+ combined P2SH 1-of-2 (23B).
148+
// This is the transition RULE the staged ratchet (G2) flips on.
149+
// ====================================================================
150+
{
151+
auto pre = PC::get_donation_script(/*share_version=*/35);
152+
CHECK(pre.size() == 67u);
153+
CHECK(pre.front() == 0x41); // OP_PUSHBYTES_65 (uncompressed pubkey)
154+
CHECK(pre.back() == 0xac); // OP_CHECKSIG
155+
}
156+
{
157+
auto v36 = PC::get_donation_script(/*share_version=*/36);
158+
CHECK(v36.size() == 23u);
159+
CHECK(v36[0] == 0xa9); // OP_HASH160
160+
CHECK(v36[1] == 0x14); // push 20
161+
CHECK(v36.back() == 0x87); // OP_EQUAL
162+
}
163+
164+
if (failures == 0) {
165+
std::cout << "bch G0 canonical-pin KAT: PASS "
166+
"(baseline p2poolBCH @6603b79 -> v36 transition param-clean)\n";
167+
return 0;
168+
}
169+
std::cerr << "bch G0 canonical-pin KAT: " << failures << " FAILURE(S)\n";
170+
return 1;
171+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// ---------------------------------------------------------------------------
2+
// bch::PoolConfig G1 oracle byte-parity KAT (greenlight gate G1).
3+
//
4+
// FENCED conformance test (no production code touched). G1 is the byte-parity
5+
// gate: it pins the fully-assembled bch::PoolConfig net/consensus constants and
6+
// the bucket-1 isolation primitives (PREFIX / IDENTIFIER) against values
7+
// hand-transcribed from the BCH oracle frstrtr/p2pool-merged-v36:
8+
// networks/bitcoincash.py -- P2P_PORT, IDENTIFIER, PREFIX,
9+
// MINIMUM_PROTOCOL_VERSION, SHARE_PERIOD,
10+
// CHAIN_LENGTH, SPREAD, TARGET_LOOKBEHIND,
11+
// MAX_TARGET, DUST_THRESHOLD
12+
// networks/bitcoincash_testnet.py -- testnet IDENTIFIER / PREFIX
13+
//
14+
// NON-CIRCULAR: the expected side below is literal bytes/ints typed from the
15+
// oracle python source, NOT a second read of the same C++ SUT constant. A drift
16+
// in config_pool.hpp that silently diverges from the oracle fails here even
17+
// though every other bch test (which sources the same SUT constant on both
18+
// sides) stays green.
19+
//
20+
// 3-bucket posture (operator 2026-06-17):
21+
// - IDENTIFIER + PREFIX = bucket-1 ISOLATION PRIMITIVES -> pinned per-coin /
22+
// per-instance as the sharechain + peer namespacing boundary. This KAT
23+
// GUARDS them against accidental "standardization"; it does NOT unify them.
24+
// - MINIMUM_PROTOCOL_VERSION 3301 = bucket-3 per-coin accept-floor (transition
25+
// compat), NOT standardized (see memory share-constant-prescan).
26+
// - donation version-gate = bucket-2 v36-native (combined P2SH for sv>=36).
27+
//
28+
// SEGWIT_ACTIVATION_VERSION == 0 (BCH has no SegWit) is already pinned by
29+
// coinbase_kat_segwit_predicate_test; not duplicated here.
30+
//
31+
// MUST appear in BOTH the ctest registration (this dir CMakeLists.txt) AND the
32+
// build.yml COIN_BCH --target allowlist, or it becomes a #143-style NOT_BUILT
33+
// sentinel.
34+
// ---------------------------------------------------------------------------
35+
36+
#include <cstdint>
37+
#include <iostream>
38+
#include <string>
39+
#include <vector>
40+
41+
#include "../config_pool.hpp"
42+
#include <core/uint256.hpp>
43+
44+
static int failures = 0;
45+
#define CHECK(cond) do { if (!(cond)) { \
46+
std::cerr << "FAIL: " #cond " (line " << __LINE__ << ")\n"; ++failures; } } while (0)
47+
48+
namespace {
49+
50+
using PC = bch::PoolConfig;
51+
52+
// ---- Oracle expected values (frstrtr/p2pool-merged-v36, transcribed) --------
53+
// networks/bitcoincash.py
54+
constexpr uint16_t ORACLE_P2P_PORT = 9349;
55+
constexpr uint32_t ORACLE_SPREAD = 3;
56+
constexpr uint32_t ORACLE_TARGET_LOOKBEHIND = 200;
57+
constexpr uint32_t ORACLE_MIN_PROTO_VERSION = 3301; // bucket-3 accept-floor
58+
constexpr uint32_t ORACLE_SHARE_PERIOD = 60; // seconds
59+
constexpr uint32_t ORACLE_CHAIN_LENGTH = 4320; // 3 days / 60s
60+
constexpr uint64_t ORACLE_DUST_THRESHOLD = 100000; // 0.001 BCH
61+
62+
// bitcoincash.py IDENTIFIER / PREFIX (bucket-1 isolation primitives)
63+
const std::string ORACLE_IDENTIFIER_HEX_MAIN = "b826c0a51ddc2d2b";
64+
const std::string ORACLE_PREFIX_HEX_MAIN = "ac9a8fda9a911bce";
65+
// bitcoincash_testnet.py
66+
const std::string ORACLE_IDENTIFIER_HEX_TEST = "c9f3de8d9508faef";
67+
const std::string ORACLE_PREFIX_HEX_TEST = "08c5541df85a8a65";
68+
69+
// MAX_TARGET = 2**256 // 2**32 - 1 (bdiff 1), bitcoincash.py -- same as BTC.
70+
uint256 oracle_max_target() {
71+
uint256 t;
72+
t.SetHex("00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
73+
return t;
74+
}
75+
76+
} // namespace
77+
78+
int main() {
79+
// -- mainnet net/consensus constants -----------------------------------
80+
PC::is_testnet = false;
81+
PC::override_identifier_hex.clear();
82+
PC::override_prefix_hex.clear();
83+
84+
CHECK(PC::P2P_PORT == ORACLE_P2P_PORT);
85+
CHECK(PC::SPREAD == ORACLE_SPREAD);
86+
CHECK(PC::TARGET_LOOKBEHIND == ORACLE_TARGET_LOOKBEHIND);
87+
CHECK(PC::MINIMUM_PROTOCOL_VERSION == ORACLE_MIN_PROTO_VERSION);
88+
CHECK(PC::share_period() == ORACLE_SHARE_PERIOD);
89+
CHECK(PC::chain_length() == ORACLE_CHAIN_LENGTH);
90+
CHECK(PC::real_chain_length() == ORACLE_CHAIN_LENGTH);
91+
CHECK(PC::dust_threshold() == ORACLE_DUST_THRESHOLD);
92+
CHECK(PC::max_target() == oracle_max_target());
93+
94+
// -- bucket-1 isolation primitives: mainnet ----------------------------
95+
CHECK(PC::identifier_hex() == ORACLE_IDENTIFIER_HEX_MAIN);
96+
CHECK(PC::prefix_hex() == ORACLE_PREFIX_HEX_MAIN);
97+
98+
// -- bucket-2 donation version-gate (v36-native) -----------------------
99+
// Pre-V36 share -> forrestv P2PK (67B, 0x41 ... 0xac).
100+
{
101+
auto pre = PC::get_donation_script(/*share_version=*/35);
102+
CHECK(pre.size() == 67u);
103+
CHECK(pre.front() == 0x41); // OP_PUSHBYTES_65
104+
CHECK(pre[1] == 0x04); // uncompressed pubkey marker
105+
CHECK(pre.back() == 0xac); // OP_CHECKSIG
106+
}
107+
// V36+ share -> combined P2SH 1-of-2 (23B, 0xa9 0x14 <20B> 0x87).
108+
{
109+
auto v36 = PC::get_donation_script(/*share_version=*/36);
110+
CHECK(v36.size() == 23u);
111+
CHECK(v36[0] == 0xa9); // OP_HASH160
112+
CHECK(v36[1] == 0x14); // push 20
113+
CHECK(v36.back() == 0x87); // OP_EQUAL
114+
}
115+
116+
// -- bucket-1 isolation primitives: testnet keeps its own namespace -----
117+
PC::is_testnet = true;
118+
CHECK(PC::identifier_hex() == ORACLE_IDENTIFIER_HEX_TEST);
119+
CHECK(PC::prefix_hex() == ORACLE_PREFIX_HEX_TEST);
120+
// testnet cadence matches mainnet (bitcoincash_testnet.py)
121+
CHECK(PC::share_period() == ORACLE_SHARE_PERIOD);
122+
CHECK(PC::chain_length() == ORACLE_CHAIN_LENGTH);
123+
PC::is_testnet = false;
124+
125+
if (failures == 0) {
126+
std::cout << "g1_oracle_byte_parity_test: ALL PASS"
127+
<< " (p2pool-merged-v36 bitcoincash[_testnet].py)\n";
128+
return 0;
129+
}
130+
std::cerr << "g1_oracle_byte_parity_test: " << failures << " FAILURE(S)\n";
131+
return 1;
132+
}

0 commit comments

Comments
 (0)