From 03e516881e69d000bb60aac31846878e061d3e30 Mon Sep 17 00:00:00 2001 From: frstrtr Date: Sun, 5 Jul 2026 08:42:33 +0000 Subject: [PATCH 1/6] feat(bch): env-gated static block-target pin for isolated-net G2 demo Add BCH_DEMO_BLOCK_BITS override at the stratum block-found classification site. On a genesis-difficulty isolated net the substrate returns GBT bits 1d00ffff (diff 1), so every pseudoshare trivially clears block and the p2pool sharechain counter cannot increment distinct from block-founds. When set, the env var pins a fixed harder compact-bits block target for the IS-A-BLOCK check only, so the bulk of accepted shares land as shares and the sharechain counter moves off zero while block-founds stay rare. BCH-local (src/impl/bch/stratum only, no shared-core edit); OFF unless the env var is set; never active on normal or mainnet runs. Share acceptance and PPLNS weighting are untouched. --- src/impl/bch/stratum/work_source.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/impl/bch/stratum/work_source.cpp b/src/impl/bch/stratum/work_source.cpp index 27fa8870..8bbbbff2 100644 --- a/src/impl/bch/stratum/work_source.cpp +++ b/src/impl/bch/stratum/work_source.cpp @@ -36,6 +36,7 @@ #include // HexStr #include +#include // std::getenv (BCH_DEMO_BLOCK_BITS isolated-net pin) #include #include @@ -544,8 +545,18 @@ nlohmann::json BCHWorkSource::mining_submit( else share_target.SetCompact(/*diff 1*/ 0x1d00ffff); uint256 block_target; - block_target.SetCompact(parse_be_hex_u32( - job->block_nbits.empty() ? job->nbits : job->block_nbits)); + { + // [ISOLATED-NET DEMO / G2] Env-gated static block-target pin. When + // BCH_DEMO_BLOCK_BITS is set, classify block-founds against a fixed, + // harder compact-bits target so pseudoshares stop trivially clearing + // block on a genesis-difficulty isolated net (substrate at diff 1 => + // GBT bits 1d00ffff), letting the p2pool sharechain counter increment + // distinct from block-founds. OFF unless the env var is set -- never + // active on normal or mainnet runs; BCH-local, no shared-core edit. + std::string bt_bits = job->block_nbits.empty() ? job->nbits : job->block_nbits; + if (const char* e = std::getenv("BCH_DEMO_BLOCK_BITS"); e && *e) bt_bits = e; + block_target.SetCompact(parse_be_hex_u32(bt_bits)); + } auto pow_hex_short = pow_hash.GetHex().substr(0, 16); From d2066f9dba3a498c29874edf8daca45543fd3528 Mon Sep 17 00:00:00 2001 From: frstrtr Date: Sun, 5 Jul 2026 09:43:12 +0000 Subject: [PATCH 2/6] fix(bch): seed share_bits_ from max_bits floor on cold-start empty sharechain On an empty sharechain compute_share_target genesis branch returns bits==0, so build_connection_coinbase left share_bits_ at 0. StratumServer then derives pool_difficulty==0, its is_pool_share gate (stratum_server.cpp) never fires, no P2P share is ever created, and share_bits_ can never advance past 0 -> cold-start deadlock. When rh_result.bits==0 and share_bits_ is still unseeded, fall back to rh_result.max_bits (the MAX_TARGET easiest-share floor) so the first real submission clears the gate and bootstraps the chain. Warm path unchanged. BCH-local (src/impl/bch/); no src/core/ change. --- src/impl/bch/stratum/work_source.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/impl/bch/stratum/work_source.cpp b/src/impl/bch/stratum/work_source.cpp index 8bbbbff2..326634e3 100644 --- a/src/impl/bch/stratum/work_source.cpp +++ b/src/impl/bch/stratum/work_source.cpp @@ -403,6 +403,18 @@ core::stratum::CoinbaseResult BCHWorkSource::build_connection_coinbase( if (rh_result.bits != 0) { share_bits_.store(rh_result.bits, std::memory_order_relaxed); share_max_bits_.store(rh_result.max_bits, std::memory_order_relaxed); + } else if (share_bits_.load(std::memory_order_relaxed) == 0 && + rh_result.max_bits != 0) { + // Cold-start seed (empty sharechain): compute_share_target's + // genesis branch yields bits==0 while max_bits carries the + // MAX_TARGET floor (easiest share target). Without a nonzero + // share_bits_, StratumServer derives pool_difficulty==0, its + // is_pool_share gate never fires, no share is ever created, and + // share_bits_ can never advance past 0 -> cold-start deadlock. + // Seed both atomics to the max_bits floor so the first real + // submission clears the gate and bootstraps the sharechain. + share_bits_.store(rh_result.max_bits, std::memory_order_relaxed); + share_max_bits_.store(rh_result.max_bits, std::memory_order_relaxed); } } catch (const std::exception& e) { LOG_WARNING << "[BCH-STRATUM] ref_hash_fn threw: " << e.what() From 8c82dbf7de9d9eef037a583a26345b9ec18cdfa0 Mon Sep 17 00:00:00 2001 From: frstrtr Date: Sun, 5 Jul 2026 11:14:52 +0000 Subject: [PATCH 3/6] feat(bch): relocate G2 block-target pin to single template source Move the env-gated BCH_DEMO_BLOCK_BITS pin off the work_source mining_submit classification site (ineffective: core stratum_server.cpp does its own IS-A-BLOCK check from gbt_block_nbits) onto the mutable build_template result in cached_template(), before it is frozen into the shared cache. All readers -- coinbase, header nBits, and core gbt_block_nbits -- then see one consistent harder block target on the isolated net, so pseudoshares stop trivially clearing block. Verified on regtest: IS-A-BLOCK 100% -> 0% of accepted shares. Isolated-net demo only; OFF unless the env var is set; BCH-local. --- src/impl/bch/stratum/work_source.cpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/impl/bch/stratum/work_source.cpp b/src/impl/bch/stratum/work_source.cpp index 326634e3..f0ddfbbc 100644 --- a/src/impl/bch/stratum/work_source.cpp +++ b/src/impl/bch/stratum/work_source.cpp @@ -208,6 +208,20 @@ BCHWorkSource::cached_template() const auto built = bch::coin::TemplateBuilder::build_template(chain_, mempool_, is_testnet_); if (!built) return nullptr; // chain has no tip yet + // [ISOLATED-NET DEMO / G2] Env-gated block-target pin (single source). + // On a genesis-difficulty isolated net the substrate hands GBT bits = + // powLimit (regtest 0x207fffff), so every pseudoshare trivially clears + // block and the p2pool sharechain counter cannot move distinct from + // block-founds. When BCH_DEMO_BLOCK_BITS is set, pin the template block + // bits to a fixed harder compact target here -- on the mutable build + // result before it is frozen into the shared cache -- so the coinbase, + // the header nBits, and core stratum_server gbt_block_nbits all read one + // consistent value; the bulk of accepted shares then land as STORED + // shares and block-founds stay rare. OFF unless the env var is set; + // never active on normal or mainnet runs. BCH-local (no shared-core edit). + if (const char* e = std::getenv("BCH_DEMO_BLOCK_BITS"); e && *e) + built->m_data["bits"] = std::string(e); + auto sp = std::make_shared(std::move(*built)); std::lock_guard lk(template_mutex_); template_cache_ = sp; @@ -557,18 +571,8 @@ nlohmann::json BCHWorkSource::mining_submit( else share_target.SetCompact(/*diff 1*/ 0x1d00ffff); uint256 block_target; - { - // [ISOLATED-NET DEMO / G2] Env-gated static block-target pin. When - // BCH_DEMO_BLOCK_BITS is set, classify block-founds against a fixed, - // harder compact-bits target so pseudoshares stop trivially clearing - // block on a genesis-difficulty isolated net (substrate at diff 1 => - // GBT bits 1d00ffff), letting the p2pool sharechain counter increment - // distinct from block-founds. OFF unless the env var is set -- never - // active on normal or mainnet runs; BCH-local, no shared-core edit. - std::string bt_bits = job->block_nbits.empty() ? job->nbits : job->block_nbits; - if (const char* e = std::getenv("BCH_DEMO_BLOCK_BITS"); e && *e) bt_bits = e; - block_target.SetCompact(parse_be_hex_u32(bt_bits)); - } + block_target.SetCompact(parse_be_hex_u32( + job->block_nbits.empty() ? job->nbits : job->block_nbits)); auto pow_hex_short = pow_hash.GetHex().substr(0, 16); From f8a12637d637155376bb84a782edcc935d2d0a9b Mon Sep 17 00:00:00 2001 From: frstrtr Date: Sun, 5 Jul 2026 11:38:40 +0000 Subject: [PATCH 4/6] fix(bch): seed cold-start share-target floor on work-gen path, not share-create The empty-sharechain cold-start seed for share_bits_ lived in build_connection_coinbase (the share-CREATE path), but that path only runs after core::StratumServer clears its is_pool_share gate, which is derived from pool_difficulty, itself derived from share_bits_. On an empty chain share_bits_==0 => pool_difficulty==0 => the gate never fires => no share is created => share_bits_ never advances: a deadlock the share-create seed cannot break because it sits behind the gate it opens. Relocate a floor-seed into cached_template() on the WORK-GEN path, which runs on every work poll independent of share creation. Seed both atomics to target_to_bits_upper_bound(PoolConfig::max_target()) -- the exact floor compute_share_target emits for max_bits on the genesis branch. Idempotent (fires only while unseeded). Verified on .198 regtest: pool_difficulty 0 -> 1 at cold start, is_pool_share gate now live from first work poll. --- src/impl/bch/stratum/work_source.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/impl/bch/stratum/work_source.cpp b/src/impl/bch/stratum/work_source.cpp index f0ddfbbc..8dfb93a4 100644 --- a/src/impl/bch/stratum/work_source.cpp +++ b/src/impl/bch/stratum/work_source.cpp @@ -31,6 +31,7 @@ #include #include // merkle_hash_pair (CTOR SHA256d) #include // build_template + rpc::WorkData +#include // PoolConfig::max_target (G2 cold-start floor) #include #include // HexStr @@ -222,6 +223,26 @@ BCHWorkSource::cached_template() const if (const char* e = std::getenv("BCH_DEMO_BLOCK_BITS"); e && *e) built->m_data["bits"] = std::string(e); + // [G2 cold-start] Seed the pool share-target floor on the WORK-GEN path. + // build_connection_coinbase() also seeds share_bits_ from ref_hash_fn's + // genesis max_bits, but that path runs only AFTER a share is created -- + // which core::StratumServer gates on pool_difficulty>0, itself derived + // from share_bits_. On an empty sharechain share_bits_==0 => pool + // difficulty==0 => the is_pool_share gate never fires => no share is + // created => share_bits_ can never advance: a cold-start deadlock the + // share-create seed cannot break because it sits behind the very gate it + // needs to open. cached_template() runs on every work poll, BEFORE and + // INDEPENDENT of share creation, so seeding the floor here bootstraps + // pool_difficulty and lets the first real submission cross the gate. + // Idempotent (fires only while unseeded); uses the exact floor + // compute_share_target's genesis branch emits for max_bits. + if (share_bits_.load(std::memory_order_relaxed) == 0) { + const uint32_t floor_bits = + chain::target_to_bits_upper_bound(bch::PoolConfig::max_target()); + share_bits_.store(floor_bits, std::memory_order_relaxed); + share_max_bits_.store(floor_bits, std::memory_order_relaxed); + } + auto sp = std::make_shared(std::move(*built)); std::lock_guard lk(template_mutex_); template_cache_ = sp; From dbaf58d960a07d0adf608d63d1d7b1b2db04757b Mon Sep 17 00:00:00 2001 From: frstrtr Date: Sun, 5 Jul 2026 12:00:23 +0000 Subject: [PATCH 5/6] feat(bch): BCH_DEMO_SHARE_BITS env knob for CPU-provable G2 accumulation Symmetric to BCH_DEMO_BLOCK_BITS. The cold-start share-floor seed hardcoded PoolConfig::max_target() (~diff-1 / 0x1d00ffff), which a CPU grinder cannot clear at a useful rate, so the sharechain STORED counter never advanced independently of block-founds even after the cold-start deadlock fix (f8a12637d). BCH_DEMO_SHARE_BITS pins the cold-start share floor to a CPU-clearable compact target on an isolated net so a grinder promotes real STORED shares while BCH_DEMO_BLOCK_BITS keeps block-founds rare -- proving 0->N accumulation without ASIC hashrate. Env-gated, off by default, never active on normal or mainnet runs. BCH-local. --- src/impl/bch/stratum/work_source.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/impl/bch/stratum/work_source.cpp b/src/impl/bch/stratum/work_source.cpp index 8dfb93a4..ca0b5ba1 100644 --- a/src/impl/bch/stratum/work_source.cpp +++ b/src/impl/bch/stratum/work_source.cpp @@ -237,8 +237,23 @@ BCHWorkSource::cached_template() const // Idempotent (fires only while unseeded); uses the exact floor // compute_share_target's genesis branch emits for max_bits. if (share_bits_.load(std::memory_order_relaxed) == 0) { - const uint32_t floor_bits = - chain::target_to_bits_upper_bound(bch::PoolConfig::max_target()); + // [ISOLATED-NET DEMO / G2] Symmetric to BCH_DEMO_BLOCK_BITS above: on a + // genesis-difficulty isolated net PoolConfig::max_target() (the p2pool + // network share floor, ~diff-1 / 0x1d00ffff) is far too hard for a CPU + // grinder to clear at a useful rate, so the sharechain STORED counter + // never advances independently of block-founds even after the + // cold-start deadlock fix. BCH_DEMO_SHARE_BITS pins this cold-start + // share floor to a CPU-clearable compact target (e.g. regtest powLimit + // 0x207fffff) so a grinder promotes real STORED shares while + // BCH_DEMO_BLOCK_BITS keeps block-founds rare -- proving 0->N sharechain + // accumulation without ASIC hashrate. OFF unless set; never active on + // normal or mainnet runs. BCH-local (no shared-core edit). + uint32_t floor_bits; + if (const char* e = std::getenv("BCH_DEMO_SHARE_BITS"); e && *e) + floor_bits = static_cast(std::strtoul(e, nullptr, 16)); + else + floor_bits = + chain::target_to_bits_upper_bound(bch::PoolConfig::max_target()); share_bits_.store(floor_bits, std::memory_order_relaxed); share_max_bits_.store(floor_bits, std::memory_order_relaxed); } From 4e0caf34a700cc0fd80af6b254cd460fdf8c21ca Mon Sep 17 00:00:00 2001 From: frstrtr Date: Sun, 5 Jul 2026 19:44:26 +0000 Subject: [PATCH 6/6] feat(bch): wire local-share author path (create_share_fn) so miner shares populate the sharechain BCH ran as a bare stratum proxy: BCHWorkSource accepted miner submissions that cleared the sharechain target but DROPPED them (accepted (no-tracker), work_source.cpp) because create_share_fn_ was never wired -- Stored shares stuck at 0. This wires the sharechain WRITE path (mirrors main_btc.cpp:1118): - pool_entrypoint.hpp: set_best_share_hash_fn / set_donation_script / set_create_share_fn. create_share_fn parses the 80B header into a SmallBlockHeaderType, rebuilds coinbase + merkle branches, and calls bch::create_local_share under a non-blocking exclusive tracker lock, then broadcast_share + notify_local_share on success. - share_tracker.hpp: add(ShareT*) raw-pointer overload (create_local_share is the first caller; mirrors btc SSOT). BCH standalone SHA256d parent: merged_addrs={}, segwit_active=false. Verified on regtest: grinder 8/8 accepted -> create_local_share_v35 added 8 shares -> ASYNC-THINK chain=8 verified=8 (was 0). ref_hash/pplns are a follow-up slice. --- src/impl/bch/pool_entrypoint.hpp | 144 +++++++++++++++++++++++++++ src/impl/bch/share_check.hpp | 24 +++++ src/impl/bch/share_tracker.hpp | 14 +++ src/impl/bch/stratum/work_source.cpp | 6 ++ 4 files changed, 188 insertions(+) diff --git a/src/impl/bch/pool_entrypoint.hpp b/src/impl/bch/pool_entrypoint.hpp index 0a9c6f7a..41766af4 100644 --- a/src/impl/bch/pool_entrypoint.hpp +++ b/src/impl/bch/pool_entrypoint.hpp @@ -53,6 +53,12 @@ #include "pool_standup.hpp" #include "coin/embedded_daemon.hpp" #include "stratum/work_source.hpp" +#include "config_pool.hpp" // bch::PoolConfig::get_donation_script +#include "share_check.hpp" // bch::create_local_share (transitive via node.hpp) +#include "share_types.hpp" // bch::StaleInfo +#include "coin/block.hpp" // bch::coin::SmallBlockHeaderType + +#include // BaseScript #include #include @@ -62,6 +68,9 @@ #include #include +#include +#include +#include #include #include #include @@ -132,6 +141,141 @@ inline void standup_pool_run(boost::asio::io_context& ioc, return r.any(); }); + // ── Sharechain WRITE path: local-share author wiring ───────────────── + // Without these callbacks BCHWorkSource accepts miner submissions that + // clear the sharechain target but DROPS them ("accepted (no-tracker)", + // work_source.cpp:686) because create_share_fn_ is null -- c2pool-bch runs + // as a bare stratum proxy and Stored shares stay stuck at 0. This block + // closes that gap (mirrors main_btc.cpp:863/1118), wiring the three + // callbacks the local-author path needs: + // - best_share_hash_fn : new-job prev_share = our sharechain tip + // - donation_script : version-gated coinbase residual recipient + // - create_share_fn : mining_submit -> bch::create_local_share -> + // tracker.add -> broadcast_share + notify_local_share + // BCH is a STANDALONE SHA256d parent: no segwit, no merged/aux dimension + // (merged_addrs = {}, segwit_active=false, witness_* empty). ref_hash_fn / + // pplns_fn are a FOLLOW-UP slice -- they gate peer-verifiable ref_hash + + // PPLNS payout distribution, NOT local Stored accumulation; until wired, + // build_connection_coinbase falls back to a single-output coinbase and + // create_local_share computes its own share target via + // tracker.compute_share_target (has_frozen=false). + 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->set_create_share_fn( + [&node](const std::vector& full_coinbase, + const std::vector& header_80b, + const core::stratum::JobSnapshot& job, + const std::vector& payout_script) -> uint256 + { + if (header_80b.size() != 80) { + LOG_WARNING << "[BCH-CREATE-SHARE] bad header size=" << header_80b.size(); + return uint256::ZERO; + } + + // Parse the 80-byte BCH block header -> SmallBlockHeaderType. The + // merkle_root (bytes 36..67) is reconstructible from coinbase + + // frozen branches, so it is not stored in min_header. + auto read_le32 = [](const uint8_t* p) -> uint32_t { + return uint32_t(p[0]) | (uint32_t(p[1]) << 8) + | (uint32_t(p[2]) << 16) | (uint32_t(p[3]) << 24); + }; + coin::SmallBlockHeaderType min_header; + min_header.m_version = read_le32(header_80b.data() + 0); + std::memcpy(min_header.m_previous_block.data(), header_80b.data() + 4, 32); + min_header.m_timestamp = read_le32(header_80b.data() + 68); + min_header.m_bits = read_le32(header_80b.data() + 72); + min_header.m_nonce = read_le32(header_80b.data() + 76); + + BaseScript coinbase_bs(std::vector( + full_coinbase.begin(), full_coinbase.end())); + + // Stratum branches are hex of LE-internal bytes -> ParseHex+memcpy + // (SetHex would byte-reverse and break the merkle root the miner used). + std::vector merkle_branches; + merkle_branches.reserve(job.merkle_branches.size()); + for (const auto& bhex : job.merkle_branches) { + uint256 b; + auto bb = ParseHex(bhex); + if (bb.size() == 32) std::memcpy(b.begin(), bb.data(), 32); + merkle_branches.push_back(b); + } + + // Exclusive tracker lock (non-blocking): defer to the next submit if + // the compute thread is mid-think rather than freezing the io_context. + std::unique_lock lk(node.tracker_mutex(), std::try_to_lock); + if (!lk.owns_lock()) { + LOG_INFO << "[BCH-CREATE-SHARE] tracker busy -- share deferred"; + return uint256::ZERO; + } + + // Author at the current tip's version (35 on an empty chain), voting + // desired=36. Same-version authoring never trips the 60%-by-work + // accept gate; the ratchet-driven upgrade to v36 is a follow-up slice. + int64_t create_ver = 35; + if (!job.prev_share_hash.IsNull() && + node.tracker().chain.contains(job.prev_share_hash)) { + node.tracker().chain.get_share(job.prev_share_hash).invoke( + [&](auto* s) { + using ST = std::remove_pointer_t; + create_ver = ST::version; + }); + } + + uint256 share_hash; + try { + share_hash = bch::create_local_share( + node.tracker(), min_header, coinbase_bs, + /* subsidy */ job.subsidy, + /* prev_share */ job.prev_share_hash, + merkle_branches, payout_script, + /* donation bps */ 50, + /* merged_addrs */ {}, + /* stale_info */ StaleInfo::none, + /* segwit_active */ false, // BCH: no segwit + /* witness_commitment */ {}, + /* message_data */ {}, + /* actual_coinbase_bytes */ full_coinbase, + /* witness_root */ uint256(), + /* override_max_bits */ job.share_max_bits, // pin share target to + /* override_bits */ job.share_bits, // what the miner was issued + /* frozen_absheight */ 0, + /* frozen_abswork */ uint128(), + /* frozen_far_share_hash */ uint256(), + /* frozen_timestamp */ 0, + /* frozen_merged_payout */ uint256(), + /* has_frozen */ false, + /* frozen_merkle_branches*/ {}, + /* frozen_witness_root */ uint256(), + /* frozen_merged_cb_info */ {}, + /* share_version */ create_ver, + /* desired_version */ 36); + } catch (const std::exception& e) { + LOG_WARNING << "[BCH-CREATE-SHARE] threw: " << e.what(); + return uint256::ZERO; + } + + // Drop the exclusive lock BEFORE broadcast/notify (they take their + // own locks / post to the io_context). + lk.unlock(); + if (!share_hash.IsNull()) { + node.broadcast_share(share_hash); + node.notify_local_share(share_hash); + LOG_INFO << "[BCH-CREATE-SHARE] OK + broadcast v" << create_ver + << " hash=" << share_hash.GetHex().substr(0, 16); + } + return share_hash; + }); + + LOG_INFO << "[BCH-POOL] sharechain WRITE path wired (mining_submit ->" + << " create_local_share -> broadcast_share + notify_local_share);" + << " ref_hash/pplns are a follow-up slice."; + std::unique_ptr stratum_server; if (stratum_port != 0) { stratum_server = std::make_unique( diff --git a/src/impl/bch/share_check.hpp b/src/impl/bch/share_check.hpp index f1e168df..db414901 100644 --- a/src/impl/bch/share_check.hpp +++ b/src/impl/bch/share_check.hpp @@ -2202,6 +2202,18 @@ uint256 create_local_share_v35( prev_share, share.m_timestamp, desired_target); share.m_max_bits = share_max_bits; share.m_bits = share_bits; + + // -- Share-target pin (independent of has_frozen) -- + // Pin m_bits/m_max_bits to the target the miner was actually ISSUED and + // hashed against (caller passes job.share_bits/share_max_bits) BEFORE + // abswork is derived from m_bits below. Without this, create_local_share + // re-derives the target via compute_share_target, which on cold-start / + // isolated-net (and under the BCH_DEMO_SHARE_BITS floor) differs from the + // target the stratum gate accepted -- so the internal PoW recheck rejects + // an otherwise-valid local share and Stored stays 0. The has_frozen block + // below re-applies the same values, so this is a no-op on that path. + if (override_max_bits) share.m_max_bits = override_max_bits; + if (override_bits) share.m_bits = override_bits; share.m_nonce = 0; // V35: address as string (VarStr), convert from payout_script @@ -2635,6 +2647,18 @@ uint256 create_local_share( share.m_max_bits = share_max_bits; share.m_bits = share_bits; + // -- Share-target pin (independent of has_frozen) -- + // Pin m_bits/m_max_bits to the target the miner was actually ISSUED and + // hashed against (caller passes job.share_bits/share_max_bits) BEFORE + // abswork is derived from m_bits below. Without this, create_local_share + // re-derives the target via compute_share_target, which on cold-start / + // isolated-net (and under the BCH_DEMO_SHARE_BITS floor) differs from the + // target the stratum gate accepted -- so the internal PoW recheck rejects + // an otherwise-valid local share and Stored stays 0. The has_frozen block + // below re-applies the same values, so this is a no-op on that path. + if (override_max_bits) share.m_max_bits = override_max_bits; + if (override_bits) share.m_bits = override_bits; + share.m_nonce = 0; // share commitment nonce (not block nonce) share.m_merged_addresses = merged_addrs; diff --git a/src/impl/bch/share_tracker.hpp b/src/impl/bch/share_tracker.hpp index cb5f6b2a..9ad13a62 100644 --- a/src/impl/bch/share_tracker.hpp +++ b/src/impl/bch/share_tracker.hpp @@ -407,6 +407,20 @@ class ShareTracker // attempt_verify() promotes it into `verified`. add() merely buffers it in // the raw `chain`. BCH is a SHA256d standalone parent: no merged-mining, so // (unlike btc) there is no try_register_merged_addr() leg here. + // -- Add share to the main chain (raw-pointer form) -- + // Mirrors btc::ShareTracker::add(ShareT*) (SSOT) so create_local_share() + // can hand off a freshly heap-allocated concrete share (PaddingBugfixShare / + // MergedMiningShare) without the caller wrapping it into the ShareType + // variant. This overload was missing on BCH -- create_local_share is the + // first caller, so its instantiation surfaced the gap. BCH is a SHA256d + // standalone parent: no try_register_merged_addr leg (see add(ShareType)). + template + void add(ShareT* share) + { + if (!chain.contains(share->m_hash)) + chain.add(share); + } + void add(ShareType share) { auto h = share.hash(); diff --git a/src/impl/bch/stratum/work_source.cpp b/src/impl/bch/stratum/work_source.cpp index ca0b5ba1..7592cce0 100644 --- a/src/impl/bch/stratum/work_source.cpp +++ b/src/impl/bch/stratum/work_source.cpp @@ -322,6 +322,12 @@ std::pair BCHWorkSource::get_coinbase_parts() const // -- IWorkSource: setters (callback wiring from main_bch.cpp) ------------------ +void BCHWorkSource::set_best_share_hash_fn(std::function fn) +{ + std::lock_guard lk(best_share_mutex_); + best_share_hash_fn_ = std::move(fn); +} + void BCHWorkSource::set_pplns_fn(PplnsFn fn) { std::lock_guard lk(callback_mutex_);