Skip to content

Commit 0f3bbbf

Browse files
authored
Merge pull request #69: btc-embedded combined head: Family-1 CoinAdapter + web_server rewire + V36 leak fix (4-coin)
btc-embedded combined head: Family-1 CoinAdapter + web_server rewire + V36 leak fix (4-coin)
2 parents e401267 + 4c4b08b commit 0f3bbbf

67 files changed

Lines changed: 22187 additions & 130 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,6 @@ external/**/relic_conf.h.in
212212
external/**/relic_conf.h.old
213213
# Doxygen output (e.g. external/dashbls/depends/mimalloc/docs/)
214214
external/**/depends/**/docs/
215+
216+
# Release artifact staging area (built binaries, not source)
217+
release-staging/

src/c2pool/CMakeLists.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,25 @@ target_link_libraries(c2pool_enhanced
135135
)
136136
target_link_libraries(c2pool_enhanced c2pool_hashrate pool) # OBJECT-lib SCC direct-naming (#22/#39)
137137

138+
# c2pool-btc: BTC SPV embedded p2pool — Phase B0 scaffold (stub entry).
139+
# Library `btc` is a clone of `ltc` with namespace renamed and CMake targets
140+
# adjusted; real entry-point port is in later B-phases.
141+
# Links `ltc` transitively because core/web_server.cpp references LTC symbols
142+
# (see comment at the bottom of this file). Untangling that is out of B0 scope.
143+
add_executable(c2pool-btc main_btc.cpp)
144+
target_compile_definitions(c2pool-btc PRIVATE C2POOL_VERSION="${C2POOL_GIT_VERSION}")
145+
target_link_libraries(c2pool-btc
146+
c2pool_node_enhanced
147+
c2pool_payout
148+
c2pool_merged_mining
149+
core
150+
ltc
151+
btc
152+
btc_stratum
153+
nlohmann_json::nlohmann_json
154+
${Boost_LIBRARIES}
155+
)
156+
138157
# Windows: Boost.Asio requires Winsock libraries; /bigobj for large translation units
139158
if(WIN32)
140159
target_link_libraries(c2pool ws2_32 mswsock bcrypt dbghelp)

src/c2pool/main_btc.cpp

Lines changed: 1208 additions & 0 deletions
Large diffs are not rendered by default.

src/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ set(source
3030
addr_store.hpp addr_store.cpp
3131
web_server.hpp web_server.cpp
3232
stratum_server.hpp stratum_server.cpp
33+
stratum_types.hpp
34+
stratum_work_source.hpp
3335
address_utils.hpp address_utils.cpp
3436
leveldb_store.hpp leveldb_store.cpp
3537
address_validator.hpp address_validator.cpp

src/core/coin/node_iface.hpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#pragma once
2+
3+
// ---------------------------------------------------------------------------
4+
// core::coin::ICoinNode -- generic per-coin node interface for src/core/web_server
5+
// (family-1 link fix, P2 WorkView seam).
6+
//
7+
// web_server is SHARED core but historically forward-declared and held raw
8+
// pointers to CONCRETE impl/ltc types (ltc::coin::NodeRPC,
9+
// ltc::interfaces::Node, ltc::coin::CoinNodeInterface). That breaks the BTC link
10+
// (ltc symbols absent; btc's per-coin types differ).
11+
//
12+
// SUPERSEDES the earlier template<class WorkData> INodeRPC/ICoinNode draft. The
13+
// nm census proved web_server consumes a WorkData via only its agnostic slice
14+
// (m_data/m_hashes/m_latency), never m_txs -- so getwork() does NOT need to
15+
// return the per-coin WorkData across the seam. Instead the concrete coin node
16+
// keeps the FULL WorkData coin-side (work.set(wd)) and hands core a WorkView.
17+
// A single NON-TEMPLATE virtual now suffices, and the original "keep getwork
18+
// parametric" gate is preserved where it matters: full WorkData (incl. m_txs)
19+
// never leaves the coin.
20+
//
21+
// Each coin's concrete node (ltc::coin::CoinNode, btc::coin::CoinNode) inherits
22+
// this base directly (direct-inherit, no adapter -- ltc-doge OK) and web_server
23+
// holds one core::coin::ICoinNode* m_coin_node.
24+
// ---------------------------------------------------------------------------
25+
26+
#include <string>
27+
28+
#include <core/coin/work_view.hpp>
29+
30+
namespace core::coin {
31+
32+
struct ICoinNode {
33+
virtual ~ICoinNode() = default;
34+
35+
// Build (or fetch) a block template and return its coin-agnostic slice.
36+
// The concrete impl makes the embedded-vs-rpc decision internally, calls
37+
// work.set(wd) to retain the FULL per-coin WorkData coin-side, then moves
38+
// m_data/m_hashes/m_latency into the returned view. Throws std::runtime_error
39+
// if no template is available. (Folds web_server.cpp:2167-2172.)
40+
virtual WorkView get_work_view() = 0;
41+
42+
// Submit a pre-serialised block as hex. Coin-agnostic 2-arg form: the
43+
// LTC-MWEB extension block is NOT carried on this shared virtual -- mweb is
44+
// LTC-specific and the concrete ltc node forwards to its 3-arg rpc overload
45+
// with mweb="" coin-side. Sole caller (web_server.cpp:2730) passed mweb="",
46+
// so dropping the slot cannot break MWEB submit. Returns true iff accepted.
47+
virtual bool submit_block_hex(const std::string& block_hex,
48+
bool ignore_failure) = 0;
49+
50+
// True iff this node has the embedded in-process template source.
51+
// is_embedded() and has_rpc() are ORTHOGONAL -- a node may have BOTH (embedded
52+
// preferred for get_work_view(), external RPC as fallback). Preserves the
53+
// source labels (web_server.cpp:4423-4451) and the status JSON key "embedded"
54+
// (web_server.cpp:4696).
55+
virtual bool is_embedded() const = 0;
56+
57+
// True iff this node has an external coin-RPC client. ORTHOGONAL to
58+
// is_embedded() -- may co-exist with embedded as the getwork fallback and is
59+
// the sole path for submit_block_hex(). Backs the status JSON key "has_rpc"
60+
// (web_server.cpp:4697).
61+
virtual bool has_rpc() const = 0;
62+
};
63+
64+
} // namespace core::coin

src/core/coin/utxo_view_cache.hpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,12 @@ class UTXOViewCache {
145145
bool is_coinbase = first_tx;
146146
first_tx = false;
147147

148-
// Detect HogEx (MWEB): last tx with m_hogEx flag → outputs are pegouts
149-
bool is_hogex = tx.m_hogEx;
148+
// Detect HogEx (MWEB): last tx with m_hogEx flag → outputs are pegouts.
149+
// BTC's MutableTransaction has no m_hogEx (no MWEB) — gate by requires.
150+
bool is_hogex = false;
151+
if constexpr (requires { tx.m_hogEx; }) {
152+
is_hogex = tx.m_hogEx;
153+
}
150154

151155
if (!is_coinbase) {
152156
// Spend inputs — save spent coins for undo

src/core/coin/work_view.hpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#pragma once
2+
3+
// ---------------------------------------------------------------------------
4+
// core::coin::WorkView -- the coin-AGNOSTIC slice of a per-coin WorkData that
5+
// src/core/web_server actually consumes (family-1 link fix, P2 seam).
6+
//
7+
// nm census of web_server.cpp.o @ c4547575 (ci-steward, authoritative): the TU
8+
// reads a WorkData via ONLY these three fields -- m_data, m_hashes, m_latency.
9+
// It NEVER touches m_txs (grep 'm_txs' src/core/web_server.cpp == 0 hits). So
10+
// the link break is not "abstract the interface", it is "stop crossing the seam
11+
// with the payload": the per-coin WorkData (incl. vector<Transaction> m_txs)
12+
// stays coin-side via work.set(wd); only this view crosses into core. That
13+
// severs BOTH cluster A (ltc:: symbols) and cluster B (Transaction type) at
14+
// once -- web_server.cpp.o then names no ltc:: symbol and no Transaction type.
15+
//
16+
// Field names mirror the concrete per-coin WorkData EXACTLY so every existing
17+
// web_server line (wd.m_data / wd.m_hashes / wd.m_latency) is UNCHANGED.
18+
// - m_data : getblocktemplate-shaped json the stratum/getwork path serves
19+
// - m_hashes : merkle/branch hashes (vector<uint256>)
20+
// - m_latency : template build latency; consumed at web_server.cpp:2381 as
21+
// static_cast<double>(wd.m_latency) -> m_last_work_latency.store
22+
// ---------------------------------------------------------------------------
23+
24+
#include <ctime>
25+
#include <vector>
26+
27+
#include <nlohmann/json.hpp>
28+
#include <core/uint256.hpp>
29+
30+
namespace core::coin {
31+
32+
struct WorkView {
33+
nlohmann::json m_data;
34+
std::vector<uint256> m_hashes;
35+
std::time_t m_latency = 0;
36+
};
37+
38+
} // namespace core::coin

src/core/stratum_server.cpp

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ RateMonitor::get_datums_in_last(double dt) const {
6262
/// Static member definition
6363
std::atomic<uint64_t> StratumSession::job_counter_{0};
6464

65-
StratumServer::StratumServer(net::io_context& ioc, const std::string& address, uint16_t port, std::shared_ptr<MiningInterface> mining_interface)
65+
StratumServer::StratumServer(net::io_context& ioc, const std::string& address, uint16_t port, std::shared_ptr<IWorkSource> mining_interface)
6666
: ioc_(ioc)
6767
, acceptor_(ioc)
6868
, mining_interface_(mining_interface)
@@ -102,14 +102,41 @@ bool StratumServer::start()
102102

103103
void StratumServer::stop()
104104
{
105-
if (running_) {
106-
try {
107-
acceptor_.close();
108-
running_ = false;
109-
LOG_INFO << "StratumServer stopped";
110-
} catch (const std::exception& e) {
111-
LOG_ERROR << "Error stopping StratumServer: " << e.what();
105+
if (!running_) return;
106+
107+
try {
108+
// Stop accepting new connections first — no more sessions can spawn
109+
// after this point, so the snapshot below is exhaustive.
110+
boost::system::error_code ec;
111+
acceptor_.cancel(ec);
112+
acceptor_.close(ec);
113+
running_ = false;
114+
115+
// Snapshot + clear the live-sessions set under the mutex, then close
116+
// each one OUTSIDE the lock. cancel_timers() does asio operations
117+
// (timer.cancel + socket.close) that should not run with our
118+
// sessions_mutex_ held — the read-error handler will fire on the
119+
// io_context thread and try to acquire sessions_mutex_ via
120+
// unregister_session.
121+
std::set<std::shared_ptr<StratumSession>> snapshot;
122+
{
123+
std::lock_guard<std::mutex> lk(sessions_mutex_);
124+
snapshot = std::move(sessions_);
125+
sessions_.clear();
112126
}
127+
128+
for (auto& session : snapshot) {
129+
try {
130+
session->shutdown();
131+
} catch (const std::exception& e) {
132+
LOG_WARNING << "StratumSession shutdown threw: " << e.what();
133+
}
134+
}
135+
136+
LOG_INFO << "StratumServer stopped (closed " << snapshot.size()
137+
<< " active session" << (snapshot.size() == 1 ? "" : "s") << ")";
138+
} catch (const std::exception& e) {
139+
LOG_ERROR << "Error stopping StratumServer: " << e.what();
113140
}
114141
}
115142

@@ -275,7 +302,7 @@ void StratumServer::notify_all()
275302
}
276303

277304
/// StratumSession Implementation
278-
StratumSession::StratumSession(tcp::socket socket, std::shared_ptr<MiningInterface> mining_interface,
305+
StratumSession::StratumSession(tcp::socket socket, std::shared_ptr<IWorkSource> mining_interface,
279306
StratumServer* server)
280307
: socket_(std::move(socket))
281308
, mining_interface_(mining_interface)
@@ -917,16 +944,33 @@ nlohmann::json StratumSession::handle_submit(const nlohmann::json& params, const
917944
response["error"] = nlohmann::json::array({20, "Invalid version mask", nullptr});
918945
return response;
919946
}
920-
// Apply: keep non-rolling bits from job, take rolling bits from miner
921-
effective_version = (job.version & ~version_rolling_mask_) | (miner_version_bits & version_rolling_mask_);
947+
// BIP 320: version_bits is the XOR-difference between the miner's
948+
// rolled version and the job version (per spec: "bits set by miner
949+
// to be different from version field given by mining.notify").
950+
// Recovery: effective = job ^ version_bits. ESP-Miner / bitaxe
951+
// firmware sends version_bits this way (asic_result_task.c:60:
952+
// `version_bits = rolled_version ^ active_job->version`). Earlier
953+
// mask check ensures version_bits flips only mask-bits, so XOR
954+
// only modifies the negotiated bits.
955+
//
956+
// Old REPLACE convention `(job & ~mask) | (bits & mask)` silently
957+
// zeroed any job-version bits in the mask region (e.g. BIP 9
958+
// signal bits) before OR'ing — produced a header version that
959+
// bitaxe never hashed, every share rejected at vardiff gate.
960+
effective_version = job.version ^ miner_version_bits;
922961
} catch (...) {
923962
LOG_WARNING << "[Stratum] Invalid version_bits hex from " << username_ << ": " << version_bits;
924963
}
925964
}
926965

927966
// Use gbt_prevhash (BE display hex) — SetHex converts to LE internal,
928967
// matching build_block_from_stratum which the daemon accepts.
929-
double share_difficulty = MiningInterface::calculate_share_difficulty(
968+
// Per-coin PoW dispatch via IWorkSource virtual: LTC delegates to the
969+
// existing static scrypt-based calculate_share_difficulty; BTC's
970+
// BTCWorkSource implements this with SHA256d. Without this dispatch
971+
// every BTC stratum submission gets a garbage scrypt-based diff and
972+
// rejects at the vardiff gate.
973+
double share_difficulty = mining_interface_->compute_share_difficulty(
930974
job.coinb1, job.coinb2,
931975
extranonce1_, extranonce2, ntime, nonce,
932976
effective_version, job.gbt_prevhash, job.nbits, job.merkle_branches);
@@ -937,7 +981,7 @@ nlohmann::json StratumSession::handle_submit(const nlohmann::json& params, const
937981

938982
// Pool share difficulty (for P2P share creation threshold)
939983
double pool_difficulty = 0.0;
940-
uint32_t sb = mining_interface_->m_share_bits.load();
984+
uint32_t sb = mining_interface_->get_share_bits();
941985
if (sb != 0)
942986
pool_difficulty = chain::target_to_difficulty(chain::bits_to_target(sb));
943987

@@ -975,8 +1019,8 @@ nlohmann::json StratumSession::handle_submit(const nlohmann::json& params, const
9751019
snapshot.subsidy = job.subsidy;
9761020
snapshot.witness_commitment_hex = job.witness_commitment_hex;
9771021
snapshot.witness_root = job.witness_root;
978-
snapshot.share_bits = mining_interface_->m_share_bits.load();
979-
snapshot.share_max_bits = mining_interface_->m_share_max_bits.load();
1022+
snapshot.share_bits = mining_interface_->get_share_bits();
1023+
snapshot.share_max_bits = mining_interface_->get_share_max_bits();
9801024
// Pass frozen share fields through to ShareCreationParams
9811025
snapshot.frozen_ref.absheight = job.frozen_absheight;
9821026
snapshot.frozen_ref.abswork = job.frozen_abswork;
@@ -1317,7 +1361,7 @@ void StratumSession::send_notify_work(bool force_clean, const uint256* frozen_be
13171361
// coinbase/tx_data consistency. Overriding them caused GENTX validation
13181362
// failures on p2pool nodes (absheight, bits mismatch).
13191363
if (!cbr.snapshot.merkle_branches.empty()) {
1320-
merkle_branches_vec = cbr.snapshot.merkle_branches;
1364+
merkle_branches_vec = std::move(cbr.snapshot.merkle_branches);
13211365
merkle_branches = nlohmann::json::array();
13221366
for (const auto& h : merkle_branches_vec)
13231367
merkle_branches.push_back(h);
@@ -1340,7 +1384,7 @@ void StratumSession::send_notify_work(bool force_clean, const uint256* frozen_be
13401384
je.coinb1 = coinb1;
13411385
je.coinb2 = coinb2;
13421386
je.version = version_u32;
1343-
je.merkle_branches = merkle_branches_vec;
1387+
je.merkle_branches = std::move(merkle_branches_vec);
13441388
je.gbt_block_nbits = gbt_block_nbits;
13451389
active_jobs_[job_id] = std::move(je);
13461390
}
@@ -1379,7 +1423,10 @@ void StratumSession::send_notify_work(bool force_clean, const uint256* frozen_be
13791423
// Use tx_data from the atomic snapshot — NOT from the potentially stale
13801424
// tmpl fetched at the top of send_notify_work(). This ensures the block
13811425
// body transactions match the witness commitment and merkle branches.
1382-
je.tx_data = cbr.snapshot.tx_data;
1426+
// std::move because cbr is a local of this function and snapshot.tx_data
1427+
// is not referenced again after this assignment — saves a deep copy of
1428+
// the per-tx hex string vector on every notify (Option 2 fix).
1429+
je.tx_data = std::move(cbr.snapshot.tx_data);
13831430
}
13841431

13851432
// VARDIFF: do NOT override per-connection difficulty with pool share_bits.

src/core/stratum_server.hpp

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,18 @@
2020

2121
#include <core/log.hpp>
2222
#include <core/uint256.hpp>
23+
#include <core/stratum_work_source.hpp>
2324
#include <c2pool/hashrate/tracker.hpp>
2425

2526
namespace core {
2627

2728
namespace net = boost::asio;
2829
using tcp = net::ip::tcp;
2930

30-
// Forward declaration — defined in web_server.hpp
31-
class MiningInterface;
31+
// IWorkSource is the abstract interface stratum_server holds (defined in
32+
// core/stratum_work_source.hpp). LTC's MiningInterface and BTC's
33+
// btc::stratum::BTCWorkSource both implement it.
34+
using IWorkSource = core::stratum::IWorkSource;
3235

3336
/// Sliding-window rate monitor matching p2pool's util.math.RateMonitor.
3437
/// Records timestamped work datums for smooth hashrate estimation.
@@ -89,7 +92,7 @@ class StratumSession : public std::enable_shared_from_this<StratumSession>
8992
boost::asio::streambuf buffer_;
9093
std::deque<std::string> write_queue_; // async write queue (non-blocking)
9194
bool writing_ = false; // true while an async_write is in flight
92-
std::shared_ptr<MiningInterface> mining_interface_;
95+
std::shared_ptr<IWorkSource> mining_interface_;
9396
StratumServer* server_ = nullptr; // back-pointer for RateMonitor recording
9497
std::string subscription_id_;
9598
std::string extranonce1_;
@@ -117,7 +120,7 @@ class StratumSession : public std::enable_shared_from_this<StratumSession>
117120
std::string coinb2;
118121
uint32_t version{};
119122
std::vector<std::string> merkle_branches;
120-
std::vector<std::string> tx_data; // raw tx hex from GBT template
123+
std::shared_ptr<const std::vector<std::string>> tx_data; // raw tx hex from GBT template (a1: shared/lazy)
121124
std::string mweb; // MWEB extension data
122125
bool segwit_active{false};
123126
uint256 prev_share_hash; // share chain tip when this job was built
@@ -173,7 +176,7 @@ class StratumSession : public std::enable_shared_from_this<StratumSession>
173176
boost::asio::steady_timer work_push_timer_;
174177

175178
public:
176-
explicit StratumSession(tcp::socket socket, std::shared_ptr<MiningInterface> mining_interface,
179+
explicit StratumSession(tcp::socket socket, std::shared_ptr<IWorkSource> mining_interface,
177180
StratumServer* server = nullptr);
178181
void start();
179182

@@ -206,6 +209,13 @@ class StratumSession : public std::enable_shared_from_this<StratumSession>
206209
// best_share_hash_fn() live. This prevents PPLNS recomputation race when
207210
// best_share changes mid-iteration in notify_all().
208211
void send_notify_work(bool force_clean = false, const uint256* frozen_best_share = nullptr);
212+
213+
// Graceful shutdown: cancel timers + close socket. The pending async_read
214+
// fails with operation_aborted, the read handler runs the standard
215+
// disconnect path (cancel_timers, unregister_stratum_worker, log). Idempotent.
216+
// Called by StratumServer::stop() during process shutdown so no queued
217+
// async handlers reference half-destroyed state when the io_context dies.
218+
void shutdown() { cancel_timers(); }
209219
private:
210220
void start_periodic_work_push();
211221
void schedule_work_push_timer();
@@ -220,7 +230,7 @@ class StratumServer
220230
{
221231
net::io_context& ioc_;
222232
tcp::acceptor acceptor_;
223-
std::shared_ptr<MiningInterface> mining_interface_;
233+
std::shared_ptr<IWorkSource> mining_interface_;
224234
std::string bind_address_;
225235
uint16_t port_;
226236
bool running_;
@@ -243,7 +253,7 @@ class StratumServer
243253
mutable std::mutex cache_mutex_;
244254

245255
public:
246-
StratumServer(net::io_context& ioc, const std::string& address, uint16_t port, std::shared_ptr<MiningInterface> mining_interface);
256+
StratumServer(net::io_context& ioc, const std::string& address, uint16_t port, std::shared_ptr<IWorkSource> mining_interface);
247257
~StratumServer();
248258

249259
bool start();

0 commit comments

Comments
 (0)