@@ -62,7 +62,7 @@ RateMonitor::get_datums_in_last(double dt) const {
6262// / Static member definition
6363std::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
103103void 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.
0 commit comments