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
85 changes: 85 additions & 0 deletions src/impl/dash/config_pool.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#pragma once

// DASH P2Pool sharechain network configuration (oracle-sourced SSOT).
//
// SOURCE OF TRUTH: the DASH oracle frstrtr/p2pool-dash, networks/dash.py +
// networks/dash_testnet.py (operator 2026-06-17 per-coin re-scope: DASH conforms
// to its OWN older-than-v35 oracle, NOT a v35-uniform baseline).
//
// SCOPE: pins the p2pool *sharechain* framing constants (PREFIX/IDENTIFIER/
// SHARE_PERIOD/CHAIN_LENGTH/TARGET_LOOKBEHIND/SPREAD/P2P_PORT/WORKER_PORT/
// MIN_PROTOCOL/MAX_TARGET) as the single place a DASH sharechain constant can
// drift. Consumed by test_dash_conformance (oracle pin) and is the SSOT the
// dash::make_coin_params factory will read. Factory wiring + Fileconfig/pool.yaml
// runtime-override integration are the S6 follow-on, deliberately out of scope
// here so this header carries no file-loading machinery.
//
// PREFIX/IDENTIFIER are ISOLATION PRIMITIVES (operator v36_standardization_goal
// 2026-06-17): kept per-coin AND per-instance, NEVER unified cross-coin.

#include <cstdint>
#include <string>

#include <core/uint256.hpp>

namespace dash
{

// DASH sharechain p2pool constants. Source of truth: p2pool-dash oracle
// networks/dash.py (mainnet) + networks/dash_testnet.py (testnet).
struct PoolConfig
{
// ---- mainnet (networks/dash.py) ----
static constexpr uint16_t P2P_PORT = 8999;
static constexpr uint16_t WORKER_PORT = 7903;
static constexpr uint32_t SHARE_PERIOD = 20; // seconds
static constexpr uint32_t CHAIN_LENGTH = 4320; // 24*60*60//20
static constexpr uint32_t REAL_CHAIN_LENGTH = 4320;
static constexpr uint32_t TARGET_LOOKBEHIND = 100;
static constexpr uint32_t SPREAD = 10; // blocks
static constexpr uint32_t MINIMUM_PROTOCOL_VERSION = 1700; // protocol v1700 floor

// ---- testnet (networks/dash_testnet.py) ----
static constexpr uint16_t TESTNET_P2P_PORT = 18999;
static constexpr uint16_t TESTNET_WORKER_PORT = 17903;
static constexpr uint32_t TESTNET_SHARE_PERIOD = 20;
static constexpr uint32_t TESTNET_CHAIN_LENGTH = 4320;
static constexpr uint32_t TESTNET_REAL_CHAIN_LENGTH = 4320;

static inline bool is_testnet = false;

static uint16_t p2p_port() { return is_testnet ? TESTNET_P2P_PORT : P2P_PORT; }
static uint16_t worker_port() { return is_testnet ? TESTNET_WORKER_PORT : WORKER_PORT; }
static uint32_t share_period() { return is_testnet ? TESTNET_SHARE_PERIOD : SHARE_PERIOD; }
static uint32_t chain_length() { return is_testnet ? TESTNET_CHAIN_LENGTH : CHAIN_LENGTH; }
static uint32_t real_chain_length() { return is_testnet ? TESTNET_REAL_CHAIN_LENGTH : REAL_CHAIN_LENGTH; }

// ISOLATION PRIMITIVES — per-coin AND per-instance, never unified cross-coin.
static inline const std::string IDENTIFIER_HEX = "7242ef345e1bed6b";
static inline const std::string PREFIX_HEX = "3b3e1286f446b891";
static inline const std::string TESTNET_IDENTIFIER_HEX = "b6deb1e543fe2427";
static inline const std::string TESTNET_PREFIX_HEX = "198b644f6821e3b3";

static const std::string& identifier_hex() { return is_testnet ? TESTNET_IDENTIFIER_HEX : IDENTIFIER_HEX; }
static const std::string& prefix_hex() { return is_testnet ? TESTNET_PREFIX_HEX : PREFIX_HEX; }

// MAX_TARGET: easiest allowed share difficulty (share-diff floor).
// mainnet : 0xFFFF * 2**208 (standard bdiff difficulty-1 target)
// testnet : 2**256 // 2**20 - 1
static uint256 max_target()
{
static const uint256 MAINNET_MAX = [] {
uint256 t;
t.SetHex("00000000ffff0000000000000000000000000000000000000000000000000000");
return t;
}();
static const uint256 TESTNET_MAX = [] {
uint256 t;
t.SetHex("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
return t;
}();
return is_testnet ? TESTNET_MAX : MAINNET_MAX;
}
};

} // namespace dash
138 changes: 138 additions & 0 deletions src/impl/dash/params.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#pragma once

// DASH CoinParams factory: builds a fully populated core::CoinParams for the
// DASH (X11) p2pool parent chain.
//
// SOURCE OF TRUTH: the DASH oracle frstrtr/p2pool-dash, networks/dash.py +
// networks/dash_testnet.py (operator 2026-06-17 per-coin re-scope: DASH conforms
// to its OWN older-than-v35 oracle, NOT a v35-uniform baseline).
//
// POOL-LEVEL FIELDS are sourced exclusively from dash::PoolConfig
// (config_pool.hpp) — the sharechain SSOT shipped in PR #146 — so there is
// exactly one place a DASH sharechain constant can drift. COIN-LEVEL fields are
// inlined here with oracle citations (mirroring ltc/params.hpp; DASH carries no
// separate config_coin.hpp).
//
// PREFIX/IDENTIFIER are ISOLATION PRIMITIVES (operator v36_standardization_goal
// 2026-06-17): kept per-coin AND per-instance, NEVER unified cross-coin.

#include "config_pool.hpp"
#include "crypto/hash_x11.hpp"
#include "share_check.hpp" // dash::DONATION_SCRIPT (consensus-critical SSOT)

#include <core/coin_params.hpp>
#include <core/pow.hpp>

#include <optional>
#include <string>
#include <vector>

namespace dash
{

// Runtime override seam for pool.yaml (Fileconfig consume-side, S6 follow-on).
// ONLY operationally-tunable, NON-consensus, NON-isolation pool fields may be
// overridden by an operator pool.yaml. Consensus-critical fields (share version,
// max_target, donation script, X11 pow/block identity) and the network ISOLATION
// PRIMITIVES (prefix/identifier) are deliberately ABSENT from this struct and are
// therefore NEVER overridable: they stay pinned to the dash::PoolConfig SSOT
// regardless of any override file, so a mis-edited pool.yaml can retune
// ports/peers but can NEVER fork the sharechain off its oracle-conformant
// identity. The YAML file-load half lands when DASH gains its config_pool.cpp
// Fileconfig (mirrors dgb/btc); this header carries no file IO.
struct PoolOverrides
{
std::optional<uint16_t> p2p_port;
std::optional<uint16_t> worker_port;
std::optional<std::vector<std::string>> bootstrap_addrs;
};

inline core::CoinParams make_coin_params(bool testnet, const PoolOverrides& overrides)
{
core::CoinParams p;

// ===== Coin-level (net.PARENT) — DASH oracle networks/dash.py PARENT =====
p.symbol = "DASH";
p.block_period = 150; // target_spacing (2.5 min), matches header_chain DGW target_spacing

// Address encoding (oracle PARENT). DASH has a SINGLE P2SH version; there is
// no secondary P2SH prefix and no bech32 HRP on the older baseline.
if (testnet) {
p.address_version = 140; // testnet PUBKEY_ADDRESS (y...)
p.address_p2sh_version = 19; // testnet SCRIPT_ADDRESS
} else {
p.address_version = 76; // mainnet PUBKEY_ADDRESS (X...)
p.address_p2sh_version = 16; // mainnet SCRIPT_ADDRESS (7...)
}
p.address_p2sh_version2 = 0; // DASH: no secondary P2SH prefix
p.bech32_hrp = ""; // DASH: no segwit / no bech32

// PoW: X11 work AND X11 block identity (unlike LTC, DASH identifies blocks by
// X11, not SHA256d — see header_chain.hpp block_hash_func = pow_func = x11).
p.pow_func = [](std::span<const unsigned char> d) -> uint256 { return dash::crypto::hash_x11(d); };
p.block_hash_func = [](std::span<const unsigned char> d) -> uint256 { return dash::crypto::hash_x11(d); };

// Subsidy: DASH initial 5 DASH, -1/14 (~7.14%) every 210240 blocks (oracle
// PARENT.SUBSIDY_FUNC; halving_interval/initial_subsidy match header_chain).
// [confirm-vs-oracle] gradual-decrease rounding to be re-pinned against a
// captured node corpus in the real-node KAT follow-on.
p.subsidy_func = [](uint32_t height) -> uint64_t {
uint64_t subsidy = 500000000ULL; // 5 DASH
for (uint32_t period = (height + 1) / 210240; period > 0; --period)
subsidy -= subsidy / 14;
return subsidy;
};

p.dust_threshold = 5460; // [confirm-vs-oracle] legacy DASH dust floor (relay policy, not consensus)

// Softforks: DASH older baseline has NO segwit (segwit_activation_version=0).
p.softforks_required = {};
p.segwit_activation_version = 0;

// ===== Pool-level (net) — from dash::PoolConfig (config_pool.hpp SSOT) =====
PoolConfig::is_testnet = testnet;
p.p2p_port = PoolConfig::p2p_port();
p.worker_port = PoolConfig::worker_port();

p.share_period = PoolConfig::share_period();
p.chain_length = PoolConfig::chain_length();
p.real_chain_length = PoolConfig::real_chain_length();

p.target_lookbehind = PoolConfig::TARGET_LOOKBEHIND;
p.spread = PoolConfig::SPREAD;
p.minimum_protocol_version = PoolConfig::MINIMUM_PROTOCOL_VERSION;
p.block_max_size = 0; // DASH: no segwit weight accounting
p.block_max_weight = 0;

p.max_target = PoolConfig::max_target();

// Network identification — DASH oracle (isolation primitives, never unified).
p.identifier_hex = PoolConfig::IDENTIFIER_HEX;
p.prefix_hex = PoolConfig::PREFIX_HEX;
p.testnet_identifier_hex = PoolConfig::TESTNET_IDENTIFIER_HEX;
p.testnet_prefix_hex = PoolConfig::TESTNET_PREFIX_HEX;

// Donation script (consensus-critical) — DASH is ALWAYS P2PKH (no segwit, no
// v36 combined-P2SH on the older baseline). Single SSOT = dash::DONATION_SCRIPT.
p.donation_script_func = [](int64_t /*share_version*/) -> std::vector<unsigned char> {
return DONATION_SCRIPT;
};

p.current_share_version = 16; // DASH older-than-v35 baseline (m_desired_version{16})
p.is_testnet = testnet;

// ----- pool.yaml runtime overrides (tunable, non-consensus only) -----
if (overrides.p2p_port) p.p2p_port = *overrides.p2p_port;
if (overrides.worker_port) p.worker_port = *overrides.worker_port;
if (overrides.bootstrap_addrs) p.bootstrap_addrs = *overrides.bootstrap_addrs;

return p;
}

// Convenience overload: no operator overrides -> pure SSOT/oracle CoinParams.
inline core::CoinParams make_coin_params(bool testnet)
{
return make_coin_params(testnet, PoolOverrides{});
}

} // namespace dash
160 changes: 160 additions & 0 deletions test/test_dash_conformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <impl/dash/pplns.hpp> // dash::pplns::compute_payouts, dash::ShareChain, DashShare
#include <impl/dash/version_negotiation.hpp> // dash::version_negotiation::get_desired_version_counts/weights
#include <impl/dash/coin/vendor/cbtx.hpp> // dash::coin::vendor::CCbTx, parse_cbtx
#include <impl/dash/config_pool.hpp> // dash::PoolConfig (sharechain SSOT)
#include <impl/bitcoin_family/coin/base_block.hpp> // bitcoin_family::coin::SmallBlockHeaderType

#include <core/hash.hpp> // Hash (sha256d)
Expand Down Expand Up @@ -742,3 +743,162 @@ TEST(DashConformanceCbtx, ParseCbtxIsExactInverseOfKat) {
EXPECT_FALSE(dash::coin::vendor::parse_cbtx(bytes, tx2))
<< "parse_cbtx accepted trailing garbage (wire-drift guard defeated)";
}


// -- Sharechain network-params conformance (S6 slice 3) -----------------------
// Pin DASH's p2pool sharechain framing constants against its OWN older-than-v35
// oracle (frstrtr/p2pool-dash networks/dash.py + dash_testnet.py). The expected
// values below are an INDEPENDENT transcription of the oracle, NOT a re-export of
// dash::PoolConfig, so the assertions catch a drift in either copy -- the same
// anti-circularity design used by the merkle/payout KATs above.
//
// PREFIX/IDENTIFIER are isolation primitives: pinned per-coin here, never to be
// unified cross-coin (operator v36_standardization_goal 2026-06-17).
TEST(DashConformanceNetworkParams, MainnetMatchesP2poolDashOracle) {
dash::PoolConfig::is_testnet = false;
EXPECT_EQ(dash::PoolConfig::p2p_port(), 8999);
EXPECT_EQ(dash::PoolConfig::worker_port(), 7903);
EXPECT_EQ(dash::PoolConfig::share_period(), 20u);
EXPECT_EQ(dash::PoolConfig::chain_length(), 4320u); // 24*60*60//20
EXPECT_EQ(dash::PoolConfig::real_chain_length(), 4320u);
EXPECT_EQ(dash::PoolConfig::TARGET_LOOKBEHIND, 100u);
EXPECT_EQ(dash::PoolConfig::SPREAD, 10u);
EXPECT_EQ(dash::PoolConfig::MINIMUM_PROTOCOL_VERSION, 1700u);
EXPECT_EQ(dash::PoolConfig::identifier_hex(), std::string("7242ef345e1bed6b"));
EXPECT_EQ(dash::PoolConfig::prefix_hex(), std::string("3b3e1286f446b891"));
uint256 expect_max;
expect_max.SetHex("00000000ffff0000000000000000000000000000000000000000000000000000");
EXPECT_EQ(dash::PoolConfig::max_target(), expect_max); // 0xFFFF * 2**208
}

TEST(DashConformanceNetworkParams, TestnetMatchesP2poolDashOracle) {
dash::PoolConfig::is_testnet = true;
EXPECT_EQ(dash::PoolConfig::p2p_port(), 18999);
EXPECT_EQ(dash::PoolConfig::worker_port(), 17903);
EXPECT_EQ(dash::PoolConfig::share_period(), 20u);
EXPECT_EQ(dash::PoolConfig::chain_length(), 4320u);
EXPECT_EQ(dash::PoolConfig::identifier_hex(), std::string("b6deb1e543fe2427"));
EXPECT_EQ(dash::PoolConfig::prefix_hex(), std::string("198b644f6821e3b3"));
uint256 expect_max;
expect_max.SetHex("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
EXPECT_EQ(dash::PoolConfig::max_target(), expect_max); // 2**256 // 2**20 - 1
dash::PoolConfig::is_testnet = false; // restore global for any later tests
}

// ── Factory↔SSOT conformance (S6 slice: make_coin_params wiring) ─────────────
// The dash::make_coin_params factory MUST populate core::CoinParams pool-level
// fields from the dash::PoolConfig SSOT (config_pool.hpp) and the coin-level
// fields from the DASH oracle, so share_check/coinbase_builder — which consume a
// const core::CoinParams& — never run on an unpopulated or drifted struct. This
// pins the factory output WITHOUT a node dependency: it is the node-free half of
// S6, complementary to the real-node KAT capture.
#include <impl/dash/params.hpp> // dash::make_coin_params

TEST(DashConformanceFactory, PoolFieldsSourcedFromSSOT) {
for (bool testnet : {false, true}) {
dash::PoolConfig::is_testnet = testnet;
auto p = dash::make_coin_params(testnet);
EXPECT_EQ(p.p2p_port, dash::PoolConfig::p2p_port());
EXPECT_EQ(p.worker_port, dash::PoolConfig::worker_port());
EXPECT_EQ(p.share_period, dash::PoolConfig::share_period());
EXPECT_EQ(p.chain_length, dash::PoolConfig::chain_length());
EXPECT_EQ(p.real_chain_length, dash::PoolConfig::real_chain_length());
EXPECT_EQ(p.target_lookbehind, dash::PoolConfig::TARGET_LOOKBEHIND);
EXPECT_EQ(p.spread, dash::PoolConfig::SPREAD);
EXPECT_EQ(p.minimum_protocol_version, dash::PoolConfig::MINIMUM_PROTOCOL_VERSION);
EXPECT_EQ(p.identifier_hex, dash::PoolConfig::IDENTIFIER_HEX);
EXPECT_EQ(p.prefix_hex, dash::PoolConfig::PREFIX_HEX);
EXPECT_EQ(p.testnet_identifier_hex, dash::PoolConfig::TESTNET_IDENTIFIER_HEX);
EXPECT_EQ(p.testnet_prefix_hex, dash::PoolConfig::TESTNET_PREFIX_HEX);
dash::PoolConfig::is_testnet = testnet;
EXPECT_EQ(p.max_target, dash::PoolConfig::max_target());
EXPECT_EQ(p.is_testnet, testnet);
}
}

TEST(DashConformanceFactory, CoinFieldsMatchDashOracle) {
auto mn = dash::make_coin_params(/*testnet=*/false);
EXPECT_EQ(mn.symbol, "DASH");
EXPECT_EQ(mn.block_period, 150u);
EXPECT_EQ(mn.address_version, 76); // X...
EXPECT_EQ(mn.address_p2sh_version, 16); // 7...
EXPECT_EQ(mn.address_p2sh_version2, 0); // no secondary P2SH
EXPECT_EQ(mn.segwit_activation_version, 0u); // DASH: no segwit
EXPECT_EQ(mn.current_share_version, 16u); // older-than-v35 baseline

auto tn = dash::make_coin_params(/*testnet=*/true);
EXPECT_EQ(tn.address_version, 140); // y...
EXPECT_EQ(tn.address_p2sh_version, 19);
}

// The factory donation script is byte-identical to the DONATION_SCRIPT SSOT for
// every share version (DASH is always P2PKH; no v36 combined-P2SH switch).
TEST(DashConformanceFactory, DonationScriptIsSSOTForAllVersions) {
auto p = dash::make_coin_params(/*testnet=*/false);
for (int64_t v : {0, 15, 16, 35, 36}) {
EXPECT_EQ(p.donation_script_func(v), dash::DONATION_SCRIPT)
<< "donation script diverged at share_version=" << v;
}
}

// PoW and block-identity hashes are BOTH X11 (DASH identifies blocks by X11,
// not SHA256d) — guard against an accidental copy of the LTC sha256d shape.
TEST(DashConformanceFactory, PowAndBlockHashAreBothX11) {
auto p = dash::make_coin_params(/*testnet=*/false);
ASSERT_TRUE(p.pow_func);
ASSERT_TRUE(p.block_hash_func);
std::vector<unsigned char> sample(80, 0xab);
std::span<const unsigned char> s(sample.data(), sample.size());
uint256 expect = dash::crypto::hash_x11(s);
EXPECT_EQ(p.pow_func(s), expect);
EXPECT_EQ(p.block_hash_func(s), expect);
}

// ── pool.yaml runtime-override seam (S6 slice: make_coin_params overrides) ────
// The override overload lets an operator pool.yaml retune ONLY non-consensus,
// non-isolation pool fields (ports, bootstrap peers). With no overrides the
// factory output is byte-identical to the pure-SSOT overload; consensus-critical
// and isolation fields are NEVER reachable from PoolOverrides (compile-time:
// the struct has no field for them) and stay pinned to the SSOT even when the
// tunable fields are overridden. Node-free — pins the seam pool.yaml populates.
TEST(DashConformanceFactory, NoOverridesEqualsSSOTOverload) {
for (bool testnet : {false, true}) {
auto base = dash::make_coin_params(testnet);
auto ovr = dash::make_coin_params(testnet, dash::PoolOverrides{});
EXPECT_EQ(ovr.p2p_port, base.p2p_port);
EXPECT_EQ(ovr.worker_port, base.worker_port);
EXPECT_EQ(ovr.bootstrap_addrs, base.bootstrap_addrs);
}
}

TEST(DashConformanceFactory, OverridesApplyToTunableFields) {
dash::PoolOverrides ov;
ov.p2p_port = 19000;
ov.worker_port = 19001;
ov.bootstrap_addrs = std::vector<std::string>{"seed.example:8999", "node2.example:8999"};
auto p = dash::make_coin_params(/*testnet=*/false, ov);
EXPECT_EQ(p.p2p_port, 19000);
EXPECT_EQ(p.worker_port, 19001);
ASSERT_EQ(p.bootstrap_addrs.size(), 2u);
EXPECT_EQ(p.bootstrap_addrs[0], "seed.example:8999");
EXPECT_EQ(p.bootstrap_addrs[1], "node2.example:8999");
}

// Even with tunable overrides set, consensus + isolation fields MUST equal the
// SSOT/oracle values — a mis-edited pool.yaml can never fork the sharechain.
TEST(DashConformanceFactory, OverridesNeverTouchConsensusOrIsolation) {
dash::PoolOverrides ov;
ov.p2p_port = 19000;
ov.worker_port = 19001;
auto ssot = dash::make_coin_params(/*testnet=*/false);
auto p = dash::make_coin_params(/*testnet=*/false, ov);
EXPECT_EQ(p.identifier_hex, ssot.identifier_hex);
EXPECT_EQ(p.prefix_hex, ssot.prefix_hex);
EXPECT_EQ(p.testnet_identifier_hex, ssot.testnet_identifier_hex);
EXPECT_EQ(p.testnet_prefix_hex, ssot.testnet_prefix_hex);
EXPECT_EQ(p.max_target, ssot.max_target);
EXPECT_EQ(p.current_share_version, ssot.current_share_version);
EXPECT_EQ(p.minimum_protocol_version, ssot.minimum_protocol_version);
for (int64_t v : {0, 16, 35, 36})
EXPECT_EQ(p.donation_script_func(v), dash::DONATION_SCRIPT);
}
Loading