diff --git a/src/c2pool/c2pool_refactored.cpp b/src/c2pool/c2pool_refactored.cpp index d4052cb9b..4a438e624 100644 --- a/src/c2pool/c2pool_refactored.cpp +++ b/src/c2pool/c2pool_refactored.cpp @@ -4446,7 +4446,7 @@ int main(int argc, char* argv[]) { // V35: convert pubkey_hash to address string (uses raw bytes) if (share_version <= 35) { - params.address = ltc::pubkey_hash_to_address(params.pubkey_hash, params.pubkey_type); + params.address = ltc::pubkey_hash_to_address(params.pubkey_hash, params.pubkey_type, p2p_node->coin_params()); } // No P2WPKH reversal — c2pool stores raw witness program @@ -4553,7 +4553,7 @@ int main(int argc, char* argv[]) { } LOG_TRACE << "[ref_hash_fn] computing ref_hash..."; - auto [rh, nonce] = ltc::compute_ref_hash_for_work(params); + auto [rh, nonce] = ltc::compute_ref_hash_for_work(params, p2p_node->coin_params()); LOG_TRACE << "[ref_hash_fn] ref_hash computed"; core::MiningInterface::RefHashResult result; result.ref_hash = rh; @@ -4719,6 +4719,7 @@ int main(int argc, char* argv[]) { // Pass frozen fields from template time so ref_hash matches coinbase. uint256 share_hash = ltc::create_local_share( p2p_node->tracker(), + p2p_node->coin_params(), min_header, coinbase, p.subsidy, diff --git a/src/impl/ltc/node.cpp b/src/impl/ltc/node.cpp index 2b5b83400..74d17786f 100644 --- a/src/impl/ltc/node.cpp +++ b/src/impl/ltc/node.cpp @@ -247,10 +247,10 @@ int NodeImpl::get_verified_count() const { return get_tracker_snapshot().verifie void NodeImpl::send_version(peer_ptr peer) { auto rmsg = ltc::message_version::make_raw( - ltc::PoolConfig::ADVERTISED_PROTOCOL_VERSION, + m_tracker.m_params->minimum_protocol_version, 1, // services addr_t{1, peer->addr()}, // addr_to (the remote) - addr_t{1, NetService{"0.0.0.0", ltc::PoolConfig::P2P_PORT}}, // addr_from (us) + addr_t{1, NetService{"0.0.0.0", m_tracker.m_params->p2p_port}}, // addr_from (us) m_nonce, m_software_version, 1, // mode (always 1 for legacy compat) @@ -304,11 +304,11 @@ std::optional NodeImpl::handle_version(std::unique_ptr } // Reject peers running too-old protocol - if (msg->m_version < ltc::PoolConfig::MINIMUM_PROTOCOL_VERSION) + if (msg->m_version < m_tracker.m_params->minimum_protocol_version) { LOG_WARNING << "Peer " << msg->m_addr_from.m_endpoint.to_string() << " protocol " << msg->m_version - << " < minimum " << ltc::PoolConfig::MINIMUM_PROTOCOL_VERSION + << " < minimum " << m_tracker.m_params->minimum_protocol_version << ", disconnecting"; throw std::runtime_error("peer protocol too old"); } @@ -362,7 +362,7 @@ void NodeImpl::processing_shares(HandleSharesData& data_ref, NetService addr) try { share.ACTION({ - obj->m_hash = share_init_verify(*obj, true); + obj->m_hash = share_init_verify(*obj, *m_tracker.m_params, true); }); } catch (const std::exception&) @@ -391,22 +391,21 @@ void NodeImpl::processing_shares_phase2(HandleSharesData& data, NetService addr) // Non-blocking mutex: if think() holds the exclusive lock on the compute // thread, queue this batch for processing after think() releases. The IO // thread never blocks — keepalive timers and network I/O continue. - // Acquire the tracker lock and HOLD it across the entire mutation body below. - // try_to_lock keeps the IO thread non-blocking — if think()/clean holds the - // exclusive lock we queue the batch and return. Once acquired we must NOT - // release until all m_tracker.chain mutations are done: the prior code - // released here and ran the body lock-free, letting the compute-thread - // clean_tracker() exclusive prune (drop_tails) free chain nodes mid-mutation - // → SIGSEGV (kr1z1s LTC/DGB). Released just before run_think() below. - std::unique_lock lock(m_tracker_mutex, std::try_to_lock); - if (!lock.owns_lock()) { - LOG_INFO << "[ASYNC-DEFER] processing_shares_phase2: mutex busy, queuing " - << data.m_items.size() << " shares from " << addr.to_string() - << " (pending=" << m_pending_adds.size() + 1 << ")"; - m_pending_adds.push_back(PendingShareBatch{ - std::make_unique(std::move(data)), addr}); - return; + { + std::unique_lock lock(m_tracker_mutex, std::try_to_lock); + if (!lock.owns_lock()) { + LOG_INFO << "[ASYNC-DEFER] processing_shares_phase2: mutex busy, queuing " + << data.m_items.size() << " shares from " << addr.to_string() + << " (pending=" << m_pending_adds.size() + 1 << ")"; + m_pending_adds.push_back(PendingShareBatch{ + std::make_unique(std::move(data)), addr}); + return; + } } + // Lock released — proceed with normal processing. + // No lock needed for the rest: we only enter here when think() is NOT + // running (m_tracker_mutex was available), and ASIO single-thread + // guarantees no other IO handler overlaps. // Step 1: collect verified shares (skip any that failed verification, hash still null) std::vector valid_shares; @@ -567,11 +566,6 @@ void NodeImpl::processing_shares_phase2(HandleSharesData& data, NetService addr) << " chain=" << m_tracker.chain.size() << ")"; } - // Release the tracker lock before triggering think(): run_think() posts the - // think+prune job to the compute thread, which needs the exclusive lock. All - // chain mutations above are complete at this point. - lock.unlock(); - // Trigger think() after every share batch (p2pool: set_best_share after handle_shares). // p2pool calls set_best_share() after EVERY batch with new_count > 0 — no size gate. // think() scores heads and updates best_share + desired set for download_shares. @@ -796,18 +790,14 @@ void NodeImpl::notify_local_share(const uint256& share_hash) { // p2pool: set_best_share() → think() synchronously on the reactor thread. // Use think() for ALL best_share decisions, matching p2pool exactly. - if (share_hash.IsNull()) + if (share_hash.IsNull() || !m_tracker.chain.contains(share_hash)) return; - // Both the chain.contains() read AND attempt_verify() run UNDER the tracker - // lock. A bare m_tracker.chain.contains() here (the prior code) raced the - // compute-thread clean_tracker() exclusive prune freeing chain nodes → - // SIGSEGV (kr1z1s LTC/DGB). try_to_lock keeps the IO thread non-blocking; if - // think()/clean holds the lock we skip the inline verify — the share is - // already in-chain and run_think() below will score it next cycle. + // Try inline verify — if think() holds the mutex, defer to next think() cycle. + // The share is already in the chain; think() will verify + score it. { std::unique_lock lock(m_tracker_mutex, std::try_to_lock); - if (lock.owns_lock() && m_tracker.chain.contains(share_hash)) + if (lock.owns_lock()) m_tracker.attempt_verify(share_hash); } @@ -1059,7 +1049,7 @@ void NodeImpl::load_persisted_shares() return; } - const size_t keep_per_head = PoolConfig::chain_length() * 2 + 10; + const size_t keep_per_head = m_tracker.m_params->chain_length * 2 + 10; const size_t total_in_db = all_hashes.size(); // Only load the most recent shares (highest height = end of vector) @@ -1121,7 +1111,7 @@ void NodeImpl::load_persisted_shares() g_last_pow_hash = uint256(); g_last_init_is_block = false; uint256 computed_hash; - share.ACTION({ computed_hash = share_init_verify(*obj, true); }); + share.ACTION({ computed_hash = share_init_verify(*obj, *m_tracker.m_params, true); }); if (!g_last_pow_hash.IsNull()) idx->pow_hash = g_last_pow_hash; if (!computed_hash.IsNull() && computed_hash != hash) { @@ -1264,7 +1254,7 @@ void NodeImpl::prune_shares(const uint256& /*best_share*/) // - Remove ONE child of qualifying tail per iteration // - Loop up to 1000 times (gradual, not bulk) // - Also cascade removal to verified - const auto CL = static_cast(PoolConfig::chain_length()); + const auto CL = static_cast(m_tracker.m_params->chain_length); const int32_t min_depth = 2 * CL + 10; for (int iter = 0; iter < 1000; ++iter) @@ -1603,7 +1593,7 @@ void NodeImpl::heartbeat_log() } if (!walk_start.IsNull() && m_tracker.chain.contains(walk_start)) { int window = std::min(height, static_cast( - std::min(size_t(3600) / PoolConfig::share_period(), size_t(height)))); + std::min(size_t(3600) / m_tracker.m_params->share_period, size_t(height)))); if (window > 0) { auto walkable = m_tracker.chain.get_height(walk_start); auto walk_n = std::min(window, walkable); @@ -1663,7 +1653,7 @@ void NodeImpl::heartbeat_log() try { auto aps = m_tracker.get_pool_attempts_per_second( m_best_share_hash, - std::min(height - 1, static_cast(PoolConfig::TARGET_LOOKBEHIND)), + std::min(height - 1, static_cast(m_tracker.m_params->target_lookbehind)), /*min_work=*/false); double pool_hs = static_cast(aps.GetLow64()); double real_pool_hs = (stale_prop < 0.999 && pool_hs > 0) @@ -1751,7 +1741,7 @@ void NodeImpl::clean_tracker() // Steps 2-3: Prune (still holding exclusive lock) auto now_sec = static_cast(std::time(nullptr)); - auto CL = static_cast(ltc::PoolConfig::chain_length()); + auto CL = static_cast(m_tracker.m_params->chain_length); // Step 2: Eat stale heads (p2pool node.py:358-378) // Three guards protect useful heads: diff --git a/src/impl/ltc/node.hpp b/src/impl/ltc/node.hpp index d3d921f5c..3e5cd8572 100644 --- a/src/impl/ltc/node.hpp +++ b/src/impl/ltc/node.hpp @@ -1,11 +1,13 @@ #pragma once #include "config.hpp" +#include "params.hpp" #include "share.hpp" #include "share_tracker.hpp" #include "peer.hpp" #include "messages.hpp" +#include #include #include #include @@ -40,6 +42,7 @@ class NodeImpl : public pool::BaseNode ::REQUEST, uint64_t, std::vector>; protected: + core::CoinParams m_coin_params; ltc::Handler m_handler; share_getter_t m_share_getter; ShareTracker m_tracker; @@ -125,11 +128,16 @@ class NodeImpl : public pool::BaseNode public: NodeImpl() - : m_share_getter(nullptr, - [](uint256, peer_ptr, std::vector, uint64_t, std::vector){}) {} + : m_coin_params(ltc::make_coin_params(false)), + m_share_getter(nullptr, + [](uint256, peer_ptr, std::vector, uint64_t, std::vector){}) + { + m_tracker.m_params = &m_coin_params; + } NodeImpl(boost::asio::io_context* ctx, config_t* config) - : base_t(ctx, config), + : m_coin_params(ltc::make_coin_params(config->m_testnet)), + base_t(ctx, config), m_share_getter(ctx, [](uint256 req_id, peer_ptr to_peer, std::vector hashes, uint64_t parents, @@ -140,6 +148,8 @@ class NodeImpl : public pool::BaseNode }, 15) // p2pool p2p.py:80: timeout=15 for share requests { + m_tracker.m_params = &m_coin_params; + // Seed addr store with hardcoded bootstrap peers m_addrs.load(config->pool()->m_bootstrap_addrs); // Randomise our nonce so we detect self-connections @@ -189,6 +199,7 @@ class NodeImpl : public pool::BaseNode /// or startup code (before compute thread exists). /// IO-thread code MUST use read_tracker() instead. ShareTracker& tracker() { return m_tracker; } + const core::CoinParams& coin_params() const { return m_coin_params; } /// RAII guard for IO-thread tracker reads. /// - IO thread: acquires shared_lock(try_to_lock). Returns falsy if busy. diff --git a/src/impl/ltc/params.hpp b/src/impl/ltc/params.hpp new file mode 100644 index 000000000..250808cfd --- /dev/null +++ b/src/impl/ltc/params.hpp @@ -0,0 +1,133 @@ +#pragma once + +// LTC CoinParams factory: creates a fully populated CoinParams for Litecoin. +// All values must match frstrtr/p2pool-merged-v36 exactly. + +#include +#include + +namespace ltc +{ + +inline core::CoinParams make_coin_params(bool testnet) +{ + core::CoinParams p; + + // ===== Coin-level (net.PARENT) ===== + p.symbol = "LTC"; + p.block_period = 150; // 2.5 min + + // Address encoding + if (testnet) { + p.address_version = 111; + p.address_p2sh_version = 196; + p.address_p2sh_version2 = 58; + p.bech32_hrp = "tltc1"; + } else { + p.address_version = 48; + p.address_p2sh_version = 50; + p.address_p2sh_version2 = 5; + p.bech32_hrp = "ltc1"; + } + + // PoW + p.pow_func = core::pow::scrypt; + p.block_hash_func = core::pow::sha256d; // LTC identifies blocks by SHA256d + + // Subsidy: 50 LTC halving every 840,000 blocks + p.subsidy_func = [](uint32_t height) -> uint64_t { + return uint64_t(50) * 100000000ULL >> ((height + 1) / 840000); + }; + + p.dust_threshold = 100000; // 0.001 LTC + + // Softforks + p.softforks_required = {"bip65", "csv", "segwit", "taproot", "mweb"}; + p.segwit_activation_version = 17; + + // ===== Pool-level (net) ===== + p.p2p_port = 9326; + p.worker_port = 9327; + + if (testnet) { + p.share_period = 4; + p.chain_length = 400; + p.real_chain_length = 400; + } else { + p.share_period = 15; + p.chain_length = 8640; + p.real_chain_length = 8640; + } + + p.target_lookbehind = 200; + p.spread = 3; + p.minimum_protocol_version = 3600; + p.block_max_size = 1000000; + p.block_max_weight = 4000000; + + // Max target + if (testnet) { + // 2^256 / 20 - 1 + p.max_target.SetHex("0ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccb"); + } else { + p.max_target.SetHex("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + } + + // Network identification + p.identifier_hex = "e037d5b8c6923410"; + p.prefix_hex = "7208c1a53ef629b0"; + p.testnet_identifier_hex = "cca5e24ec6408b1e"; + p.testnet_prefix_hex = "ad9614f6466a39cf"; + + // Bootstrap + p.bootstrap_addrs = { + "ml.toom.im", + "usa.p2p-spb.xyz", + "102.160.209.121", + "5.188.104.245", + "20.127.82.115", + "31.25.241.224", + "20.113.157.65", + "20.106.76.227", + "15.218.180.55", + "173.79.139.224", + "174.60.78.162", + }; + + // Donation scripts (consensus-critical, must match p2pool data.py) + // Pre-V36: P2PK (OP_PUSHBYTES_65 OP_CHECKSIG) + static constexpr uint8_t DONATION_SCRIPT[] = { + 0x41, + 0x04, 0xff, 0xd0, 0x3d, 0xe4, 0x4a, 0x6e, 0x11, + 0xb9, 0x91, 0x7f, 0x3a, 0x29, 0xf9, 0x44, 0x32, + 0x83, 0xd9, 0x87, 0x1c, 0x9d, 0x74, 0x3e, 0xf3, + 0x0d, 0x5e, 0xdd, 0xcd, 0x37, 0x09, 0x4b, 0x64, + 0xd1, 0xb3, 0xd8, 0x09, 0x04, 0x96, 0xb5, 0x32, + 0x56, 0x78, 0x6b, 0xf5, 0xc8, 0x29, 0x32, 0xec, + 0x23, 0xc3, 0xb7, 0x4d, 0x9f, 0x05, 0xa6, 0xf9, + 0x5a, 0x8b, 0x55, 0x29, 0x35, 0x26, 0x56, 0x66, + 0x4b, + 0xac + }; + // V36+: P2SH 1-of-2 multisig (OP_HASH160 OP_EQUAL) + static constexpr uint8_t COMBINED_DONATION_SCRIPT[] = { + 0xa9, 0x14, + 0x8c, 0x62, 0x72, 0x62, 0x1d, 0x89, 0xe8, 0xfa, + 0x52, 0x6d, 0xd8, 0x6a, 0xcf, 0xf6, 0x0c, 0x71, + 0x36, 0xbe, 0x8e, 0x85, + 0x87 + }; + + p.donation_script_func = [](int64_t share_version) -> std::vector { + if (share_version >= 36) + return {std::begin(COMBINED_DONATION_SCRIPT), std::end(COMBINED_DONATION_SCRIPT)}; + return {std::begin(DONATION_SCRIPT), std::end(DONATION_SCRIPT)}; + }; + + p.current_share_version = 36; + p.is_testnet = testnet; + + return p; +} + +} // namespace ltc diff --git a/src/impl/ltc/share_check.hpp b/src/impl/ltc/share_check.hpp index ef45b861f..317166f85 100644 --- a/src/impl/ltc/share_check.hpp +++ b/src/impl/ltc/share_check.hpp @@ -8,9 +8,11 @@ #include "share_messages.hpp" #include "share_types.hpp" +#include #include #include #include +#include #include #include #include @@ -223,12 +225,13 @@ inline uint256 check_merkle_link(const uint256& tip_hash, const MerkleLink& link // Legacy: networks/network.cpp "init gentx_before_refhash" // Formula: VarStr(DONATION_SCRIPT) + int64(0) + VarStr(0x6a28 + int256(0) + int64(0))[:3] // ============================================================================ -inline std::vector compute_gentx_before_refhash(int64_t share_version) +inline std::vector compute_gentx_before_refhash(int64_t share_version, + const core::CoinParams& params) { std::vector result; // 1. VarStr(DONATION_SCRIPT) - auto donation_script = PoolConfig::get_donation_script(share_version); + auto donation_script = params.donation_script_func(share_version); { PackStream s; BaseScript bs; @@ -280,34 +283,30 @@ inline std::vector compute_gentx_before_refhash(int64_t share_ver // Used for V35 share_data.address field (VarStr). // pubkey_type: 0=P2PKH, 1=P2WPKH, 2=P2SH (same as V36 encoding) // ============================================================================ -inline std::string pubkey_hash_to_address(const uint160& pubkey_hash, uint8_t pubkey_type) +inline std::string pubkey_hash_to_address(const uint160& pubkey_hash, uint8_t pubkey_type, + const core::CoinParams& params) { - bool testnet = PoolConfig::is_testnet; if (pubkey_type == 0) { // P2PKH: Base58Check with version byte - uint8_t ver = testnet ? 0x6f : 0x30; std::vector payload(21); - payload[0] = ver; + payload[0] = params.address_version; std::memcpy(payload.data() + 1, pubkey_hash.data(), 20); return EncodeBase58Check({payload.data(), payload.size()}); } else if (pubkey_type == 1) { // P2WPKH: Bech32 segwit v0 - std::string hrp = testnet ? "tltc" : "ltc"; std::vector prog(20); std::memcpy(prog.data(), pubkey_hash.data(), 20); - return bech32::encode_segwit(hrp, 0, prog); + return bech32::encode_segwit(params.bech32_hrp, 0, prog); } else if (pubkey_type == 2) { // P2SH: Base58Check with version byte - uint8_t ver = testnet ? 0xc4 : 0x32; std::vector payload(21); - payload[0] = ver; + payload[0] = params.address_p2sh_version; std::memcpy(payload.data() + 1, pubkey_hash.data(), 20); return EncodeBase58Check({payload.data(), payload.size()}); } // Fallback: P2PKH - uint8_t ver = testnet ? 0x6f : 0x30; std::vector payload(21); - payload[0] = ver; + payload[0] = params.address_version; std::memcpy(payload.data() + 1, pubkey_hash.data(), 20); return EncodeBase58Check({payload.data(), payload.size()}); } @@ -348,13 +347,14 @@ struct RefHashParams { BaseScript message_data; // V36 PossiblyNoneType(b'', VarStrType()) }; -inline std::pair compute_ref_hash_for_work(const RefHashParams& p) +inline std::pair compute_ref_hash_for_work(const RefHashParams& p, + const core::CoinParams& params) { PackStream ref_stream; // IDENTIFIER { - auto hex = PoolConfig::identifier_hex(); + auto hex = params.active_identifier_hex(); for (size_t i = 0; i + 1 < hex.size(); i += 2) { unsigned char byte = static_cast( std::stoul(hex.substr(i, 2), nullptr, 16)); @@ -474,7 +474,7 @@ inline thread_local uint256 g_last_pow_hash; // scrypt hash of the share header // verification path from legacy Share::init(). // ============================================================================ template -uint256 share_init_verify(const ShareT& share, bool check_pow = true) +uint256 share_init_verify(const ShareT& share, const core::CoinParams& params, bool check_pow = true) { // --- Basic validation --- if (share.m_coinbase.size() < 2 || share.m_coinbase.size() > 100) @@ -507,7 +507,7 @@ uint256 share_init_verify(const ShareT& share, bool check_pow = true) // IDENTIFIER bytes (8 bytes from IDENTIFIER_HEX) { - auto hex = PoolConfig::identifier_hex(); + auto hex = params.active_identifier_hex(); for (size_t i = 0; i + 1 < hex.size(); i += 2) { unsigned char byte = static_cast( @@ -652,7 +652,7 @@ uint256 share_init_verify(const ShareT& share, bool check_pow = true) hash_link_data.insert(hash_link_data.end(), p, p + 4); } - auto gentx_before_refhash = compute_gentx_before_refhash(ver); + auto gentx_before_refhash = compute_gentx_before_refhash(ver, params); // --- check_hash_link → gentx_hash --- uint256 gentx_hash = check_hash_link(share.m_hash_link, hash_link_data, gentx_before_refhash); @@ -717,18 +717,16 @@ uint256 share_init_verify(const ShareT& share, bool check_pow = true) uint256 target = chain::bits_to_target(share.m_bits); if (target.IsNull()) throw std::invalid_argument("share target is zero"); - if (target > PoolConfig::max_target()) + if (target > params.max_target) throw std::invalid_argument("share target exceeds MAX_TARGET"); auto max_target = chain::bits_to_target(share.m_max_bits); if (!max_target.IsNull() && target > max_target) throw std::invalid_argument("share target exceeds max_target — too easy"); - // Compute the scrypt hash of the 80-byte block header - char pow_hash_bytes[32]; - scrypt_1024_1_1_256(reinterpret_cast(header_stream.data()), - pow_hash_bytes); - uint256 pow_hash; - memcpy(pow_hash.begin(), pow_hash_bytes, 32); + // Compute PoW hash using coin-specific function (scrypt for LTC, x11 for Dash, etc.) + auto hdr_pow_span = std::span( + reinterpret_cast(header_stream.data()), header_stream.size()); + uint256 pow_hash = params.pow_func(hdr_pow_span); g_last_pow_hash = pow_hash; // cache for attempt_verify merged check if (pow_hash > target) @@ -932,7 +930,7 @@ inline std::vector get_share_script(const auto* obj) // Reference: frstrtr/p2pool-merged-v36 p2pool/data.py generate_transaction() // ============================================================================ template -uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, bool dump_diag = false, bool v36_active = false) +uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const core::CoinParams& params, bool dump_diag = false, bool v36_active = false) { auto gst_t0 = std::chrono::steady_clock::now(); constexpr int64_t ver = ShareT::version; @@ -971,7 +969,7 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, bool // Without this guard, attempt_verify() (which allows CHAIN_LENGTH+1) can // trigger a PPLNS walk that terminates early, producing wrong coinbase // amounts and causing persistent GENTX-MISMATCH during bootstrap. - auto chain_len = static_cast(PoolConfig::real_chain_length()); + auto chain_len = static_cast(params.real_chain_length); { auto pplns_height = tracker.chain.get_height(prev_hash); auto pplns_last = tracker.chain.get_last(prev_hash); @@ -985,7 +983,7 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, bool // block_target from block header bits (matches Python: self.header['bits'].target) auto block_target = chain::bits_to_target(share.m_min_header.m_bits); auto max_weight = chain::target_to_average_attempts(block_target) - * PoolConfig::SPREAD * 65535; + * params.spread * 65535; // PPLNS formula selected by runtime v36_active (AutoRatchet state), // not compile-time share version. Ref: p2pool data.py:879, work.py:759. @@ -1120,23 +1118,8 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, bool auto gst_t2 = std::chrono::steady_clock::now(); // after amounts // Python: sorted(dests, key=lambda a: (amounts[a], a))[-4000:] // = ascending by (amount, script), keep last 4000 (highest amounts) - // F11: canonical exclude-then-append donation handling (p2pool data.py - // generate_transaction). The per-miner dests list excludes BOTH donation - // scripts; a COMBINED_DONATION_SCRIPT-keyed weight folds into the single - // donation-last output, and any DONATION_SCRIPT-keyed weight is dropped. - const std::vector combined_donation_script( - PoolConfig::COMBINED_DONATION_SCRIPT.begin(), PoolConfig::COMBINED_DONATION_SCRIPT.end()); - const std::vector p2pk_donation_script( - PoolConfig::DONATION_SCRIPT.begin(), PoolConfig::DONATION_SCRIPT.end()); - if (auto _dit = amounts.find(combined_donation_script); _dit != amounts.end()) - donation_amount += _dit->second; - std::vector, uint64_t>> payout_outputs; - payout_outputs.reserve(amounts.size()); - for (auto& _kv : amounts) { - if (_kv.first == combined_donation_script || _kv.first == p2pk_donation_script) - continue; - payout_outputs.emplace_back(_kv.first, _kv.second); - } + std::vector, uint64_t>> payout_outputs( + amounts.begin(), amounts.end()); std::sort(payout_outputs.begin(), payout_outputs.end(), [](const auto& a, const auto& b) { if (a.second != b.second) return a.second < b.second; // asc by amount @@ -1251,7 +1234,7 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, bool // Donation output — V35 shares always use P2PK DONATION_SCRIPT, // V36 shares always use P2SH COMBINED_DONATION_SCRIPT. // Each share was created with the donation script matching its version. - auto donation_script = PoolConfig::get_donation_script(ver); + auto donation_script = params.donation_script_func(ver); write_txout(donation_amount, donation_script); // OP_RETURN commitment: value=0, script = 0x6a28 + ref_hash(32) + last_txout_nonce(8) @@ -1261,7 +1244,7 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, bool // IDENTIFIER bytes { - auto hex = PoolConfig::identifier_hex(); + auto hex = params.active_identifier_hex(); for (size_t i = 0; i + 1 < hex.size(); i += 2) { unsigned char byte = static_cast( @@ -1396,7 +1379,7 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, bool { if constexpr (requires { share.m_hash_link.m_extra_data; }) { - auto gbr = compute_gentx_before_refhash(ver); + auto gbr = compute_gentx_before_refhash(ver, params); // prefix = full coinbase minus last 44 bytes (ref_hash 32 + nonce 8 + locktime 4) size_t prefix_len = tx.size() - 44; std::vector prefix( @@ -1480,7 +1463,7 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, bool // First 5 + last 5 shares in PPLNS walk (for cross-impl comparison) if (!share.m_prev_hash.IsNull() && tracker.chain.contains(share.m_prev_hash)) { auto cl = std::min(tracker.chain.get_height(share.m_prev_hash), - static_cast(PoolConfig::real_chain_length())); + static_cast(params.real_chain_length)); LOG_WARNING << "[GENTX-DIAG] PPLNS walk_count=" << cl << " total_weight=" << total_weight.GetHex() << " total_don_weight=" << total_donation_weight.GetHex() @@ -1561,7 +1544,7 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, bool // ============================================================================ template std::string verify_merged_coinbase_commitment( - const ShareT& share, TrackerT& tracker) + const ShareT& share, TrackerT& tracker, const core::CoinParams& params) { if constexpr (ShareT::version < 36) return {}; @@ -1576,14 +1559,14 @@ std::string verify_merged_coinbase_commitment( if (share.m_prev_hash.IsNull() || !tracker.chain.contains(share.m_prev_hash)) return {}; auto height = tracker.chain.get_height(share.m_prev_hash); - if (height < static_cast(PoolConfig::real_chain_length())) + if (height < static_cast(params.real_chain_length)) return {}; // Insufficient depth — skip (match Python behavior) auto block_target = chain::bits_to_target(share.m_bits); auto max_weight = chain::target_to_average_attempts(block_target) - * 65535 * PoolConfig::SPREAD; + * 65535 * params.spread; int32_t chain_len = std::min(height, - static_cast(PoolConfig::real_chain_length())); + static_cast(params.real_chain_length)); // Parse mm_data from LTC coinbase scriptSig const auto& coinbase = share.m_coinbase.m_data; @@ -1627,7 +1610,7 @@ std::string verify_merged_coinbase_commitment( continue; // No V36 shares → can't verify // Build payout list (same logic as payout_provider) - auto donation_script = PoolConfig::get_donation_script(36); + auto donation_script = params.donation_script_func(36); uint64_t coinbase_value = info.m_coinbase_value; // Convert weights to integer payouts @@ -1710,7 +1693,8 @@ template bool share_check(const ShareT& share, const uint256& share_hash, const uint256& gentx_hash, - TrackerT& tracker) + TrackerT& tracker, + const core::CoinParams& params) { // 1. Timestamp check — must not be more than 600s in the future auto now = static_cast( @@ -1730,7 +1714,7 @@ bool share_check(const ShareT& share, // canonical 60% switch rule is now the ONLY version gate. AutoRatchet stays // count-based and does not gate peer shares here. { - auto chain_length = static_cast(PoolConfig::chain_length()); + auto chain_length = static_cast(params.chain_length); if (!share.m_prev_hash.IsNull() && tracker.chain.contains(share.m_prev_hash)) { // Parent's share version (the type the incoming share must legally follow) @@ -1798,7 +1782,7 @@ bool share_check(const ShareT& share, bool v36_active = (share_ver >= 36); if (!share.m_prev_hash.IsNull() && tracker.chain.contains(share.m_prev_hash)) { - uint256 expected_gentx = generate_share_transaction(share, tracker, false, v36_active); + uint256 expected_gentx = generate_share_transaction(share, tracker, params, false, v36_active); if (expected_gentx != gentx_hash) { LOG_WARNING << "GENTX-MISMATCH detail:" @@ -1812,7 +1796,7 @@ bool share_check(const ShareT& share, auto chain_len = std::min( tracker.chain.get_height(share.m_prev_hash), - static_cast(PoolConfig::real_chain_length())); + static_cast(params.real_chain_length)); // V35: log grandparent + height-1 window info uint256 gp_hash; if (tracker.chain.contains(share.m_prev_hash)) @@ -1822,7 +1806,7 @@ bool share_check(const ShareT& share, << " v35_walk=" << v35_walk << " grandparent=" << (gp_hash.IsNull() ? "null" : gp_hash.GetHex().substr(0,16)) << " prev_height=" << tracker.chain.get_height(share.m_prev_hash) - << " real_chain_length=" << PoolConfig::real_chain_length(); + << " real_chain_length=" << params.real_chain_length; // Compare share target: what c2pool computes vs what the share has { @@ -1831,7 +1815,7 @@ bool share_check(const ShareT& share, auto [cst_max_bits, cst_bits] = tracker.compute_share_target( share.m_prev_hash, share.m_timestamp, uint256()); auto share_aps = tracker.get_pool_attempts_per_second( - share.m_prev_hash, PoolConfig::TARGET_LOOKBEHIND, true); + share.m_prev_hash, params.target_lookbehind, true); LOG_WARNING << "[GENTX-TARGET] share_bits=0x" << std::hex << share.m_bits << " share_max_bits=0x" << share.m_max_bits << " c2pool_bits=0x" << cst_bits @@ -1840,7 +1824,7 @@ bool share_check(const ShareT& share, << " prev=" << share.m_prev_hash.GetHex().substr(0,16); // APS walk component dump for cross-impl comparison - int32_t dist = PoolConfig::TARGET_LOOKBEHIND; + int32_t dist = params.target_lookbehind; auto near_hash = share.m_prev_hash; auto far_hash = near_hash; int32_t actual_dist = 0; @@ -1898,12 +1882,12 @@ bool share_check(const ShareT& share, if (s_diag_count++ < 5) { LOG_WARNING << "[GENTX-DIAG] Re-running generate_share_transaction with full dump (v36_active=" << v36_active << "):"; - generate_share_transaction(share, tracker, true, v36_active); + generate_share_transaction(share, tracker, params, true, v36_active); // Per-share PPLNS walk dump — compare with p2pool's [PARENT-PPLNS] output. // Uses same parameters as generate_share_transaction's V36 path. if (v36_active && !share.m_prev_hash.IsNull()) { - auto diag_chain_len = static_cast(PoolConfig::real_chain_length()); + auto diag_chain_len = static_cast(params.real_chain_length); LOG_WARNING << "[GENTX-DIAG] Per-share V36 PPLNS walk from prev=" << share.m_prev_hash.GetHex().substr(0, 16) << ":"; tracker.dump_v36_pplns_walk(share.m_prev_hash, diag_chain_len); @@ -1949,7 +1933,7 @@ bool share_check(const ShareT& share, // Verifies the actual merged coinbase matches canonical PPLNS construction. if constexpr (ShareT::version >= 36) { - auto mcv_err = verify_merged_coinbase_commitment(share, tracker); + auto mcv_err = verify_merged_coinbase_commitment(share, tracker, params); if (!mcv_err.empty()) throw std::invalid_argument("merged coinbase verification: " + mcv_err); } @@ -1964,14 +1948,14 @@ bool share_check(const ShareT& share, // Returns the computed share hash. // ============================================================================ template -uint256 verify_share(const ShareT& share, TrackerT& tracker) +uint256 verify_share(const ShareT& share, TrackerT& tracker, const core::CoinParams& params) { auto vt0 = std::chrono::steady_clock::now(); // share_init_verify computes gentx_hash along the way — we need it // for the GenerateShareTransaction comparison in share_check. // Skip scrypt PoW re-check when hash was already computed in Phase 1 // (processing_shares offloads scrypt to m_verify_pool; no need to repeat). - uint256 hash = share_init_verify(share, share.m_hash.IsNull()); + uint256 hash = share_init_verify(share, params, share.m_hash.IsNull()); auto vt1 = std::chrono::steady_clock::now(); // Verify recomputed hash matches stored hash (informational). @@ -1988,12 +1972,12 @@ uint256 verify_share(const ShareT& share, TrackerT& tracker) // Re-derive gentx_hash for the check phase constexpr int64_t ver = ShareT::version; - auto gentx_before_refhash = compute_gentx_before_refhash(ver); + auto gentx_before_refhash = compute_gentx_before_refhash(ver, params); // Rebuild ref_hash + hash_link_data the same way init does PackStream ref_stream; { - auto hex = PoolConfig::identifier_hex(); + auto hex = params.active_identifier_hex(); for (size_t i = 0; i + 1 < hex.size(); i += 2) { unsigned char byte = static_cast( @@ -2118,7 +2102,7 @@ uint256 verify_share(const ShareT& share, TrackerT& tracker) } } - share_check(share, hash, gentx_hash, tracker); + share_check(share, hash, gentx_hash, tracker, params); { auto vt2 = std::chrono::steady_clock::now(); auto init_us = std::chrono::duration_cast(vt1 - vt0).count(); @@ -2148,6 +2132,7 @@ uint256 verify_share(const ShareT& share, TrackerT& tracker) template uint256 create_local_share_v35( TrackerT& tracker, + const core::CoinParams& params, const coin::SmallBlockHeaderType& min_header, const BaseScript& coinbase, uint64_t subsidy, @@ -2215,7 +2200,7 @@ uint256 create_local_share_v35( } else if (payout_script.size() >= 20) { std::memcpy(pubkey_hash.data(), payout_script.data(), 20); } - std::string addr_str = pubkey_hash_to_address(pubkey_hash, pubkey_type); + std::string addr_str = pubkey_hash_to_address(pubkey_hash, pubkey_type, params); share.m_address.m_data.assign(addr_str.begin(), addr_str.end()); { auto roundtrip = core::address_to_script(addr_str); @@ -2304,7 +2289,7 @@ uint256 create_local_share_v35( // --- Compute ref_hash (V35 format) --- PackStream ref_stream; { - auto hex = PoolConfig::identifier_hex(); + auto hex = params.active_identifier_hex(); for (size_t i = 0; i + 1 < hex.size(); i += 2) { unsigned char byte = static_cast( std::stoul(hex.substr(i, 2), nullptr, 16)); @@ -2355,7 +2340,7 @@ uint256 create_local_share_v35( } // --- Derive hash_link (V35: HashLinkType, no extra_data) --- - auto gentx_before_refhash = compute_gentx_before_refhash(int64_t(35)); + auto gentx_before_refhash = compute_gentx_before_refhash(int64_t(35), params); std::vector coinbase_bytes_for_hashlink; if (!actual_coinbase_bytes.empty()) { @@ -2378,10 +2363,10 @@ uint256 create_local_share_v35( if (!pplns_start.IsNull()) { auto height = tracker.chain.get_height(prev_share); int32_t max_shares = std::max(0, std::min(height, - static_cast(PoolConfig::real_chain_length())) - 1); + static_cast(params.real_chain_length)) - 1); auto block_target = chain::bits_to_target(share.m_min_header.m_bits); auto desired_weight = chain::target_to_average_attempts(block_target) - * uint288(PoolConfig::SPREAD) * uint288(65535); + * uint288(params.spread) * uint288(65535); // Flat weight accumulation (not decayed) auto result = tracker.get_cumulative_weights(pplns_start, max_shares, desired_weight); weights = std::move(result.weights); @@ -2406,23 +2391,8 @@ uint256 create_local_share_v35( // V35: no minimum donation enforcement (unlike v36) uint64_t donation_amount = (subsidy > sum_amounts) ? (subsidy - sum_amounts) : 0; - // F11: canonical exclude-then-append donation handling (p2pool data.py - // generate_transaction). The per-miner dests list excludes BOTH donation - // scripts; a COMBINED_DONATION_SCRIPT-keyed weight folds into the single - // donation-last output, and any DONATION_SCRIPT-keyed weight is dropped. - const std::vector combined_donation_script( - PoolConfig::COMBINED_DONATION_SCRIPT.begin(), PoolConfig::COMBINED_DONATION_SCRIPT.end()); - const std::vector p2pk_donation_script( - PoolConfig::DONATION_SCRIPT.begin(), PoolConfig::DONATION_SCRIPT.end()); - if (auto _dit = amounts.find(combined_donation_script); _dit != amounts.end()) - donation_amount += _dit->second; - std::vector, uint64_t>> payout_outputs; - payout_outputs.reserve(amounts.size()); - for (auto& _kv : amounts) { - if (_kv.first == combined_donation_script || _kv.first == p2pk_donation_script) - continue; - payout_outputs.emplace_back(_kv.first, _kv.second); - } + std::vector, uint64_t>> payout_outputs( + amounts.begin(), amounts.end()); std::sort(payout_outputs.begin(), payout_outputs.end(), [](const auto& a, const auto& b) { if (a.second != b.second) return a.second < b.second; @@ -2457,7 +2427,7 @@ uint256 create_local_share_v35( } for (auto& [script, amount] : payout_outputs) write_txout(amount, script); // V35: use pre-V36 DONATION_SCRIPT - write_txout(donation_amount, PoolConfig::get_donation_script(int64_t(35))); + write_txout(donation_amount, params.donation_script_func(int64_t(35))); { std::vector op; op.push_back(0x6a); op.push_back(0x28); op.insert(op.end(), ref_hash.data(), ref_hash.data() + 32); uint64_t n = share.m_last_txout_nonce; auto* p = reinterpret_cast(&n); @@ -2515,10 +2485,7 @@ uint256 create_local_share_v35( { uint256 target = chain::bits_to_target(share.m_bits); if (!target.IsNull()) { - char pow_bytes[32]; - scrypt_1024_1_1_256(reinterpret_cast(hdr_span.data()), pow_bytes); - uint256 pow_hash; - memcpy(pow_hash.begin(), pow_bytes, 32); + uint256 pow_hash = params.pow_func(hdr_span); if (pow_hash > target) { return uint256(); // didn't meet share target @@ -2565,6 +2532,7 @@ uint256 create_local_share_v35( template uint256 create_local_share( TrackerT& tracker, + const core::CoinParams& params, const coin::SmallBlockHeaderType& min_header, const BaseScript& coinbase, uint64_t subsidy, @@ -2598,7 +2566,7 @@ uint256 create_local_share( // V35 path: delegate to version-specific implementation if (share_version <= 35) return create_local_share_v35( - tracker, min_header, coinbase, subsidy, prev_share, merkle_branches, + tracker, params, min_header, coinbase, subsidy, prev_share, merkle_branches, payout_script, donation, stale_info, segwit_active, witness_commitment_hex, actual_coinbase_bytes, witness_root, override_max_bits, override_bits, frozen_absheight, frozen_abswork, frozen_far_share_hash, frozen_timestamp, @@ -2814,7 +2782,7 @@ uint256 create_local_share( // --- Compute the ref_hash --- PackStream ref_stream; { - auto hex = PoolConfig::identifier_hex(); + auto hex = params.active_identifier_hex(); for (size_t i = 0; i + 1 < hex.size(); i += 2) { unsigned char byte = static_cast( std::stoul(hex.substr(i, 2), nullptr, 16)); @@ -2914,7 +2882,7 @@ uint256 create_local_share( // directly. This eliminates the race where the share chain changes between // template creation and share submission, which caused GENTX mismatches // and p2pool peer bans. - auto gentx_before_refhash = compute_gentx_before_refhash(int64_t(36)); + auto gentx_before_refhash = compute_gentx_before_refhash(int64_t(36), params); std::vector coinbase_bytes_for_hashlink; if (!actual_coinbase_bytes.empty()) { @@ -2930,10 +2898,10 @@ uint256 create_local_share( if (!prev_share.IsNull() && tracker.chain.contains(prev_share)) { // Pass REAL_CHAIN_LENGTH — walk naturally stops at chain end. - auto chain_len = static_cast(PoolConfig::real_chain_length()); + auto chain_len = static_cast(params.real_chain_length); auto block_target = chain::bits_to_target(share.m_min_header.m_bits); auto max_weight = chain::target_to_average_attempts(block_target) - * PoolConfig::SPREAD * 65535; + * params.spread * 65535; auto result = tracker.get_v36_decayed_cumulative_weights(prev_share, chain_len, max_weight); weights = std::move(result.weights); total_weight = result.total_weight; @@ -2963,23 +2931,8 @@ uint256 create_local_share( } } - // F11: canonical exclude-then-append donation handling (p2pool data.py - // generate_transaction). The per-miner dests list excludes BOTH donation - // scripts; a COMBINED_DONATION_SCRIPT-keyed weight folds into the single - // donation-last output, and any DONATION_SCRIPT-keyed weight is dropped. - const std::vector combined_donation_script( - PoolConfig::COMBINED_DONATION_SCRIPT.begin(), PoolConfig::COMBINED_DONATION_SCRIPT.end()); - const std::vector p2pk_donation_script( - PoolConfig::DONATION_SCRIPT.begin(), PoolConfig::DONATION_SCRIPT.end()); - if (auto _dit = amounts.find(combined_donation_script); _dit != amounts.end()) - donation_amount += _dit->second; - std::vector, uint64_t>> payout_outputs; - payout_outputs.reserve(amounts.size()); - for (auto& _kv : amounts) { - if (_kv.first == combined_donation_script || _kv.first == p2pk_donation_script) - continue; - payout_outputs.emplace_back(_kv.first, _kv.second); - } + std::vector, uint64_t>> payout_outputs( + amounts.begin(), amounts.end()); std::sort(payout_outputs.begin(), payout_outputs.end(), [](const auto& a, const auto& b) { if (a.second != b.second) return a.second < b.second; @@ -3013,7 +2966,7 @@ uint256 create_local_share( write_txout(0, wscript); } for (auto& [script, amount] : payout_outputs) write_txout(amount, script); - write_txout(donation_amount, PoolConfig::get_donation_script(int64_t(36))); + write_txout(donation_amount, params.donation_script_func(int64_t(36))); { std::vector op; op.push_back(0x6a); op.push_back(0x28); op.insert(op.end(), ref_hash.data(), ref_hash.data() + 32); uint64_t n = share.m_last_txout_nonce; auto* p = reinterpret_cast(&n); @@ -3178,10 +3131,7 @@ uint256 create_local_share( { uint256 target = chain::bits_to_target(share.m_bits); if (!target.IsNull()) { - char pow_bytes[32]; - scrypt_1024_1_1_256(reinterpret_cast(hdr_span.data()), pow_bytes); - uint256 pow_hash; - memcpy(pow_hash.begin(), pow_bytes, 32); + uint256 pow_hash = params.pow_func(hdr_span); if (pow_hash > target) { // Expected: most stratum pseudoshares don't meet share target @@ -3226,7 +3176,7 @@ uint256 create_local_share( // with this ref_hash produces the same gentx_hash as direct Hash(coinbase), // peers will accept the share. { - auto gentx_before_refhash_xc = compute_gentx_before_refhash(int64_t(36)); + auto gentx_before_refhash_xc = compute_gentx_before_refhash(int64_t(36), params); // Use the ref_hash from the coinbase (same as what hash_link was built with) std::vector xc_data; @@ -3330,7 +3280,7 @@ uint256 create_local_share( { static int xcheck_count = 0; if (true) { // Always cross-check (was: xcheck_count < 5) - uint256 verify_hash = generate_share_transaction(*heap_share, tracker, true, (MergedMiningShare::version >= 36)); + uint256 verify_hash = generate_share_transaction(*heap_share, tracker, params, true, (MergedMiningShare::version >= 36)); bool xcheck_ok = (verify_hash == gentx_hash_for_header); if (xcheck_ok) { LOG_INFO << "[Pool] Cross-check PASSED"; diff --git a/src/impl/ltc/share_tracker.hpp b/src/impl/ltc/share_tracker.hpp index f5140b225..11ea48fe3 100644 --- a/src/impl/ltc/share_tracker.hpp +++ b/src/impl/ltc/share_tracker.hpp @@ -23,6 +23,7 @@ inline uint64_t mul128_shift(uint64_t a, uint64_t b, unsigned shift) { #include "config_pool.hpp" #include +#include #include #include #include @@ -317,6 +318,11 @@ class ShareTracker ShareChain chain; ShareChain verified; + // Coin parameters (non-owning). Set by the owning ltc::NodeImpl right + // after construction (m_tracker.m_params = &m_coin_params). Drives the + // parameterized verify_share() and protocol-version lookups (PR-0 S1). + const core::CoinParams* m_params = nullptr; + // Set by think() Phase 2 when verification budget is exhausted. // Checked by run_think() to schedule a deferred continuation. @@ -472,7 +478,7 @@ class ShareTracker auto t0 = std::chrono::steady_clock::now(); auto& share_var = chain.get_share(share_hash); share_var.ACTION({ - auto computed_hash = verify_share(*obj, *this); + auto computed_hash = verify_share(*obj, *this, *m_params); (void)computed_hash; }); { diff --git a/test/test_hash_link.cpp b/test/test_hash_link.cpp index b072bd27f..4e75cedfc 100644 --- a/test/test_hash_link.cpp +++ b/test/test_hash_link.cpp @@ -13,11 +13,16 @@ #include #include #include +#include #include #include #include +// LTC CoinParams for compute_gentx_before_refhash(); these are byte-layout +// tests and only use params.donation_script_func, which is network-independent. +static const core::CoinParams g_hashlink_params = ltc::make_coin_params(/*testnet=*/false); + namespace { // Build a byte vector of given size with sequential values @@ -152,7 +157,7 @@ TEST(HashLink, RoundTripRealisticCoinbase) { auto coinbase = make_bytes(250, 0x01); // compute_gentx_before_refhash would give ~83 bytes - auto gentx_before_refhash = ltc::compute_gentx_before_refhash(int64_t(36)); + auto gentx_before_refhash = ltc::compute_gentx_before_refhash(int64_t(36), g_hashlink_params); // For the test, we must ensure the prefix ends with gentx_before_refhash. // Place gentx_before_refhash at the end of the prefix (before suffix). @@ -301,7 +306,7 @@ TEST(HashLink, ProductionPathCoinbase) { // Donation output: value(8) + VarStr(COMBINED_DONATION_SCRIPT) // OP_RETURN output: value(8=0) + 0x2a + 0x6a + 0x28 + ref_hash(32) + nonce(8) - auto gentx_before_refhash = ltc::compute_gentx_before_refhash(int64_t(36)); + auto gentx_before_refhash = ltc::compute_gentx_before_refhash(int64_t(36), g_hashlink_params); ASSERT_GT(gentx_before_refhash.size(), 0u); // Print gentx_before_refhash for debugging diff --git a/test/test_threading.cpp b/test/test_threading.cpp index 80c01bc4e..eb610c846 100644 --- a/test/test_threading.cpp +++ b/test/test_threading.cpp @@ -47,6 +47,7 @@ #include #include +#include #include #include #include @@ -90,6 +91,11 @@ static ltc::ShareType load_test_share() return ltc::load_share(rshare, NetService{"0.0.0.0", 0}); } +// LTC testnet CoinParams for the share-verify path (the loaded share is a +// real V36 *testnet* share). const + namespace-scope so the threaded test +// below reads it concurrently without capturing it per-lambda. +static const core::CoinParams g_test_params = ltc::make_coin_params(/*testnet=*/true); + // ─── Test 1: scrypt is controlled by check_pow flag ────────────────────────── TEST(VerifyShareThreading, ScryptControlledByCheckPowFlag) @@ -116,7 +122,7 @@ TEST(VerifyShareThreading, ScryptControlledByCheckPowFlag) auto t_fast_start = std::chrono::steady_clock::now(); for (int i = 0; i < SCRYPT_REPS; i++) { - share.ACTION({ ltc::share_init_verify(*obj, false); }); + share.ACTION({ ltc::share_init_verify(*obj, g_test_params, false); }); } auto t_fast_us = std::chrono::duration_cast( std::chrono::steady_clock::now() - t_fast_start).count(); @@ -145,7 +151,7 @@ TEST(VerifyShareThreading, VerifyShareUsesPresetHashToSkipScrypt) bool phase1_ok = true; try { share.ACTION({ - obj->m_hash = ltc::share_init_verify(*obj, true); + obj->m_hash = ltc::share_init_verify(*obj, g_test_params, true); phase1_hash = obj->m_hash; }); } catch (const std::exception& e) { @@ -153,7 +159,7 @@ TEST(VerifyShareThreading, VerifyShareUsesPresetHashToSkipScrypt) // stricter than the hex data meets. Fall back to check_pow=false. phase1_ok = false; share.ACTION({ - obj->m_hash = ltc::share_init_verify(*obj, false); + obj->m_hash = ltc::share_init_verify(*obj, g_test_params, false); phase1_hash = obj->m_hash; }); } @@ -172,7 +178,7 @@ TEST(VerifyShareThreading, VerifyShareUsesPresetHashToSkipScrypt) // Result must equal the stored hash (SHA256d is deterministic). uint256 verify_hash; share.ACTION({ - verify_hash = ltc::share_init_verify(*obj, obj->m_hash.IsNull()); + verify_hash = ltc::share_init_verify(*obj, g_test_params, obj->m_hash.IsNull()); }); EXPECT_EQ(verify_hash, phase1_hash) @@ -193,7 +199,7 @@ TEST(VerifyShareThreading, VerifyShareUsesPresetHashToSkipScrypt) for (int i = 0; i < N; i++) { share.ACTION({ - ltc::share_init_verify(*obj, obj->m_hash.IsNull()); // check_pow=false + ltc::share_init_verify(*obj, g_test_params, obj->m_hash.IsNull()); // check_pow=false }); } auto fast_us = std::chrono::duration_cast( @@ -342,7 +348,7 @@ TEST(VerifyShareThreading, ParallelShareInitVerifyDeterministic) { shares[i].ACTION({ // Use check_pow=false to avoid PoW target mismatch on testnet data - results[i] = ltc::share_init_verify(*obj, false); + results[i] = ltc::share_init_verify(*obj, g_test_params, false); }); }); }