Skip to content

Commit 4e0caf3

Browse files
committed
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.
1 parent dbaf58d commit 4e0caf3

4 files changed

Lines changed: 188 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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,12 @@ std::pair<std::string, std::string> BCHWorkSource::get_coinbase_parts() const
322322

323323
// -- IWorkSource: setters (callback wiring from main_bch.cpp) ------------------
324324

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+
325331
void BCHWorkSource::set_pplns_fn(PplnsFn fn)
326332
{
327333
std::lock_guard<std::mutex> lk(callback_mutex_);

0 commit comments

Comments
 (0)