Skip to content

Commit e49dbe4

Browse files
authored
Merge pull request #642 from frstrtr/bch/g2-local-share-author
feat(bch): wire local-share author path (G2 sharechain accumulation)
2 parents 7e1a14f + 4e0caf3 commit e49dbe4

4 files changed

Lines changed: 251 additions & 0 deletions

File tree

src/impl/bch/pool_entrypoint.hpp

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@
5353
#include "pool_standup.hpp"
5454
#include "coin/embedded_daemon.hpp"
5555
#include "stratum/work_source.hpp"
56+
#include "config_pool.hpp" // bch::PoolConfig::get_donation_script
57+
#include "share_check.hpp" // bch::create_local_share (transitive via node.hpp)
58+
#include "share_types.hpp" // bch::StaleInfo
59+
#include "coin/block.hpp" // bch::coin::SmallBlockHeaderType
60+
61+
#include <core/pack_types.hpp> // BaseScript
5662

5763
#include <core/log.hpp>
5864
#include <core/stratum_server.hpp>
@@ -62,6 +68,9 @@
6268
#include <boost/asio/io_context.hpp>
6369

6470
#include <cstdint>
71+
#include <cstring>
72+
#include <shared_mutex>
73+
#include <type_traits>
6574
#include <memory>
6675
#include <stdexcept>
6776
#include <string>
@@ -132,6 +141,141 @@ inline void standup_pool_run(boost::asio::io_context& ioc,
132141
return r.any();
133142
});
134143

144+
// ── Sharechain WRITE path: local-share author wiring ─────────────────
145+
// Without these callbacks BCHWorkSource accepts miner submissions that
146+
// clear the sharechain target but DROPS them ("accepted (no-tracker)",
147+
// work_source.cpp:686) because create_share_fn_ is null -- c2pool-bch runs
148+
// as a bare stratum proxy and Stored shares stay stuck at 0. This block
149+
// closes that gap (mirrors main_btc.cpp:863/1118), wiring the three
150+
// callbacks the local-author path needs:
151+
// - best_share_hash_fn : new-job prev_share = our sharechain tip
152+
// - donation_script : version-gated coinbase residual recipient
153+
// - create_share_fn : mining_submit -> bch::create_local_share ->
154+
// tracker.add -> broadcast_share + notify_local_share
155+
// BCH is a STANDALONE SHA256d parent: no segwit, no merged/aux dimension
156+
// (merged_addrs = {}, segwit_active=false, witness_* empty). ref_hash_fn /
157+
// pplns_fn are a FOLLOW-UP slice -- they gate peer-verifiable ref_hash +
158+
// PPLNS payout distribution, NOT local Stored accumulation; until wired,
159+
// build_connection_coinbase falls back to a single-output coinbase and
160+
// create_local_share computes its own share target via
161+
// tracker.compute_share_target (has_frozen=false).
162+
work_source->set_best_share_hash_fn(
163+
[&node]() -> uint256 { return node.best_share_hash(); });
164+
165+
// Initial donation matches the cold-start create version (35 -> P2PK). A
166+
// ratchet-driven refresh to the COMBINED P2SH on v36 activation is the same
167+
// follow-up slice as pplns_fn/ref_hash_fn.
168+
work_source->set_donation_script(PoolConfig::get_donation_script(35));
169+
170+
work_source->set_create_share_fn(
171+
[&node](const std::vector<unsigned char>& full_coinbase,
172+
const std::vector<uint8_t>& header_80b,
173+
const core::stratum::JobSnapshot& job,
174+
const std::vector<unsigned char>& payout_script) -> uint256
175+
{
176+
if (header_80b.size() != 80) {
177+
LOG_WARNING << "[BCH-CREATE-SHARE] bad header size=" << header_80b.size();
178+
return uint256::ZERO;
179+
}
180+
181+
// Parse the 80-byte BCH block header -> SmallBlockHeaderType. The
182+
// merkle_root (bytes 36..67) is reconstructible from coinbase +
183+
// frozen branches, so it is not stored in min_header.
184+
auto read_le32 = [](const uint8_t* p) -> uint32_t {
185+
return uint32_t(p[0]) | (uint32_t(p[1]) << 8)
186+
| (uint32_t(p[2]) << 16) | (uint32_t(p[3]) << 24);
187+
};
188+
coin::SmallBlockHeaderType min_header;
189+
min_header.m_version = read_le32(header_80b.data() + 0);
190+
std::memcpy(min_header.m_previous_block.data(), header_80b.data() + 4, 32);
191+
min_header.m_timestamp = read_le32(header_80b.data() + 68);
192+
min_header.m_bits = read_le32(header_80b.data() + 72);
193+
min_header.m_nonce = read_le32(header_80b.data() + 76);
194+
195+
BaseScript coinbase_bs(std::vector<unsigned char>(
196+
full_coinbase.begin(), full_coinbase.end()));
197+
198+
// Stratum branches are hex of LE-internal bytes -> ParseHex+memcpy
199+
// (SetHex would byte-reverse and break the merkle root the miner used).
200+
std::vector<uint256> merkle_branches;
201+
merkle_branches.reserve(job.merkle_branches.size());
202+
for (const auto& bhex : job.merkle_branches) {
203+
uint256 b;
204+
auto bb = ParseHex(bhex);
205+
if (bb.size() == 32) std::memcpy(b.begin(), bb.data(), 32);
206+
merkle_branches.push_back(b);
207+
}
208+
209+
// Exclusive tracker lock (non-blocking): defer to the next submit if
210+
// the compute thread is mid-think rather than freezing the io_context.
211+
std::unique_lock<std::shared_mutex> lk(node.tracker_mutex(), std::try_to_lock);
212+
if (!lk.owns_lock()) {
213+
LOG_INFO << "[BCH-CREATE-SHARE] tracker busy -- share deferred";
214+
return uint256::ZERO;
215+
}
216+
217+
// Author at the current tip's version (35 on an empty chain), voting
218+
// desired=36. Same-version authoring never trips the 60%-by-work
219+
// accept gate; the ratchet-driven upgrade to v36 is a follow-up slice.
220+
int64_t create_ver = 35;
221+
if (!job.prev_share_hash.IsNull() &&
222+
node.tracker().chain.contains(job.prev_share_hash)) {
223+
node.tracker().chain.get_share(job.prev_share_hash).invoke(
224+
[&](auto* s) {
225+
using ST = std::remove_pointer_t<decltype(s)>;
226+
create_ver = ST::version;
227+
});
228+
}
229+
230+
uint256 share_hash;
231+
try {
232+
share_hash = bch::create_local_share(
233+
node.tracker(), min_header, coinbase_bs,
234+
/* subsidy */ job.subsidy,
235+
/* prev_share */ job.prev_share_hash,
236+
merkle_branches, payout_script,
237+
/* donation bps */ 50,
238+
/* merged_addrs */ {},
239+
/* stale_info */ StaleInfo::none,
240+
/* segwit_active */ false, // BCH: no segwit
241+
/* witness_commitment */ {},
242+
/* message_data */ {},
243+
/* actual_coinbase_bytes */ full_coinbase,
244+
/* witness_root */ uint256(),
245+
/* override_max_bits */ job.share_max_bits, // pin share target to
246+
/* override_bits */ job.share_bits, // what the miner was issued
247+
/* frozen_absheight */ 0,
248+
/* frozen_abswork */ uint128(),
249+
/* frozen_far_share_hash */ uint256(),
250+
/* frozen_timestamp */ 0,
251+
/* frozen_merged_payout */ uint256(),
252+
/* has_frozen */ false,
253+
/* frozen_merkle_branches*/ {},
254+
/* frozen_witness_root */ uint256(),
255+
/* frozen_merged_cb_info */ {},
256+
/* share_version */ create_ver,
257+
/* desired_version */ 36);
258+
} catch (const std::exception& e) {
259+
LOG_WARNING << "[BCH-CREATE-SHARE] threw: " << e.what();
260+
return uint256::ZERO;
261+
}
262+
263+
// Drop the exclusive lock BEFORE broadcast/notify (they take their
264+
// own locks / post to the io_context).
265+
lk.unlock();
266+
if (!share_hash.IsNull()) {
267+
node.broadcast_share(share_hash);
268+
node.notify_local_share(share_hash);
269+
LOG_INFO << "[BCH-CREATE-SHARE] OK + broadcast v" << create_ver
270+
<< " hash=" << share_hash.GetHex().substr(0, 16);
271+
}
272+
return share_hash;
273+
});
274+
275+
LOG_INFO << "[BCH-POOL] sharechain WRITE path wired (mining_submit ->"
276+
<< " create_local_share -> broadcast_share + notify_local_share);"
277+
<< " ref_hash/pplns are a follow-up slice.";
278+
135279
std::unique_ptr<core::StratumServer> stratum_server;
136280
if (stratum_port != 0) {
137281
stratum_server = std::make_unique<core::StratumServer>(

src/impl/bch/share_check.hpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2202,6 +2202,18 @@ uint256 create_local_share_v35(
22022202
prev_share, share.m_timestamp, desired_target);
22032203
share.m_max_bits = share_max_bits;
22042204
share.m_bits = share_bits;
2205+
2206+
// -- Share-target pin (independent of has_frozen) --
2207+
// Pin m_bits/m_max_bits to the target the miner was actually ISSUED and
2208+
// hashed against (caller passes job.share_bits/share_max_bits) BEFORE
2209+
// abswork is derived from m_bits below. Without this, create_local_share
2210+
// re-derives the target via compute_share_target, which on cold-start /
2211+
// isolated-net (and under the BCH_DEMO_SHARE_BITS floor) differs from the
2212+
// target the stratum gate accepted -- so the internal PoW recheck rejects
2213+
// an otherwise-valid local share and Stored stays 0. The has_frozen block
2214+
// below re-applies the same values, so this is a no-op on that path.
2215+
if (override_max_bits) share.m_max_bits = override_max_bits;
2216+
if (override_bits) share.m_bits = override_bits;
22052217
share.m_nonce = 0;
22062218

22072219
// V35: address as string (VarStr), convert from payout_script
@@ -2635,6 +2647,18 @@ uint256 create_local_share(
26352647
share.m_max_bits = share_max_bits;
26362648
share.m_bits = share_bits;
26372649

2650+
// -- Share-target pin (independent of has_frozen) --
2651+
// Pin m_bits/m_max_bits to the target the miner was actually ISSUED and
2652+
// hashed against (caller passes job.share_bits/share_max_bits) BEFORE
2653+
// abswork is derived from m_bits below. Without this, create_local_share
2654+
// re-derives the target via compute_share_target, which on cold-start /
2655+
// isolated-net (and under the BCH_DEMO_SHARE_BITS floor) differs from the
2656+
// target the stratum gate accepted -- so the internal PoW recheck rejects
2657+
// an otherwise-valid local share and Stored stays 0. The has_frozen block
2658+
// below re-applies the same values, so this is a no-op on that path.
2659+
if (override_max_bits) share.m_max_bits = override_max_bits;
2660+
if (override_bits) share.m_bits = override_bits;
2661+
26382662
share.m_nonce = 0; // share commitment nonce (not block nonce)
26392663
share.m_merged_addresses = merged_addrs;
26402664

src/impl/bch/share_tracker.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,20 @@ class ShareTracker
407407
// attempt_verify() promotes it into `verified`. add() merely buffers it in
408408
// the raw `chain`. BCH is a SHA256d standalone parent: no merged-mining, so
409409
// (unlike btc) there is no try_register_merged_addr() leg here.
410+
// -- Add share to the main chain (raw-pointer form) --
411+
// Mirrors btc::ShareTracker::add(ShareT*) (SSOT) so create_local_share()
412+
// can hand off a freshly heap-allocated concrete share (PaddingBugfixShare /
413+
// MergedMiningShare) without the caller wrapping it into the ShareType
414+
// variant. This overload was missing on BCH -- create_local_share is the
415+
// first caller, so its instantiation surfaced the gap. BCH is a SHA256d
416+
// standalone parent: no try_register_merged_addr leg (see add(ShareType)).
417+
template <typename ShareT>
418+
void add(ShareT* share)
419+
{
420+
if (!chain.contains(share->m_hash))
421+
chain.add(share);
422+
}
423+
410424
void add(ShareType share)
411425
{
412426
auto h = share.hash();

src/impl/bch/stratum/work_source.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@
3131
#include <impl/bch/coin/mempool.hpp>
3232
#include <impl/bch/coin/merkle.hpp> // merkle_hash_pair (CTOR SHA256d)
3333
#include <impl/bch/coin/template_builder.hpp> // build_template + rpc::WorkData
34+
#include <impl/bch/config_pool.hpp> // PoolConfig::max_target (G2 cold-start floor)
3435

3536
#include <core/log.hpp>
3637
#include <btclibs/util/strencodings.h> // HexStr
3738

3839
#include <ctime>
40+
#include <cstdlib> // std::getenv (BCH_DEMO_BLOCK_BITS isolated-net pin)
3941
#include <span>
4042
#include <utility>
4143

@@ -207,6 +209,55 @@ BCHWorkSource::cached_template() const
207209
auto built = bch::coin::TemplateBuilder::build_template(chain_, mempool_, is_testnet_);
208210
if (!built) return nullptr; // chain has no tip yet
209211

212+
// [ISOLATED-NET DEMO / G2] Env-gated block-target pin (single source).
213+
// On a genesis-difficulty isolated net the substrate hands GBT bits =
214+
// powLimit (regtest 0x207fffff), so every pseudoshare trivially clears
215+
// block and the p2pool sharechain counter cannot move distinct from
216+
// block-founds. When BCH_DEMO_BLOCK_BITS is set, pin the template block
217+
// bits to a fixed harder compact target here -- on the mutable build
218+
// result before it is frozen into the shared cache -- so the coinbase,
219+
// the header nBits, and core stratum_server gbt_block_nbits all read one
220+
// consistent value; the bulk of accepted shares then land as STORED
221+
// shares and block-founds stay rare. OFF unless the env var is set;
222+
// never active on normal or mainnet runs. BCH-local (no shared-core edit).
223+
if (const char* e = std::getenv("BCH_DEMO_BLOCK_BITS"); e && *e)
224+
built->m_data["bits"] = std::string(e);
225+
226+
// [G2 cold-start] Seed the pool share-target floor on the WORK-GEN path.
227+
// build_connection_coinbase() also seeds share_bits_ from ref_hash_fn's
228+
// genesis max_bits, but that path runs only AFTER a share is created --
229+
// which core::StratumServer gates on pool_difficulty>0, itself derived
230+
// from share_bits_. On an empty sharechain share_bits_==0 => pool
231+
// difficulty==0 => the is_pool_share gate never fires => no share is
232+
// created => share_bits_ can never advance: a cold-start deadlock the
233+
// share-create seed cannot break because it sits behind the very gate it
234+
// needs to open. cached_template() runs on every work poll, BEFORE and
235+
// INDEPENDENT of share creation, so seeding the floor here bootstraps
236+
// pool_difficulty and lets the first real submission cross the gate.
237+
// Idempotent (fires only while unseeded); uses the exact floor
238+
// compute_share_target's genesis branch emits for max_bits.
239+
if (share_bits_.load(std::memory_order_relaxed) == 0) {
240+
// [ISOLATED-NET DEMO / G2] Symmetric to BCH_DEMO_BLOCK_BITS above: on a
241+
// genesis-difficulty isolated net PoolConfig::max_target() (the p2pool
242+
// network share floor, ~diff-1 / 0x1d00ffff) is far too hard for a CPU
243+
// grinder to clear at a useful rate, so the sharechain STORED counter
244+
// never advances independently of block-founds even after the
245+
// cold-start deadlock fix. BCH_DEMO_SHARE_BITS pins this cold-start
246+
// share floor to a CPU-clearable compact target (e.g. regtest powLimit
247+
// 0x207fffff) so a grinder promotes real STORED shares while
248+
// BCH_DEMO_BLOCK_BITS keeps block-founds rare -- proving 0->N sharechain
249+
// accumulation without ASIC hashrate. OFF unless set; never active on
250+
// normal or mainnet runs. BCH-local (no shared-core edit).
251+
uint32_t floor_bits;
252+
if (const char* e = std::getenv("BCH_DEMO_SHARE_BITS"); e && *e)
253+
floor_bits = static_cast<uint32_t>(std::strtoul(e, nullptr, 16));
254+
else
255+
floor_bits =
256+
chain::target_to_bits_upper_bound(bch::PoolConfig::max_target());
257+
share_bits_.store(floor_bits, std::memory_order_relaxed);
258+
share_max_bits_.store(floor_bits, std::memory_order_relaxed);
259+
}
260+
210261
auto sp = std::make_shared<const bch::coin::rpc::WorkData>(std::move(*built));
211262
std::lock_guard<std::mutex> lk(template_mutex_);
212263
template_cache_ = sp;
@@ -271,6 +322,12 @@ std::pair<std::string, std::string> BCHWorkSource::get_coinbase_parts() const
271322

272323
// -- IWorkSource: setters (callback wiring from main_bch.cpp) ------------------
273324

325+
void BCHWorkSource::set_best_share_hash_fn(std::function<uint256()> fn)
326+
{
327+
std::lock_guard<std::mutex> lk(best_share_mutex_);
328+
best_share_hash_fn_ = std::move(fn);
329+
}
330+
274331
void BCHWorkSource::set_pplns_fn(PplnsFn fn)
275332
{
276333
std::lock_guard<std::mutex> lk(callback_mutex_);
@@ -402,6 +459,18 @@ core::stratum::CoinbaseResult BCHWorkSource::build_connection_coinbase(
402459
if (rh_result.bits != 0) {
403460
share_bits_.store(rh_result.bits, std::memory_order_relaxed);
404461
share_max_bits_.store(rh_result.max_bits, std::memory_order_relaxed);
462+
} else if (share_bits_.load(std::memory_order_relaxed) == 0 &&
463+
rh_result.max_bits != 0) {
464+
// Cold-start seed (empty sharechain): compute_share_target's
465+
// genesis branch yields bits==0 while max_bits carries the
466+
// MAX_TARGET floor (easiest share target). Without a nonzero
467+
// share_bits_, StratumServer derives pool_difficulty==0, its
468+
// is_pool_share gate never fires, no share is ever created, and
469+
// share_bits_ can never advance past 0 -> cold-start deadlock.
470+
// Seed both atomics to the max_bits floor so the first real
471+
// submission clears the gate and bootstraps the sharechain.
472+
share_bits_.store(rh_result.max_bits, std::memory_order_relaxed);
473+
share_max_bits_.store(rh_result.max_bits, std::memory_order_relaxed);
405474
}
406475
} catch (const std::exception& e) {
407476
LOG_WARNING << "[BCH-STRATUM] ref_hash_fn threw: " << e.what()

0 commit comments

Comments
 (0)