Skip to content

Commit c9c6d77

Browse files
authored
PR-0: Foundation (5-file shared-base squash, GPG-signed 4d9b57d) (#65)
* PR-0: bitcoin_family shared foundation (WebServer coin-driven API + address_validator) Net shared-base delta over master collapsed from the 118-commit foundation: 5 files / +234 -29. socket.hpp + base_p2p_messages.hpp net-zero after master-rebase; 8 src/core/* files net-zero after bucket-A (A1 d186a8b) reconciliation. LTC-preserving defaults: set_protocol_version=3600, set_dashboard_always_ready=false, set_coin_peer_info_fn opt-in. Refs PR #43 umbrella. * PR-0 fixup: confine 3 stat-refactor deltas to DASH; preserve LTC/DOGE ltc-doge live-diff (NEEDS-CHANGE) found three LTC/DOGE-affecting deltas that rode along the rest_local_stats/update_stat_log refactor, outside the LTC-preserving-defaults framing: D1 rest_local_stats: block_value_payments regressed from block_value to 0 for LTC and DOGE. Gate payment_amount to DASH; LTC/DOGE keep block_value. D2 rest_version_signaling: returned {} for DOGE (v36, previously populated). Guard now admits LITECOIN and DOGECOIN, excludes only non-v36 chains. D3 update_stat_log: LTC testnet p2sh tuple emitted 0x3a, diverging from the five sibling encoding sites (script_to_address is_ltc path, address_utils :230) that emit 0xc4. Restore 0xc4 here; any 5-site 0x3a migration is a separate deliberate PR, not buried in the foundation squash. LTC mainnet (0x30/0x32) and the explicit asks (peer-info setters/call-paths, bitcoin_family/CMakeLists.txt inert) remain byte-for-byte preserved.
1 parent 3573ff5 commit c9c6d77

5 files changed

Lines changed: 234 additions & 29 deletions

File tree

src/core/address_validator.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ BlockchainAddressValidator::BlockchainAddressValidator()
1919
initialize_monero_configs();
2020
initialize_zcash_configs();
2121
initialize_dogecoin_configs();
22+
initialize_dash_configs();
2223
}
2324

2425
BlockchainAddressValidator::BlockchainAddressValidator(Blockchain primary_blockchain, Network primary_network)
@@ -30,6 +31,7 @@ BlockchainAddressValidator::BlockchainAddressValidator(Blockchain primary_blockc
3031
initialize_monero_configs();
3132
initialize_zcash_configs();
3233
initialize_dogecoin_configs();
34+
initialize_dash_configs();
3335
}
3436

3537
void BlockchainAddressValidator::initialize_litecoin_configs() {
@@ -166,6 +168,27 @@ void BlockchainAddressValidator::initialize_dogecoin_configs() {
166168
m_configs[Blockchain::DOGECOIN][Network::MAINNET] = doge_mainnet;
167169
}
168170

171+
void BlockchainAddressValidator::initialize_dash_configs() {
172+
BlockchainConfig dash_mainnet;
173+
dash_mainnet.blockchain = Blockchain::DASH;
174+
dash_mainnet.network = Network::MAINNET;
175+
dash_mainnet.p2pkh_versions = {76}; // X addresses
176+
dash_mainnet.p2sh_versions = {16}; // 7 addresses
177+
dash_mainnet.min_length = 25;
178+
dash_mainnet.max_length = 34;
179+
180+
BlockchainConfig dash_testnet;
181+
dash_testnet.blockchain = Blockchain::DASH;
182+
dash_testnet.network = Network::TESTNET;
183+
dash_testnet.p2pkh_versions = {140}; // y addresses
184+
dash_testnet.p2sh_versions = {19}; // 8/9 addresses
185+
dash_testnet.min_length = 25;
186+
dash_testnet.max_length = 34;
187+
188+
m_configs[Blockchain::DASH][Network::MAINNET] = dash_mainnet;
189+
m_configs[Blockchain::DASH][Network::TESTNET] = dash_testnet;
190+
}
191+
169192
AddressValidationResult BlockchainAddressValidator::validate_address(const std::string& address) const {
170193
// Use strict validation for the primary blockchain/network
171194
return validate_address_strict(address);
@@ -533,6 +556,7 @@ std::string BlockchainAddressValidator::get_blockchain_name(Blockchain blockchai
533556
case Blockchain::MONERO: return "Monero";
534557
case Blockchain::ZCASH: return "Zcash";
535558
case Blockchain::DOGECOIN: return "Dogecoin";
559+
case Blockchain::DASH: return "Dash";
536560
default: return "Unknown";
537561
}
538562
}

src/core/address_validator.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ enum class Blockchain {
2020
MONERO,
2121
ZCASH,
2222
DOGECOIN,
23+
DASH,
2324
DIGIBYTE,
2425
UNKNOWN
2526
};
@@ -89,6 +90,7 @@ class BlockchainAddressValidator {
8990
void initialize_monero_configs();
9091
void initialize_zcash_configs();
9192
void initialize_dogecoin_configs();
93+
void initialize_dash_configs();
9294

9395
// Validation helpers
9496
bool validate_base58check(const std::string& address, const BlockchainConfig& config, AddressValidationResult& result) const;

src/core/web_server.cpp

Lines changed: 146 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@ void HttpSession::process_request()
776776
std::string ext = resolved.extension().string();
777777
std::string mime = "application/octet-stream";
778778
if (ext == ".html" || ext == ".htm") mime = "text/html; charset=utf-8";
779-
else if (ext == ".js") mime = "application/javascript; charset=utf-8";
779+
else if (ext == ".js" || ext == ".mjs") mime = "application/javascript; charset=utf-8";
780780
else if (ext == ".css") mime = "text/css; charset=utf-8";
781781
else if (ext == ".json") mime = "application/json; charset=utf-8";
782782
else if (ext == ".ico") mime = "image/x-icon";
@@ -3071,6 +3071,17 @@ nlohmann::json MiningInterface::rest_current_payouts()
30713071
nlohmann::json result = nlohmann::json::object();
30723072
bool is_ltc = (m_blockchain == Blockchain::LITECOIN);
30733073

3074+
// Script-to-address resolver — picks chain-specific version bytes so
3075+
// Dash payouts render as 'X...' not Bitcoin '1...'.
3076+
auto resolve_addr = [this, is_ltc](const std::vector<unsigned char>& s) -> std::string {
3077+
if (m_blockchain == Blockchain::DASH) {
3078+
uint8_t p2pkh = m_testnet ? 140 : 76; // Dash: 'y' / 'X'
3079+
uint8_t p2sh = m_testnet ? 19 : 16; // Dash: '7'
3080+
return core::script_to_address(s, "", p2pkh, p2sh);
3081+
}
3082+
return core::script_to_address(s, is_ltc, m_testnet);
3083+
};
3084+
30743085
// Primary source: cached PPLNS outputs from the coinbase builder.
30753086
// These are always up-to-date with the latest share template and subsidy.
30763087
auto cached = get_cached_pplns_outputs();
@@ -3085,7 +3096,7 @@ nlohmann::json MiningInterface::rest_current_payouts()
30853096

30863097
// script_hex is a hex-encoded scriptPubKey — decode to bytes, then to address
30873098
auto script_bytes = ParseHex(script_hex);
3088-
std::string addr = core::script_to_address(script_bytes, is_ltc, m_testnet);
3099+
std::string addr = resolve_addr(script_bytes);
30893100
if (addr.empty() && script_bytes.size() > 33 && script_bytes.back() == 0xac) {
30903101
// P2PK: PUSH<len> <pubkey> OP_CHECKSIG → hash pubkey → P2PKH address
30913102
size_t pk_len = script_bytes[0];
@@ -3098,7 +3109,7 @@ nlohmann::json MiningInterface::rest_current_payouts()
30983109
p2pkh.insert(p2pkh.end(), rip, rip + 20);
30993110
p2pkh.push_back(0x88);
31003111
p2pkh.push_back(0xac);
3101-
addr = core::script_to_address(p2pkh, is_ltc, m_testnet);
3112+
addr = resolve_addr(p2pkh);
31023113
}
31033114
}
31043115
if (addr.empty())
@@ -3754,12 +3765,13 @@ void MiningInterface::cache_pplns_at_tip()
37543765
std::lock_guard<std::mutex> lock(m_pplns_cache_mutex);
37553766
m_pplns_per_tip[tip] = std::move(pplns);
37563767

3757-
// Evict old entries to prevent unbounded growth.
3758-
// Keep at most 100 entries (~50 minutes of tips at 30s intervals).
3759-
constexpr size_t MAX_PPLNS_CACHE = 100;
3768+
// Evict old entries to prevent unbounded growth. Cap at 5000 so Dash's
3769+
// chain_length=4320 precompute + live tip churn both fit. LTC's older
3770+
// 100-entry comment was sized for live tips only; precompute now stores
3771+
// one entry per share in the window, so the cap has to be larger than
3772+
// the biggest window we serve.
3773+
constexpr size_t MAX_PPLNS_CACHE = 5000;
37603774
if (m_pplns_per_tip.size() > MAX_PPLNS_CACHE * 2) {
3761-
// Bulk clear — cheaper than selective eviction on unordered_map
3762-
// Keep only the current tip entry
37633775
auto current = std::move(m_pplns_per_tip[tip]);
37643776
m_pplns_per_tip.clear();
37653777
m_pplns_per_tip[tip] = std::move(current);
@@ -4350,25 +4362,42 @@ nlohmann::json MiningInterface::rest_local_stats()
43504362

43514363
// block_value in coins (not satoshis)
43524364
double block_value = 0.0;
4365+
double payment_amount = 0.0;
43534366
{
43544367
std::lock_guard<std::mutex> lock(m_work_mutex);
4355-
if (!m_cached_template.is_null())
4368+
if (!m_cached_template.is_null()) {
43564369
block_value = m_cached_template.value("coinbasevalue", uint64_t(0)) / 1e8;
4370+
// Dash: payment_amount = masternode + superblock + platform payments
4371+
// These are deducted from miner payout before PPLNS distribution
4372+
payment_amount = m_cached_template.value("payment_amount", uint64_t(0)) / 1e8;
4373+
}
43574374
}
43584375
result["block_value"] = block_value;
4359-
// Deduct total fee (node fee + dev donation) from miner portion
4360-
// donation_proportion already represents the combined fee ratio
4376+
// Miner portion: subsidy minus protocol payments (masternode/superblock) minus node fee
4377+
// For LTC/DOGE: payment_amount=0, so block_value_miner = block_value * (1 - fee)
4378+
// For Dash: block_value_miner = (subsidy - payment_amount) * (1 - fee)
43614379
double fee_ratio = m_pool_fee_percent / 100.0;
4362-
result["block_value_miner"] = block_value * (1.0 - fee_ratio);
4363-
result["block_value_payments"] = block_value; // total including fees
4380+
double miner_subsidy = std::max(0.0, block_value - payment_amount);
4381+
result["block_value_miner"] = miner_subsidy * (1.0 - fee_ratio);
4382+
result["block_value_payments"] = (m_blockchain == Blockchain::DASH) ? payment_amount : block_value;
43644383

43654384
// Node fee amounts per block: fee% × (local_hashrate / pool_hashrate) × block_value
43664385
// Matches p2pool: operator gets fee% of THIS node's contribution, not the whole block.
43674386
{
43684387
double local_hr = m_stratum_hashrate_fn ? m_stratum_hashrate_fn() : 0.0;
43694388
double pool_hr = m_pool_hashrate_fn ? m_pool_hashrate_fn() : 0.0;
43704389
double node_share = (pool_hr > 0 && local_hr > 0) ? local_hr / pool_hr : 0.0;
4371-
result["node_fee_ltc"] = block_value * fee_ratio * node_share;
4390+
// Use blockchain-appropriate key for backward compatibility
4391+
double primary_fee = miner_subsidy * fee_ratio * node_share;
4392+
switch (m_blockchain) {
4393+
case Blockchain::DASH:
4394+
result["node_fee_dash"] = primary_fee; break;
4395+
case Blockchain::DOGECOIN:
4396+
result["node_fee_doge"] = primary_fee;
4397+
result["node_fee_ltc"] = primary_fee; break; // compat
4398+
default:
4399+
result["node_fee_ltc"] = primary_fee; break;
4400+
}
43724401
if (m_mm_manager) {
43734402
auto chain_infos = m_mm_manager->get_chain_infos();
43744403
for (const auto& ci : chain_infos) {
@@ -4396,7 +4425,10 @@ nlohmann::json MiningInterface::rest_local_stats()
43964425
}
43974426

43984427
// 2. No work template yet
4399-
{
4428+
// Coin targets that drive their own work pipeline (c2pool-dash)
4429+
// don't populate m_cached_template; suppress this warning when
4430+
// the dashboard-always-ready flag is set.
4431+
if (!m_dashboard_always_ready.load(std::memory_order_relaxed)) {
44004432
std::lock_guard<std::mutex> lock(m_work_mutex);
44014433
if (!m_work_valid)
44024434
warnings.push_back("No block template received yet — waiting for daemon connection");
@@ -4528,7 +4560,7 @@ nlohmann::json MiningInterface::rest_local_stats()
45284560

45294561
// p2pool-compat: version and protocol_version
45304562
result["version"] = m_pool_version;
4531-
result["protocol_version"] = 3600; // V36 share format
4563+
result["protocol_version"] = m_protocol_version.load(std::memory_order_relaxed);
45324564

45334565
return result;
45344566
}
@@ -4636,6 +4668,20 @@ nlohmann::json MiningInterface::rest_web_currency_info()
46364668
result["tx_explorer_url_prefix"] = "https://blockchair.com/dogecoin/transaction/";
46374669
}
46384670
break;
4671+
case Blockchain::DASH:
4672+
result["symbol"] = "DASH";
4673+
result["name"] = "Dash";
4674+
result["block_period"] = 150; // 2.5 min average
4675+
if (!m_custom_address_explorer.empty()) {
4676+
result["address_explorer_url_prefix"] = m_custom_address_explorer;
4677+
result["block_explorer_url_prefix"] = m_custom_block_explorer;
4678+
result["tx_explorer_url_prefix"] = m_custom_tx_explorer;
4679+
} else {
4680+
result["address_explorer_url_prefix"] = "https://blockchair.com/dash/address/";
4681+
result["block_explorer_url_prefix"] = "https://blockchair.com/dash/block/";
4682+
result["tx_explorer_url_prefix"] = "https://blockchair.com/dash/transaction/";
4683+
}
4684+
break;
46394685
}
46404686

46414687
// Expose explorer state so dashboard JS can link to internal explorer
@@ -4647,6 +4693,17 @@ nlohmann::json MiningInterface::rest_web_currency_info()
46474693
result["embedded"] = (m_embedded_node != nullptr);
46484694
result["has_rpc"] = (m_coin_rpc != nullptr);
46494695

4696+
// p2pool share version for the current coin — consumed by the
4697+
// bundled @c2pool/sharechain-explorer to classify share cells.
4698+
// Dash = v16, LTC/DOGE/BTC = v36.
4699+
switch (m_blockchain) {
4700+
case Blockchain::DASH: result["share_version"] = 16; break;
4701+
case Blockchain::LITECOIN:
4702+
case Blockchain::BITCOIN:
4703+
case Blockchain::DOGECOIN:
4704+
default: result["share_version"] = 36; break;
4705+
}
4706+
46504707
return result;
46514708
}
46524709

@@ -4702,7 +4759,11 @@ nlohmann::json MiningInterface::rest_sync_status()
47024759
// The placeholder template has height=1 and coinbasevalue=5000000000 —
47034760
// reject it by requiring height > 100.
47044761
bool has_work = false;
4705-
{
4762+
if (m_dashboard_always_ready.load(std::memory_order_relaxed)) {
4763+
// Coin-driven work pipelines (c2pool-dash) manage their own work;
4764+
// report has_work=true so the loading gate doesn't stall.
4765+
has_work = true;
4766+
} else {
47064767
std::lock_guard<std::mutex> lock(m_work_mutex);
47074768
has_work = !m_cached_template.is_null()
47084769
&& m_cached_template.value("coinbasevalue", uint64_t(0)) > 0
@@ -4734,6 +4795,12 @@ nlohmann::json MiningInterface::rest_sync_status()
47344795

47354796
bool MiningInterface::is_node_ready()
47364797
{
4798+
// Coin targets that drive their own work pipeline (c2pool-dash) can
4799+
// bypass the internal readiness gate — their dashboard is ready as
4800+
// soon as the HTTP server is up.
4801+
if (m_dashboard_always_ready.load(std::memory_order_relaxed))
4802+
return true;
4803+
47374804
// Check sharechain has verified shares
47384805
if (m_sharechain_stats_fn) {
47394806
auto sc = m_sharechain_stats_fn();
@@ -5025,6 +5092,10 @@ nlohmann::json MiningInterface::rest_node_info()
50255092
result["network"] = m_testnet ? "dogecoin_testnet" : "dogecoin";
50265093
result["symbol"] = "DOGE";
50275094
break;
5095+
case Blockchain::DASH:
5096+
result["network"] = m_testnet ? "dash_testnet" : "dash";
5097+
result["symbol"] = "DASH";
5098+
break;
50285099
}
50295100
return result;
50305101
}
@@ -5374,6 +5445,12 @@ nlohmann::json MiningInterface::rest_miner_payouts(const std::string& address)
53745445

53755446
nlohmann::json MiningInterface::rest_version_signaling(const nlohmann::json* cached_sc)
53765447
{
5448+
// V35→V36 transition tracking applies to the v36 coins (LTC and DOGE).
5449+
// Other blockchains (e.g. Dash v16) don't have a pending transition, so
5450+
// return an empty object to keep the dashboard's transition banners hidden.
5451+
if (m_blockchain != Blockchain::LITECOIN && m_blockchain != Blockchain::DOGECOIN)
5452+
return nlohmann::json::object();
5453+
53775454
// Matches p2pool's get_version_signaling() — all fields the dashboard JS expects.
53785455
constexpr int TARGET_VERSION = 36;
53795456
const std::map<int, std::string> share_type_names = {
@@ -6421,18 +6498,38 @@ nlohmann::json MiningInterface::rest_discovered_merged_blocks()
64216498
nlohmann::json MiningInterface::rest_broadcaster_status()
64226499
{
64236500
nlohmann::json result = nlohmann::json::object();
6424-
if (!m_mm_manager || !m_mm_manager->has_chains()) {
6425-
result["running"] = false;
6426-
result["last_broadcast"] = nullptr;
6501+
6502+
// LTC / merged-mining path: broadcaster is driven by MM manager.
6503+
if (m_mm_manager && m_mm_manager->has_chains()) {
6504+
result["running"] = true;
6505+
result["enabled"] = true;
6506+
result["chains"] = m_mm_manager->chain_count();
6507+
result["total_blocks_found"] = m_mm_manager->get_total_blocks();
6508+
if (m_ltc_peer_info_fn)
6509+
result["peers"] = m_ltc_peer_info_fn();
64276510
return result;
64286511
}
6429-
result["running"] = true;
6430-
result["enabled"] = true;
6431-
result["chains"] = m_mm_manager->chain_count();
6432-
result["total_blocks_found"] = m_mm_manager->get_total_blocks();
6433-
// LTC P2P peer info
6434-
if (m_ltc_peer_info_fn)
6435-
result["peers"] = m_ltc_peer_info_fn();
6512+
6513+
// Non-merged path (e.g. Dash SPV): coin_peer_info_fn provides the
6514+
// daemon P2P peer list so the dashboard's "Parent Chain Peers" panel
6515+
// can render dashd connections. Runs true iff the callback is wired
6516+
// AND at least one peer is connected.
6517+
if (m_coin_peer_info_fn) {
6518+
auto peers = m_coin_peer_info_fn();
6519+
bool any_connected = false;
6520+
if (peers.is_array()) {
6521+
for (const auto& p : peers)
6522+
if (p.value("connected", false)) { any_connected = true; break; }
6523+
}
6524+
result["running"] = any_connected;
6525+
result["enabled"] = true;
6526+
result["peers"] = std::move(peers);
6527+
result["total_blocks_found"] = 0; // no coin-side found-block counter yet
6528+
return result;
6529+
}
6530+
6531+
result["running"] = false;
6532+
result["last_broadcast"] = nullptr;
64366533
return result;
64376534
}
64386535

@@ -6888,10 +6985,30 @@ void MiningInterface::update_stat_log()
68886985
entry.current_payouts = nlohmann::json::object();
68896986
{
68906987
auto cached = get_cached_pplns_outputs();
6891-
bool is_ltc = (m_blockchain == Blockchain::LITECOIN);
6988+
// Chain-aware address encoding. The legacy is_ltc bool overload
6989+
// only covers LTC/BTC version bytes; Dash needs 76/16 mainnet,
6990+
// 140/19 testnet. Pick the right pair per blockchain so graph_db
6991+
// samples carry human-readable addresses (not Bitcoin fallback
6992+
// encodings). Parity-audit D-tier followup shipped alongside C2.
6993+
auto [p2pkh_ver, p2sh_ver, hrp] = [&]()
6994+
-> std::tuple<uint8_t, uint8_t, std::string>
6995+
{
6996+
switch (m_blockchain) {
6997+
case Blockchain::LITECOIN:
6998+
return m_testnet ? std::make_tuple<uint8_t, uint8_t, std::string>(0x6f, 0xc4, "tltc")
6999+
: std::make_tuple<uint8_t, uint8_t, std::string>(0x30, 0x32, "ltc");
7000+
case Blockchain::DASH:
7001+
return m_testnet ? std::make_tuple<uint8_t, uint8_t, std::string>(140, 19, "")
7002+
: std::make_tuple<uint8_t, uint8_t, std::string>(76, 16, "");
7003+
case Blockchain::BITCOIN:
7004+
default:
7005+
return m_testnet ? std::make_tuple<uint8_t, uint8_t, std::string>(0x6f, 0xc4, "tb")
7006+
: std::make_tuple<uint8_t, uint8_t, std::string>(0x00, 0x05, "bc");
7007+
}
7008+
}();
68927009
for (const auto& [script_hex, amount] : cached) {
68937010
auto script_bytes = ParseHex(script_hex);
6894-
std::string addr = core::script_to_address(script_bytes, is_ltc, m_testnet);
7011+
std::string addr = core::script_to_address(script_bytes, hrp, p2pkh_ver, p2sh_ver);
68957012
if (addr.empty()) addr = script_hex;
68967013
double coin_amt = static_cast<double>(amount) / 1e8;
68977014
if (entry.current_payouts.contains(addr))

0 commit comments

Comments
 (0)