Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/c2pool/c2pool_refactored.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
68 changes: 29 additions & 39 deletions src/impl/ltc/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -304,11 +304,11 @@ std::optional<pool::PeerConnectionType> 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");
}
Expand Down Expand Up @@ -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&)
Expand Down Expand Up @@ -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<HandleSharesData>(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<HandleSharesData>(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<ShareType> valid_shares;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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<int32_t>(PoolConfig::chain_length());
const auto CL = static_cast<int32_t>(m_tracker.m_params->chain_length);
const int32_t min_depth = 2 * CL + 10;

for (int iter = 0; iter < 1000; ++iter)
Expand Down Expand Up @@ -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<int>(
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);
Expand Down Expand Up @@ -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<int>(PoolConfig::TARGET_LOOKBEHIND)),
std::min(height - 1, static_cast<int>(m_tracker.m_params->target_lookbehind)),
/*min_work=*/false);
double pool_hs = static_cast<double>(aps.GetLow64());
double real_pool_hs = (stale_prop < 0.999 && pool_hs > 0)
Expand Down Expand Up @@ -1751,7 +1741,7 @@ void NodeImpl::clean_tracker()

// Steps 2-3: Prune (still holding exclusive lock)
auto now_sec = static_cast<int64_t>(std::time(nullptr));
auto CL = static_cast<int32_t>(ltc::PoolConfig::chain_length());
auto CL = static_cast<int32_t>(m_tracker.m_params->chain_length);

// Step 2: Eat stale heads (p2pool node.py:358-378)
// Three guards protect useful heads:
Expand Down
17 changes: 14 additions & 3 deletions src/impl/ltc/node.hpp
Original file line number Diff line number Diff line change
@@ -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 <core/coin_params.hpp>
#include <pool/node.hpp>
#include <pool/protocol.hpp>
#include <core/message.hpp>
Expand Down Expand Up @@ -40,6 +42,7 @@ class NodeImpl : public pool::BaseNode<ltc::Config, ltc::ShareChain, ltc::Peer>
::REQUEST<uint256, peer_ptr, std::vector<uint256>, uint64_t, std::vector<uint256>>;

protected:
core::CoinParams m_coin_params;
ltc::Handler m_handler;
share_getter_t m_share_getter;
ShareTracker m_tracker;
Expand Down Expand Up @@ -125,11 +128,16 @@ class NodeImpl : public pool::BaseNode<ltc::Config, ltc::ShareChain, ltc::Peer>

public:
NodeImpl()
: m_share_getter(nullptr,
[](uint256, peer_ptr, std::vector<uint256>, uint64_t, std::vector<uint256>){}) {}
: m_coin_params(ltc::make_coin_params(false)),
m_share_getter(nullptr,
[](uint256, peer_ptr, std::vector<uint256>, uint64_t, std::vector<uint256>){})
{
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<uint256> hashes, uint64_t parents,
Expand All @@ -140,6 +148,8 @@ class NodeImpl : public pool::BaseNode<ltc::Config, ltc::ShareChain, ltc::Peer>
},
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
Expand Down Expand Up @@ -189,6 +199,7 @@ class NodeImpl : public pool::BaseNode<ltc::Config, ltc::ShareChain, ltc::Peer>
/// 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.
Expand Down
133 changes: 133 additions & 0 deletions src/impl/ltc/params.hpp
Original file line number Diff line number Diff line change
@@ -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 <core/coin_params.hpp>
#include <core/pow.hpp>

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 <pubkey> 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 <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<unsigned char> {
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
Loading
Loading