Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ jobs:
bch_embedded_getwork_test bch_embedded_seam_workview_test \
bch_embedded_block_broadcast_test bch_coin_node_seam_test \
bch_asert_kat_test \
bch_g1_oracle_byte_parity_test bch_g0_canonical_pin_test bch_g2_ratchet_gate_kat_test \
bch_g1_oracle_byte_parity_test bch_g1_share_serialization_parity_test bch_g0_canonical_pin_test bch_g2_ratchet_gate_kat_test \
-j8

- name: Run BCH tests
Expand Down Expand Up @@ -460,7 +460,7 @@ jobs:
bch_embedded_getwork_test bch_embedded_seam_workview_test \
bch_embedded_block_broadcast_test bch_coin_node_seam_test \
bch_asert_kat_test \
bch_g1_oracle_byte_parity_test bch_g0_canonical_pin_test bch_g2_ratchet_gate_kat_test \
bch_g1_oracle_byte_parity_test bch_g1_share_serialization_parity_test bch_g0_canonical_pin_test bch_g2_ratchet_gate_kat_test \
-j8

- name: Run BCH tests under sanitizers
Expand Down
11 changes: 11 additions & 0 deletions src/impl/bch/config_pool.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ class PoolConfig : protected core::Fileconfig
static constexpr uint32_t MINIMUM_PROTOCOL_VERSION = 3301;
// Our capability: V36 shares (matches LTC/DOGE advertised version).
static constexpr uint32_t ADVERTISED_PROTOCOL_VERSION = 3600;
// -----------------------------------------------------------------------
// CROSSING-FLOOR SHARE VERSION (v36 HYBRID accept-both floor, SSOT).
// The exact share version the LIVE jtoomim-BCH sharechain currently mints.
// BCH is a CROSSING coin (not greenfield): shares author at the ratchet-
// current tip version, whose cold-start / empty-chain floor is THIS value.
// author, ref_hash, donation and the gentx-rebuild ALL feed from here so
// the miner-hashed template and the verify-side rebuild read ONE version
// -- never a scattered 35/36 literal (that inconsistency was the recompute/
// GENTX byte-offset-104 root). The 60%-by-work auto-ratchet (#326/#577)
// advances the tip past this floor to 36; bumping this to 36 forks the net.
static constexpr int64_t CROSSING_FLOOR_VERSION = 35;
// NOTE: NO SEGWIT_ACTIVATION_VERSION on BCH -- SegWit was rejected at the
// Aug-2017 fork. (BTC carries one; intentionally absent here.)
static constexpr uint32_t BLOCK_MAX_SIZE = 32000000; // 32 MB (BCH)
Expand Down
24 changes: 15 additions & 9 deletions src/impl/bch/pool_entrypoint.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,16 @@ inline void standup_pool_run(boost::asio::io_context& ioc,
work_source->set_best_share_hash_fn(
[&node]() -> uint256 { return node.best_share_hash(); });

// Initial donation matches the cold-start create version (35 -> P2PK). A
// ratchet-driven refresh to the COMBINED P2SH on v36 activation is the same
// follow-up slice as pplns_fn/ref_hash_fn.
work_source->set_donation_script(PoolConfig::get_donation_script(35));
// Work-source donation MUST match the authored share version, and both must
// match the LIVE jtoomim-BCH net -- BCH is a CROSSING coin. Shares author at
// CROSSING_FLOOR_VERSION (what the live net mints), and the donation served
// in the miner-hashed template is the floor script (sub-36 -> forrestv P2PK).
// generate_share_transaction rebuilds the coinbase donation under the SAME
// authored version, so template-donation and verify-rebuild are byte-identical
// -- closing the byte-offset-104 recompute/GENTX divergence WITHOUT forking
// off the live net (authoring v36 from genesis would). The 60%-by-work auto-
// ratchet (#326/#577) advances author + donation together once the tip>=36.
work_source->set_donation_script(PoolConfig::get_donation_script(PoolConfig::CROSSING_FLOOR_VERSION));

// -- ref_hash_fn: peer-verifiable share commitment (G2 conform) --------
// Without this the local-author coinbase carries NO p2pool OP_RETURN
Expand All @@ -191,18 +197,18 @@ inline void standup_pool_run(boost::asio::io_context& ioc,
-> core::stratum::RefHashResult
{
core::stratum::RefHashResult result;
result.share_version = 35;
result.desired_version = 36;
result.share_version = PoolConfig::CROSSING_FLOOR_VERSION; // crossing-floor SSOT (ratchet-current genesis default)
result.desired_version = core::version_gate::V36_ACTIVATION_VERSION; // vote to ratchet toward v36

bch::RefHashParams p;
p.share_version = 35;
p.share_version = PoolConfig::CROSSING_FLOOR_VERSION; // crossing-floor author version (SSOT)
p.prev_share = prev_share_hash;
p.coinbase_scriptSig = scriptSig;
p.share_nonce = 0;
p.subsidy = subsidy;
p.donation = 50; // 0.5% finder fee (matches create side)
p.stale_info = 0;
p.desired_version = 36;
p.desired_version = core::version_gate::V36_ACTIVATION_VERSION; // vote to ratchet toward v36
p.has_segwit = false; // BCH: never segwit
p.timestamp = timestamp;

Expand Down Expand Up @@ -505,7 +511,7 @@ inline void standup_pool_run(boost::asio::io_context& ioc,
// reconstruction is byte-exact; fall back to the prev-tip version
// (cold start: v35 voting v36) when ref_hash_fn produced no frozen
// data (bits == 0).
int64_t create_ver = 35;
int64_t create_ver = PoolConfig::CROSSING_FLOOR_VERSION; // crossing-floor default; tip version overrides below (ratchet-current)
if (job.frozen_ref.bits != 0) {
create_ver = job.frozen_ref.share_version;
} else if (!job.prev_share_hash.IsNull() &&
Expand Down
13 changes: 13 additions & 0 deletions src/impl/bch/share_check.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,19 @@ inline HashLinkType prefix_to_hash_link_v35(
const std::vector<unsigned char>& const_ending)
{
auto v36_link = prefix_to_hash_link(prefix, const_ending);
// FAIL-LOUD (v36 verify-preimage): a V35 HashLinkType (FixedStrType(0)) has
// NO extra_data field, so any coinbase whose trailing partial SHA-256 block
// exceeds the const_ending tail (bufsize > len(const_ending)) leaves residual
// bytes a V35 hash_link cannot carry. Silently dropping them makes
// check_hash_link recompute a DIFFERENT coinbase txid than the author
// committed, so the share fails verify against a v36 peer. This is exactly
// the case the oracle's V36 hash_link (variable-length extra_data) exists
// for: such a coinbase MUST be authored at V36. Refuse to forge an
// unrepresentable V35 hash_link rather than emit a mismatching preimage.
if (!v36_link.m_extra_data.m_data.empty())
throw std::runtime_error(
"prefix_to_hash_link_v35: coinbase requires V36 hash_link "
"(extra_data non-empty; V35 FixedStrType(0) cannot represent it)");
HashLinkType result;
result.m_state = v36_link.m_state;
result.m_length = v36_link.m_length;
Expand Down
1 change: 1 addition & 0 deletions src/impl/bch/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ if(BUILD_TESTING)
coinbase_kat_segwit_predicate_test # KAT: DGB<->BCH coinbase pairing -- gated-segwit-OFF predicate pin (pure)
asert_kat_test # M5: ASERT DAA known-answer vs BCHN @89a591f gold properties + mainnet anchor (pure)
g1_oracle_byte_parity_test # G1: oracle byte-parity vs p2pool-merged-v36 bitcoincash[_testnet].py (pure)
g1_share_serialization_parity_test # G1: share-serialization byte-parity vs jtoomim oracle wire (pure)
g0_canonical_pin_test # G0: canonical-pin vs p2poolBCH @6603b79 baseline + v35->v36 transition rule (pure)
g2_ratchet_gate_kat_test # G2: 60%-by-WORK version-switch accept gate + mint-cannot-outrun-accept (pure)
coinbase_author_kat_test # KAT G2: v36 coinbase output-assembly byte-parity vs p2pool-merged-v36 oracle (pure)
Expand Down
159 changes: 159 additions & 0 deletions src/impl/bch/test/g1_share_serialization_parity_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// ---------------------------------------------------------------------------
// bch G1 share-serialization byte-parity KAT (greenlight gate G1, share layer).
//
// FENCED conformance test (no production code touched). Complements
// g1_oracle_byte_parity_test (which pins net/consensus constants): this KAT
// pins the on-the-wire SHARE serialization byte layout of the c2pool-bch SUT
// against the wire format hand-transcribed from the BCH crossing oracle
// jtoomim / frstrtr/p2pool-merged-v36 p2pool/data.py:
//
// share_type = ComposedType([('type', VarIntType()),
// ('contents', VarStrType())]) (data.py:98)
// hash_link_type = ComposedType([('state', FixedStrType(32)),
// ('extra_data', FixedStrType(0)),
// ('length', VarIntType())]) (data.py:32)
// v36_hash_link_type= ComposedType([('state', FixedStrType(32)),
// ('extra_data', VarStrType()),
// ('length', VarIntType())]) (data.py:41)
//
// NON-CIRCULAR: the expected side below is literal wire bytes typed from the
// oracle python ComposedType field order + P2Pool's VarIntType (== Bitcoin
// CompactSize), VarStrType (CompactSize-prefixed bytes) and FixedStrType(N)
// (N raw bytes, no prefix) semantics -- NOT a second read of the same C++ SUT
// serializer. A field-order or encoding drift in share_types.hpp / RawShare
// that silently diverges from the oracle fails HERE even though every other bch
// share test (which sources the same SUT serializer on both sides) stays green.
//
// PINNED LIVE SHARE VERSION / accept-both crossing floor:
// CROSSING_FLOOR_VERSION == 35 is the version the live jtoomim BCH net mints
// today; it is the SSOT feeding author/ref_hash/donation and the accept-both
// floor until the 60%-by-work auto-ratchet (#326/#577) flips to v36. Pinned
// here so a silent bump of the crossing floor (which would fork the live
// sharechain) reddens this gate.
//
// 3-bucket posture (operator 2026-06-17):
// - the share wire FORMAT is bucket-2 v36-native shared structure -> pinned
// toward the canonical jtoomim layout (this KAT).
// - CROSSING_FLOOR_VERSION is a bucket-3 transition value (accept-both floor),
// dropped after the ratchet crossing-soak; pinned here for the crossing only.
//
// MUST appear in BOTH the ctest registration (this dir CMakeLists.txt) AND the
// build.yml COIN_BCH --target allowlist, or it becomes a #143-style NOT_BUILT
// sentinel that silently never builds.
// ---------------------------------------------------------------------------

#include <cstdint>
#include <iostream>
#include <span>
#include <string>
#include <vector>

#include <core/pack.hpp>
#include <sharechain/share.hpp>
#include "../config_pool.hpp"
#include "../share_types.hpp"

static int failures = 0;
#define CHECK_EQ(label, got, want) do { \
if ((got) != (want)) { \
std::cerr << "FAIL: " << (label) << "\n want: " << (want) \
<< "\n got : " << (got) << "\n"; \
++failures; \
} \
} while (0)

static std::string to_hex(std::span<std::byte> sp)
{
static const char* d = "0123456789abcdef";
std::string s;
s.reserve(sp.size() * 2);
for (std::byte b : sp) {
auto v = static_cast<unsigned>(b);
s.push_back(d[(v >> 4) & 0xf]);
s.push_back(d[v & 0xf]);
}
return s;
}

// 32-byte state: 0x00,0x01,...,0x1f (transcribed as the oracle FixedStrType(32)
// operand). Same value reused for the v35 and v36 hash_link vectors.
static const std::string STATE_HEX =
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";

static std::string state_raw()
{
std::string s;
for (int i = 0; i < 32; ++i) s.push_back(static_cast<char>(i));
return s;
}

int main()
{
using PC = bch::PoolConfig;

// -- 0. pinned live share version (accept-both crossing floor) ------------
// Oracle: live jtoomim BCH net mints v35 today; ratchet-current SSOT.
CHECK_EQ("CROSSING_FLOOR_VERSION == 35 (live jtoomim mint / accept floor)",
(int)PC::CROSSING_FLOOR_VERSION, 35);

// -- 1. hash_link_type (v35): state(32) + FixedStr(0) empty + VarInt(len) --
// Oracle wire = <32-byte state> || <length VarInt>. extra_data FixedStr(0)
// contributes ZERO bytes. length = 300 -> CompactSize "fd2c01".
{
bch::HashLinkType hl;
hl.m_state = FixedStrType<32>(state_raw());
hl.m_length = 300;

PackStream ss;
ss << hl;
const std::string want = STATE_HEX + "fd2c01";
CHECK_EQ("v35 hash_link_type wire (state||VarInt len, no extra_data)",
to_hex(ss.get_span()), want);
}

// -- 2. v36_hash_link_type: state(32) + VarStr(extra_data) + VarInt(len) ---
// Oracle wire = <32-byte state> || <VarStr extra_data> || <length VarInt>.
// extra_data = aa bb cc -> VarStr "03aabbcc"; length = 300 -> "fd2c01".
{
bch::V36HashLinkType hl;
hl.m_state = FixedStrType<32>(state_raw());
hl.m_extra_data = BaseScript(std::vector<unsigned char>{0xaa, 0xbb, 0xcc});
hl.m_length = 300;

PackStream ss;
ss << hl;
const std::string want = STATE_HEX + "03aabbcc" + "fd2c01";
CHECK_EQ("v36_hash_link_type wire (state||VarStr extra_data||VarInt len)",
to_hex(ss.get_span()), want);
}

// -- 3. share_type outer wrapper: VarInt(type) + VarStr(contents) ----------
// Oracle share_type = ('type', VarIntType()), ('contents', VarStrType()).
// type=35 -> "23"; contents = de ad be ef -> VarStr "04deadbeef".
{
chain::RawShare rs(35, BaseScript(std::vector<unsigned char>{0xde, 0xad, 0xbe, 0xef}));
PackStream ss;
ss << rs;
const std::string want = std::string("23") + "04deadbeef";
CHECK_EQ("share_type wrapper wire v35 (VarInt type || VarStr contents)",
to_hex(ss.get_span()), want);
}
{
// post-ratchet v36 type tag still single-byte CompactSize -> "24".
chain::RawShare rs(36, BaseScript(std::vector<unsigned char>{0xde, 0xad, 0xbe, 0xef}));
PackStream ss;
ss << rs;
const std::string want = std::string("24") + "04deadbeef";
CHECK_EQ("share_type wrapper wire v36 (VarInt type || VarStr contents)",
to_hex(ss.get_span()), want);
}

if (failures) {
std::cerr << "\nbch G1 share-serialization byte-parity KAT: "
<< failures << " MISMATCH(es) vs jtoomim oracle wire format\n";
return 1;
}
std::cout << "bch G1 share-serialization byte-parity KAT: PASS "
"(share_type + hash_link v35/v36 + crossing-floor v35 pinned)\n";
return 0;
}
Loading