Skip to content

Commit 912a06e

Browse files
authored
Merge pull request #92 from frstrtr/dash/pr0-foundation-s1
PR-0 S1: parameterize ltc share path on core::CoinParams
2 parents 91d60dc + 133ae6b commit 912a06e

8 files changed

Lines changed: 279 additions & 177 deletions

File tree

src/c2pool/c2pool_refactored.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4446,7 +4446,7 @@ int main(int argc, char* argv[]) {
44464446

44474447
// V35: convert pubkey_hash to address string (uses raw bytes)
44484448
if (share_version <= 35) {
4449-
params.address = ltc::pubkey_hash_to_address(params.pubkey_hash, params.pubkey_type);
4449+
params.address = ltc::pubkey_hash_to_address(params.pubkey_hash, params.pubkey_type, p2p_node->coin_params());
44504450
}
44514451

44524452
// No P2WPKH reversal — c2pool stores raw witness program
@@ -4553,7 +4553,7 @@ int main(int argc, char* argv[]) {
45534553
}
45544554

45554555
LOG_TRACE << "[ref_hash_fn] computing ref_hash...";
4556-
auto [rh, nonce] = ltc::compute_ref_hash_for_work(params);
4556+
auto [rh, nonce] = ltc::compute_ref_hash_for_work(params, p2p_node->coin_params());
45574557
LOG_TRACE << "[ref_hash_fn] ref_hash computed";
45584558
core::MiningInterface::RefHashResult result;
45594559
result.ref_hash = rh;
@@ -4719,6 +4719,7 @@ int main(int argc, char* argv[]) {
47194719
// Pass frozen fields from template time so ref_hash matches coinbase.
47204720
uint256 share_hash = ltc::create_local_share(
47214721
p2p_node->tracker(),
4722+
p2p_node->coin_params(),
47224723
min_header,
47234724
coinbase,
47244725
p.subsidy,

src/impl/ltc/node.cpp

Lines changed: 29 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -247,10 +247,10 @@ int NodeImpl::get_verified_count() const { return get_tracker_snapshot().verifie
247247
void NodeImpl::send_version(peer_ptr peer)
248248
{
249249
auto rmsg = ltc::message_version::make_raw(
250-
ltc::PoolConfig::ADVERTISED_PROTOCOL_VERSION,
250+
m_tracker.m_params->minimum_protocol_version,
251251
1, // services
252252
addr_t{1, peer->addr()}, // addr_to (the remote)
253-
addr_t{1, NetService{"0.0.0.0", ltc::PoolConfig::P2P_PORT}}, // addr_from (us)
253+
addr_t{1, NetService{"0.0.0.0", m_tracker.m_params->p2p_port}}, // addr_from (us)
254254
m_nonce,
255255
m_software_version,
256256
1, // mode (always 1 for legacy compat)
@@ -304,11 +304,11 @@ std::optional<pool::PeerConnectionType> NodeImpl::handle_version(std::unique_ptr
304304
}
305305

306306
// Reject peers running too-old protocol
307-
if (msg->m_version < ltc::PoolConfig::MINIMUM_PROTOCOL_VERSION)
307+
if (msg->m_version < m_tracker.m_params->minimum_protocol_version)
308308
{
309309
LOG_WARNING << "Peer " << msg->m_addr_from.m_endpoint.to_string()
310310
<< " protocol " << msg->m_version
311-
<< " < minimum " << ltc::PoolConfig::MINIMUM_PROTOCOL_VERSION
311+
<< " < minimum " << m_tracker.m_params->minimum_protocol_version
312312
<< ", disconnecting";
313313
throw std::runtime_error("peer protocol too old");
314314
}
@@ -362,7 +362,7 @@ void NodeImpl::processing_shares(HandleSharesData& data_ref, NetService addr)
362362
try
363363
{
364364
share.ACTION({
365-
obj->m_hash = share_init_verify(*obj, true);
365+
obj->m_hash = share_init_verify(*obj, *m_tracker.m_params, true);
366366
});
367367
}
368368
catch (const std::exception&)
@@ -391,22 +391,21 @@ void NodeImpl::processing_shares_phase2(HandleSharesData& data, NetService addr)
391391
// Non-blocking mutex: if think() holds the exclusive lock on the compute
392392
// thread, queue this batch for processing after think() releases. The IO
393393
// thread never blocks — keepalive timers and network I/O continue.
394-
// Acquire the tracker lock and HOLD it across the entire mutation body below.
395-
// try_to_lock keeps the IO thread non-blocking — if think()/clean holds the
396-
// exclusive lock we queue the batch and return. Once acquired we must NOT
397-
// release until all m_tracker.chain mutations are done: the prior code
398-
// released here and ran the body lock-free, letting the compute-thread
399-
// clean_tracker() exclusive prune (drop_tails) free chain nodes mid-mutation
400-
// → SIGSEGV (kr1z1s LTC/DGB). Released just before run_think() below.
401-
std::unique_lock lock(m_tracker_mutex, std::try_to_lock);
402-
if (!lock.owns_lock()) {
403-
LOG_INFO << "[ASYNC-DEFER] processing_shares_phase2: mutex busy, queuing "
404-
<< data.m_items.size() << " shares from " << addr.to_string()
405-
<< " (pending=" << m_pending_adds.size() + 1 << ")";
406-
m_pending_adds.push_back(PendingShareBatch{
407-
std::make_unique<HandleSharesData>(std::move(data)), addr});
408-
return;
394+
{
395+
std::unique_lock lock(m_tracker_mutex, std::try_to_lock);
396+
if (!lock.owns_lock()) {
397+
LOG_INFO << "[ASYNC-DEFER] processing_shares_phase2: mutex busy, queuing "
398+
<< data.m_items.size() << " shares from " << addr.to_string()
399+
<< " (pending=" << m_pending_adds.size() + 1 << ")";
400+
m_pending_adds.push_back(PendingShareBatch{
401+
std::make_unique<HandleSharesData>(std::move(data)), addr});
402+
return;
403+
}
409404
}
405+
// Lock released — proceed with normal processing.
406+
// No lock needed for the rest: we only enter here when think() is NOT
407+
// running (m_tracker_mutex was available), and ASIO single-thread
408+
// guarantees no other IO handler overlaps.
410409

411410
// Step 1: collect verified shares (skip any that failed verification, hash still null)
412411
std::vector<ShareType> valid_shares;
@@ -567,11 +566,6 @@ void NodeImpl::processing_shares_phase2(HandleSharesData& data, NetService addr)
567566
<< " chain=" << m_tracker.chain.size() << ")";
568567
}
569568

570-
// Release the tracker lock before triggering think(): run_think() posts the
571-
// think+prune job to the compute thread, which needs the exclusive lock. All
572-
// chain mutations above are complete at this point.
573-
lock.unlock();
574-
575569
// Trigger think() after every share batch (p2pool: set_best_share after handle_shares).
576570
// p2pool calls set_best_share() after EVERY batch with new_count > 0 — no size gate.
577571
// 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)
796790
{
797791
// p2pool: set_best_share() → think() synchronously on the reactor thread.
798792
// Use think() for ALL best_share decisions, matching p2pool exactly.
799-
if (share_hash.IsNull())
793+
if (share_hash.IsNull() || !m_tracker.chain.contains(share_hash))
800794
return;
801795

802-
// Both the chain.contains() read AND attempt_verify() run UNDER the tracker
803-
// lock. A bare m_tracker.chain.contains() here (the prior code) raced the
804-
// compute-thread clean_tracker() exclusive prune freeing chain nodes →
805-
// SIGSEGV (kr1z1s LTC/DGB). try_to_lock keeps the IO thread non-blocking; if
806-
// think()/clean holds the lock we skip the inline verify — the share is
807-
// already in-chain and run_think() below will score it next cycle.
796+
// Try inline verify — if think() holds the mutex, defer to next think() cycle.
797+
// The share is already in the chain; think() will verify + score it.
808798
{
809799
std::unique_lock lock(m_tracker_mutex, std::try_to_lock);
810-
if (lock.owns_lock() && m_tracker.chain.contains(share_hash))
800+
if (lock.owns_lock())
811801
m_tracker.attempt_verify(share_hash);
812802
}
813803

@@ -1059,7 +1049,7 @@ void NodeImpl::load_persisted_shares()
10591049
return;
10601050
}
10611051

1062-
const size_t keep_per_head = PoolConfig::chain_length() * 2 + 10;
1052+
const size_t keep_per_head = m_tracker.m_params->chain_length * 2 + 10;
10631053
const size_t total_in_db = all_hashes.size();
10641054

10651055
// Only load the most recent shares (highest height = end of vector)
@@ -1121,7 +1111,7 @@ void NodeImpl::load_persisted_shares()
11211111
g_last_pow_hash = uint256();
11221112
g_last_init_is_block = false;
11231113
uint256 computed_hash;
1124-
share.ACTION({ computed_hash = share_init_verify(*obj, true); });
1114+
share.ACTION({ computed_hash = share_init_verify(*obj, *m_tracker.m_params, true); });
11251115
if (!g_last_pow_hash.IsNull())
11261116
idx->pow_hash = g_last_pow_hash;
11271117
if (!computed_hash.IsNull() && computed_hash != hash) {
@@ -1264,7 +1254,7 @@ void NodeImpl::prune_shares(const uint256& /*best_share*/)
12641254
// - Remove ONE child of qualifying tail per iteration
12651255
// - Loop up to 1000 times (gradual, not bulk)
12661256
// - Also cascade removal to verified
1267-
const auto CL = static_cast<int32_t>(PoolConfig::chain_length());
1257+
const auto CL = static_cast<int32_t>(m_tracker.m_params->chain_length);
12681258
const int32_t min_depth = 2 * CL + 10;
12691259

12701260
for (int iter = 0; iter < 1000; ++iter)
@@ -1603,7 +1593,7 @@ void NodeImpl::heartbeat_log()
16031593
}
16041594
if (!walk_start.IsNull() && m_tracker.chain.contains(walk_start)) {
16051595
int window = std::min(height, static_cast<int>(
1606-
std::min(size_t(3600) / PoolConfig::share_period(), size_t(height))));
1596+
std::min(size_t(3600) / m_tracker.m_params->share_period, size_t(height))));
16071597
if (window > 0) {
16081598
auto walkable = m_tracker.chain.get_height(walk_start);
16091599
auto walk_n = std::min(window, walkable);
@@ -1663,7 +1653,7 @@ void NodeImpl::heartbeat_log()
16631653
try {
16641654
auto aps = m_tracker.get_pool_attempts_per_second(
16651655
m_best_share_hash,
1666-
std::min(height - 1, static_cast<int>(PoolConfig::TARGET_LOOKBEHIND)),
1656+
std::min(height - 1, static_cast<int>(m_tracker.m_params->target_lookbehind)),
16671657
/*min_work=*/false);
16681658
double pool_hs = static_cast<double>(aps.GetLow64());
16691659
double real_pool_hs = (stale_prop < 0.999 && pool_hs > 0)
@@ -1751,7 +1741,7 @@ void NodeImpl::clean_tracker()
17511741

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

17561746
// Step 2: Eat stale heads (p2pool node.py:358-378)
17571747
// Three guards protect useful heads:

src/impl/ltc/node.hpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#pragma once
22

33
#include "config.hpp"
4+
#include "params.hpp"
45
#include "share.hpp"
56
#include "share_tracker.hpp"
67
#include "peer.hpp"
78
#include "messages.hpp"
89

10+
#include <core/coin_params.hpp>
911
#include <pool/node.hpp>
1012
#include <pool/protocol.hpp>
1113
#include <core/message.hpp>
@@ -40,6 +42,7 @@ class NodeImpl : public pool::BaseNode<ltc::Config, ltc::ShareChain, ltc::Peer>
4042
::REQUEST<uint256, peer_ptr, std::vector<uint256>, uint64_t, std::vector<uint256>>;
4143

4244
protected:
45+
core::CoinParams m_coin_params;
4346
ltc::Handler m_handler;
4447
share_getter_t m_share_getter;
4548
ShareTracker m_tracker;
@@ -125,11 +128,16 @@ class NodeImpl : public pool::BaseNode<ltc::Config, ltc::ShareChain, ltc::Peer>
125128

126129
public:
127130
NodeImpl()
128-
: m_share_getter(nullptr,
129-
[](uint256, peer_ptr, std::vector<uint256>, uint64_t, std::vector<uint256>){}) {}
131+
: m_coin_params(ltc::make_coin_params(false)),
132+
m_share_getter(nullptr,
133+
[](uint256, peer_ptr, std::vector<uint256>, uint64_t, std::vector<uint256>){})
134+
{
135+
m_tracker.m_params = &m_coin_params;
136+
}
130137

131138
NodeImpl(boost::asio::io_context* ctx, config_t* config)
132-
: base_t(ctx, config),
139+
: m_coin_params(ltc::make_coin_params(config->m_testnet)),
140+
base_t(ctx, config),
133141
m_share_getter(ctx,
134142
[](uint256 req_id, peer_ptr to_peer,
135143
std::vector<uint256> hashes, uint64_t parents,
@@ -140,6 +148,8 @@ class NodeImpl : public pool::BaseNode<ltc::Config, ltc::ShareChain, ltc::Peer>
140148
},
141149
15) // p2pool p2p.py:80: timeout=15 for share requests
142150
{
151+
m_tracker.m_params = &m_coin_params;
152+
143153
// Seed addr store with hardcoded bootstrap peers
144154
m_addrs.load(config->pool()->m_bootstrap_addrs);
145155
// Randomise our nonce so we detect self-connections
@@ -189,6 +199,7 @@ class NodeImpl : public pool::BaseNode<ltc::Config, ltc::ShareChain, ltc::Peer>
189199
/// or startup code (before compute thread exists).
190200
/// IO-thread code MUST use read_tracker() instead.
191201
ShareTracker& tracker() { return m_tracker; }
202+
const core::CoinParams& coin_params() const { return m_coin_params; }
192203

193204
/// RAII guard for IO-thread tracker reads.
194205
/// - IO thread: acquires shared_lock(try_to_lock). Returns falsy if busy.

src/impl/ltc/params.hpp

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#pragma once
2+
3+
// LTC CoinParams factory: creates a fully populated CoinParams for Litecoin.
4+
// All values must match frstrtr/p2pool-merged-v36 exactly.
5+
6+
#include <core/coin_params.hpp>
7+
#include <core/pow.hpp>
8+
9+
namespace ltc
10+
{
11+
12+
inline core::CoinParams make_coin_params(bool testnet)
13+
{
14+
core::CoinParams p;
15+
16+
// ===== Coin-level (net.PARENT) =====
17+
p.symbol = "LTC";
18+
p.block_period = 150; // 2.5 min
19+
20+
// Address encoding
21+
if (testnet) {
22+
p.address_version = 111;
23+
p.address_p2sh_version = 196;
24+
p.address_p2sh_version2 = 58;
25+
p.bech32_hrp = "tltc1";
26+
} else {
27+
p.address_version = 48;
28+
p.address_p2sh_version = 50;
29+
p.address_p2sh_version2 = 5;
30+
p.bech32_hrp = "ltc1";
31+
}
32+
33+
// PoW
34+
p.pow_func = core::pow::scrypt;
35+
p.block_hash_func = core::pow::sha256d; // LTC identifies blocks by SHA256d
36+
37+
// Subsidy: 50 LTC halving every 840,000 blocks
38+
p.subsidy_func = [](uint32_t height) -> uint64_t {
39+
return uint64_t(50) * 100000000ULL >> ((height + 1) / 840000);
40+
};
41+
42+
p.dust_threshold = 100000; // 0.001 LTC
43+
44+
// Softforks
45+
p.softforks_required = {"bip65", "csv", "segwit", "taproot", "mweb"};
46+
p.segwit_activation_version = 17;
47+
48+
// ===== Pool-level (net) =====
49+
p.p2p_port = 9326;
50+
p.worker_port = 9327;
51+
52+
if (testnet) {
53+
p.share_period = 4;
54+
p.chain_length = 400;
55+
p.real_chain_length = 400;
56+
} else {
57+
p.share_period = 15;
58+
p.chain_length = 8640;
59+
p.real_chain_length = 8640;
60+
}
61+
62+
p.target_lookbehind = 200;
63+
p.spread = 3;
64+
p.minimum_protocol_version = 3600;
65+
p.block_max_size = 1000000;
66+
p.block_max_weight = 4000000;
67+
68+
// Max target
69+
if (testnet) {
70+
// 2^256 / 20 - 1
71+
p.max_target.SetHex("0ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccb");
72+
} else {
73+
p.max_target.SetHex("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
74+
}
75+
76+
// Network identification
77+
p.identifier_hex = "e037d5b8c6923410";
78+
p.prefix_hex = "7208c1a53ef629b0";
79+
p.testnet_identifier_hex = "cca5e24ec6408b1e";
80+
p.testnet_prefix_hex = "ad9614f6466a39cf";
81+
82+
// Bootstrap
83+
p.bootstrap_addrs = {
84+
"ml.toom.im",
85+
"usa.p2p-spb.xyz",
86+
"102.160.209.121",
87+
"5.188.104.245",
88+
"20.127.82.115",
89+
"31.25.241.224",
90+
"20.113.157.65",
91+
"20.106.76.227",
92+
"15.218.180.55",
93+
"173.79.139.224",
94+
"174.60.78.162",
95+
};
96+
97+
// Donation scripts (consensus-critical, must match p2pool data.py)
98+
// Pre-V36: P2PK (OP_PUSHBYTES_65 <pubkey> OP_CHECKSIG)
99+
static constexpr uint8_t DONATION_SCRIPT[] = {
100+
0x41,
101+
0x04, 0xff, 0xd0, 0x3d, 0xe4, 0x4a, 0x6e, 0x11,
102+
0xb9, 0x91, 0x7f, 0x3a, 0x29, 0xf9, 0x44, 0x32,
103+
0x83, 0xd9, 0x87, 0x1c, 0x9d, 0x74, 0x3e, 0xf3,
104+
0x0d, 0x5e, 0xdd, 0xcd, 0x37, 0x09, 0x4b, 0x64,
105+
0xd1, 0xb3, 0xd8, 0x09, 0x04, 0x96, 0xb5, 0x32,
106+
0x56, 0x78, 0x6b, 0xf5, 0xc8, 0x29, 0x32, 0xec,
107+
0x23, 0xc3, 0xb7, 0x4d, 0x9f, 0x05, 0xa6, 0xf9,
108+
0x5a, 0x8b, 0x55, 0x29, 0x35, 0x26, 0x56, 0x66,
109+
0x4b,
110+
0xac
111+
};
112+
// V36+: P2SH 1-of-2 multisig (OP_HASH160 <hash160> OP_EQUAL)
113+
static constexpr uint8_t COMBINED_DONATION_SCRIPT[] = {
114+
0xa9, 0x14,
115+
0x8c, 0x62, 0x72, 0x62, 0x1d, 0x89, 0xe8, 0xfa,
116+
0x52, 0x6d, 0xd8, 0x6a, 0xcf, 0xf6, 0x0c, 0x71,
117+
0x36, 0xbe, 0x8e, 0x85,
118+
0x87
119+
};
120+
121+
p.donation_script_func = [](int64_t share_version) -> std::vector<unsigned char> {
122+
if (share_version >= 36)
123+
return {std::begin(COMBINED_DONATION_SCRIPT), std::end(COMBINED_DONATION_SCRIPT)};
124+
return {std::begin(DONATION_SCRIPT), std::end(DONATION_SCRIPT)};
125+
};
126+
127+
p.current_share_version = 36;
128+
p.is_testnet = testnet;
129+
130+
return p;
131+
}
132+
133+
} // namespace ltc

0 commit comments

Comments
 (0)