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
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ jobs:
core_test sharechain_test share_test btc_share_test \
test_threading test_weights \
test_header_chain test_mempool test_template_builder \
test_doge_chain test_compact_blocks test_dash_x11_kat test_dash_header_chain test_dash_block_replay test_dash_conformance test_dash_subsidy test_dash_mempool test_dash_simplifiedmns test_dash_quorum test_dash_quorum_root test_dash_mn_state test_dash_embedded_gbt test_dash_smldiff test_dash_p2p_messages test_dash_p2p_connection test_dash_p2p_node test_dash_node_interface test_dash_config test_dash_broadcaster test_dash_broadcaster_full test_dash_share_hash_link test_dash_block_relay test_dash_rpc_request test_dash_rpc_conf test_dash_block_producer test_dash_embedded_relay_e2e test_dash_block_relay_plan test_dash_version_activation_latch test_dash_block_relay_binding test_dash_block_relay_dual_arm test_dash_coinbase_parity test_dash_donation_combined test_dash_g3_assembled test_dash_work_target test_dash_work_job_targets test_dash_stratum_binding test_dash_job_notify_roundtrip test_dash_cb_payee test_dash_stratum_notify_roundtrip test_dash_poolnode_messages test_dash_peer test_dash_share_tracker test_dash_node test_dash_share_messages test_dash_stratum_extranonce_split test_dash_stratum_submit_reassembly test_dash_difficulty_parity test_dash_auto_ratchet test_dash_min_protocol_gate test_dash_work_source test_dash_node_coin_state test_dash_coin_state_maintainer test_dash_node_embedded_wire test_dash_node_reception_wire test_dash_get_work \
test_doge_chain test_compact_blocks test_dash_x11_kat test_dash_header_chain test_dash_block_replay test_dash_conformance test_dash_subsidy test_dash_mempool test_dash_simplifiedmns test_dash_quorum test_dash_quorum_root test_dash_mn_state test_dash_embedded_gbt test_dash_smldiff test_dash_p2p_messages test_dash_p2p_connection test_dash_p2p_node test_dash_node_interface test_dash_config test_dash_broadcaster test_dash_broadcaster_full test_dash_share_hash_link test_dash_block_relay test_dash_rpc_request test_dash_rpc_conf test_dash_block_producer test_dash_embedded_relay_e2e test_dash_block_relay_plan test_dash_version_activation_latch test_dash_block_relay_binding test_dash_block_relay_dual_arm test_dash_coinbase_parity test_dash_coinbase_muldiv test_dash_donation_combined test_dash_g3_assembled test_dash_work_target test_dash_work_job_targets test_dash_stratum_binding test_dash_job_notify_roundtrip test_dash_cb_payee test_dash_stratum_notify_roundtrip test_dash_poolnode_messages test_dash_peer test_dash_share_tracker test_dash_node test_dash_share_messages test_dash_stratum_extranonce_split test_dash_stratum_submit_reassembly test_dash_difficulty_parity test_dash_auto_ratchet test_dash_min_protocol_gate test_dash_work_source test_dash_node_coin_state test_dash_coin_state_maintainer test_dash_node_embedded_wire test_dash_node_reception_wire test_dash_get_work \
test_multiaddress_pplns test_pplns_stress \
test_hash_link test_decay_pplns \
test_pplns_consensus \
Expand Down Expand Up @@ -264,7 +264,7 @@ jobs:
test_threading test_weights \
test_header_chain test_mempool test_template_builder \
test_doge_chain test_compact_blocks test_dash_x11_kat \
test_dash_header_chain test_dash_block_replay test_dash_conformance test_dash_subsidy test_dash_mempool test_dash_simplifiedmns test_dash_quorum test_dash_quorum_root test_dash_mn_state test_dash_embedded_gbt test_dash_smldiff test_dash_p2p_messages test_dash_p2p_connection test_dash_p2p_node test_dash_node_interface test_dash_config test_dash_broadcaster test_dash_broadcaster_full test_dash_share_hash_link test_dash_block_relay test_dash_rpc_request test_dash_rpc_conf test_dash_block_producer test_dash_embedded_relay_e2e test_dash_block_relay_plan test_dash_version_activation_latch test_dash_block_relay_binding test_dash_block_relay_dual_arm test_dash_coinbase_parity test_dash_donation_combined test_dash_g3_assembled test_dash_work_target test_dash_work_job_targets test_dash_stratum_binding test_dash_job_notify_roundtrip test_dash_cb_payee test_dash_stratum_notify_roundtrip test_dash_poolnode_messages test_dash_peer test_dash_share_tracker test_dash_node test_dash_share_messages test_dash_stratum_extranonce_split test_dash_stratum_submit_reassembly test_dash_difficulty_parity test_dash_auto_ratchet test_dash_min_protocol_gate test_dash_work_source test_dash_node_coin_state test_dash_coin_state_maintainer test_dash_node_embedded_wire test_dash_node_reception_wire test_dash_get_work \
test_dash_header_chain test_dash_block_replay test_dash_conformance test_dash_subsidy test_dash_mempool test_dash_simplifiedmns test_dash_quorum test_dash_quorum_root test_dash_mn_state test_dash_embedded_gbt test_dash_smldiff test_dash_p2p_messages test_dash_p2p_connection test_dash_p2p_node test_dash_node_interface test_dash_config test_dash_broadcaster test_dash_broadcaster_full test_dash_share_hash_link test_dash_block_relay test_dash_rpc_request test_dash_rpc_conf test_dash_block_producer test_dash_embedded_relay_e2e test_dash_block_relay_plan test_dash_version_activation_latch test_dash_block_relay_binding test_dash_block_relay_dual_arm test_dash_coinbase_parity test_dash_coinbase_muldiv test_dash_donation_combined test_dash_g3_assembled test_dash_work_target test_dash_work_job_targets test_dash_stratum_binding test_dash_job_notify_roundtrip test_dash_cb_payee test_dash_stratum_notify_roundtrip test_dash_poolnode_messages test_dash_peer test_dash_share_tracker test_dash_node test_dash_share_messages test_dash_stratum_extranonce_split test_dash_stratum_submit_reassembly test_dash_difficulty_parity test_dash_auto_ratchet test_dash_min_protocol_gate test_dash_work_source test_dash_node_coin_state test_dash_coin_state_maintainer test_dash_node_embedded_wire test_dash_node_reception_wire test_dash_get_work \
test_hash_link test_decay_pplns \
test_pplns_consensus \
test_v36_script_sorting test_v36_cross_impl_refhash \
Expand Down
17 changes: 8 additions & 9 deletions src/impl/dash/coinbase_builder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "coin/transaction.hpp"
#include "coin/rpc_data.hpp"
#include "share_check.hpp" // decode_payee_script, pubkey_hash_to_script2, DONATION_SCRIPT
#include "payout_muldiv.hpp" // dash::payout::payout_share (MSVC-portable 128-bit muldiv)

#include <algorithm>
#include <cstdint>
Expand Down Expand Up @@ -126,16 +127,14 @@ inline std::vector<MinerPayout> compute_dash_payouts(
core::version_gate::is_v36_active(params.current_share_version);
std::map<Script, uint64_t> amounts;
if (total_weight > 0) {
// weights/total_weight fit uint64 at the DASH layer; __uint128_t covers
// the products (worker_payout < 2^50, 49 < 2^6, weight < 2^64).
__uint128_t den = v36
? static_cast<__uint128_t>(total_weight)
: static_cast<__uint128_t>(total_weight) * 50;
// weights/total_weight fit uint64 at the DASH layer, but the muldiv
// intermediate (worker_payout*weight*49 ~ 2^120, pre-v36 den > 2^64)
// needs a true 128-bit type. dash::payout::payout_share uses native
// __uint128_t on GCC/Clang and boost uint128 on MSVC (no __int128);
// the two are pinned bit-identical by test_dash_coinbase_muldiv.
for (const auto& [script, w] : weights) {
__uint128_t num = static_cast<__uint128_t>(w)
* static_cast<__uint128_t>(worker_payout);
if (!v36) num *= 49;
amounts[script] = static_cast<uint64_t>(num / den);
amounts[script] =
dash::payout::payout_share(w, worker_payout, total_weight, v36);
}
}

Expand Down
85 changes: 85 additions & 0 deletions src/impl/dash/payout_muldiv.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#ifndef C2POOL_DASH_PAYOUT_MULDIV_HPP
#define C2POOL_DASH_PAYOUT_MULDIV_HPP

// ---------------------------------------------------------------------------
// dash::payout -- MSVC-portable 128-bit intermediate for the PPLNS coinbase
// payout proportion (coinbase_builder.hpp step 3-4).
//
// The per-script amount is a muldiv evaluated in a 128-bit intermediate:
//
// v36 : amount = worker_payout * weight / total_weight
// pre-v36 : amount = worker_payout * 49 * weight / (total_weight * 50)
//
// The numerator worker_payout*weight*49 reaches ~2^120 (weight < 2^64,
// worker_payout < 2^50, 49 < 2^6) and the pre-v36 denominator total_weight*50
// exceeds 2^64, so a true 128-bit intermediate is REQUIRED.
//
// GCC/Clang provide native unsigned __int128 -- that path is kept BYTE-EXACT
// for the shipping Linux/macOS c2pool-dash packages. MSVC has no __int128
// (error C2065: '__uint128_t': undeclared identifier), which broke the
// c2pool-dash Windows v0.2.1 build; on any compiler without __int128 we fall
// back to boost::multiprecision::uint128_t, a fixed 128-bit unsigned type that
// yields BIT-IDENTICAL results. This is CONSENSUS PAYOUT math -- a one-satoshi
// divergence forks payouts -- so test_dash_coinbase_muldiv pins native ==
// portable across the full DASH payout domain (the required guard). Mirrors the
// BCH abla (#688) and DGB arith256 (#690) MSVC-portability rework.
// ---------------------------------------------------------------------------

#include <cassert>
#include <cstdint>
#include <limits>

// Portable 128-bit intermediate on compilers without __int128 (MSVC).
// Header-only; boost is already a c2pool dependency (conan + system libboost).
#include <boost/multiprecision/cpp_int.hpp>

namespace dash {
namespace payout {

// Portable path (MSVC and any non-__int128 compiler). Fixed 128-bit unsigned
// boost intermediate -> BIT-IDENTICAL to the native __int128 result.
inline uint64_t payout_share_portable(uint64_t weight, uint64_t worker_payout,
uint64_t total_weight, bool v36) {
assert(total_weight > 0);
using u128 = boost::multiprecision::uint128_t;
u128 num = u128(weight) * u128(worker_payout);
if (!v36) num *= 49u;
const u128 den = v36 ? u128(total_weight) : u128(total_weight) * 50u;
const u128 q = num / den;
assert(q <= u128(std::numeric_limits<uint64_t>::max()));
return static_cast<uint64_t>(q);
}

#if defined(__SIZEOF_INT128__)
// Native path (GCC/Clang) -- BYTE-EXACT reproduction of the original
// coinbase_builder.hpp:131-138 block; this is what the merged Linux/macOS
// packages ship. Exposed by name so the KAT can diff it against the portable
// path on the trusted platform.
inline uint64_t payout_share_native(uint64_t weight, uint64_t worker_payout,
uint64_t total_weight, bool v36) {
assert(total_weight > 0);
const __uint128_t den = v36
? static_cast<__uint128_t>(total_weight)
: static_cast<__uint128_t>(total_weight) * 50;
__uint128_t num = static_cast<__uint128_t>(weight)
* static_cast<__uint128_t>(worker_payout);
if (!v36) num *= 49;
const __uint128_t q = num / den;
assert(q <= static_cast<__uint128_t>(std::numeric_limits<uint64_t>::max()));
return static_cast<uint64_t>(q);
}
inline uint64_t payout_share(uint64_t weight, uint64_t worker_payout,
uint64_t total_weight, bool v36) {
return payout_share_native(weight, worker_payout, total_weight, v36);
}
#else
inline uint64_t payout_share(uint64_t weight, uint64_t worker_payout,
uint64_t total_weight, bool v36) {
return payout_share_portable(weight, worker_payout, total_weight, v36);
}
#endif

} // namespace payout
} // namespace dash

#endif // C2POOL_DASH_PAYOUT_MULDIV_HPP
13 changes: 13 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,19 @@ if (BUILD_TESTING AND GTest_FOUND)
target_link_libraries(test_dash_coinbase_parity PRIVATE c2pool_payout c2pool_hashrate c2pool_merged_mining) # OBJECT-lib SCC direct-naming (#22/#39)
gtest_add_tests(test_dash_coinbase_parity "" AUTO)

# PPLNS coinbase payout muldiv: native(__int128) == portable(boost uint128)
# byte-parity -- MSVC-portability guard for coinbase_builder.hpp step 3-4
# (fixes c2pool-dash Windows v0.2.1 build). Pure header math; consensus.
add_executable(test_dash_coinbase_muldiv test_dash_coinbase_muldiv.cpp)
target_link_libraries(test_dash_coinbase_muldiv PRIVATE
GTest::gtest_main GTest::gtest
dash_x11 core
nlohmann_json::nlohmann_json
${Boost_LIBRARIES}
)
target_link_libraries(test_dash_coinbase_muldiv PRIVATE c2pool_payout c2pool_hashrate c2pool_merged_mining) # OBJECT-lib SCC direct-naming (#22/#39)
gtest_add_tests(test_dash_coinbase_muldiv "" AUTO)

add_executable(test_dash_cb_payee test_dash_cb_payee.cpp)
target_link_libraries(test_dash_cb_payee PRIVATE
GTest::gtest_main GTest::gtest
Expand Down
137 changes: 137 additions & 0 deletions test/test_dash_coinbase_muldiv.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// ---------------------------------------------------------------------------
// dash::payout 128-bit payout muldiv KAT -- the guard for the MSVC-portability
// rework of coinbase_builder.hpp (v0.2.1 c2pool-dash Windows build fix).
//
// coinbase_builder evaluates the PPLNS per-script coinbase amount in a 128-bit
// intermediate:
// v36 : amount = worker_payout * weight / total_weight
// pre-v36 : amount = worker_payout * 49 * weight / (total_weight * 50)
// GCC/Clang use native unsigned __int128; MSVC (no __int128, error C2065 on
// '__uint128_t') falls back to boost::multiprecision::uint128_t. This is
// CONSENSUS PAYOUT math -- a one-satoshi divergence forks payouts -- so the two
// paths MUST be BIT-IDENTICAL.
//
// Two layers (mirrors bch abla muldiv_kat / dgb arith256_muldiv_kat):
// A. cross-platform known answers: payout_share() / payout_share_portable()
// produce exact fixed results on EVERY compiler incl. MSVC (all MSVC can
// run -- there is no native to diff against there). Several exercise a
// product > 2^64, which a 64-bit multiply would wrap and fail.
// B. Linux-only equivalence sweep (guarded by __SIZEOF_INT128__): the
// portable path must equal the native path across the DASH payout operand
// space plus a deterministic wide fuzz, over BOTH the v36 and pre-v36
// branches. Proving portable == native on the trusted platform certifies
// the MSVC path this CI cannot run.
// ---------------------------------------------------------------------------

#include <gtest/gtest.h>

#include <cstdint>
#include <limits>

#include <impl/dash/payout_muldiv.hpp>

namespace {

using dash::payout::payout_share;
using dash::payout::payout_share_portable;

constexpr uint64_t U64MAX = std::numeric_limits<uint64_t>::max();

// Deterministic 64-bit LCG (no <random>, no Date/rand) -- reproducible vectors.
struct Lcg {
uint64_t s;
explicit Lcg(uint64_t seed) : s(seed) {}
uint64_t next() { s = s * 6364136223846793005ULL + 1442695040888963407ULL; return s; }
};

} // namespace

// ---- A. cross-platform known answers (run on ALL compilers incl. MSVC) ------
TEST(DashCoinbaseMuldiv, KnownAnswersV36) {
// v36: amount = worker_payout * weight / total_weight
EXPECT_EQ(payout_share(1, 100, 4, true), 25ULL);
EXPECT_EQ(payout_share(3, 100, 4, true), 75ULL);
EXPECT_EQ(payout_share(4, 100, 4, true), 100ULL); // whole weight -> full payout
EXPECT_EQ(payout_share(0, 100, 4, true), 0ULL);
// product > 2^64 (weight*worker_payout = 2^80): a 64-bit multiply would wrap.
EXPECT_EQ(payout_share(1099511627776ULL, 1099511627776ULL,
1099511627776ULL, true), 1099511627776ULL);
}

TEST(DashCoinbaseMuldiv, KnownAnswersPreV36) {
// pre-v36: amount = worker_payout * 49 * weight / (total_weight * 50)
EXPECT_EQ(payout_share(50, 100, 50, false), 98ULL); // whole share = 98% (2% finder fee added separately)
EXPECT_EQ(payout_share(1, 100, 2, false), 49ULL);
EXPECT_EQ(payout_share(0, 100, 50, false), 0ULL);
// numerator worker_payout*49*weight ~ 2^75 (> 2^64) and den = total*50 > 2^64.
EXPECT_EQ(payout_share(1099511627776ULL, 1000000ULL,
1099511627776ULL, false), 980000ULL);
}

TEST(DashCoinbaseMuldiv, PortablePathSatisfiesKnownAnswers) {
// the MSVC fallback must independently produce the same answers.
EXPECT_EQ(payout_share_portable(1, 100, 4, true), 25ULL);
EXPECT_EQ(payout_share_portable(4, 100, 4, true), 100ULL);
EXPECT_EQ(payout_share_portable(1099511627776ULL, 1099511627776ULL,
1099511627776ULL, true), 1099511627776ULL);
EXPECT_EQ(payout_share_portable(50, 100, 50, false), 98ULL);
EXPECT_EQ(payout_share_portable(1099511627776ULL, 1000000ULL,
1099511627776ULL, false), 980000ULL);
}

#if defined(__SIZEOF_INT128__)
// ---- B. native == portable across the DASH payout domain (trusted platform) -
TEST(DashCoinbaseMuldiv, NativeEqualsPortableGrid) {
using dash::payout::payout_share_native;
// operand shapes from the coinbase_builder call site: weights are subsets of
// total_weight (so quotient <= worker_payout), worker_payout < 2^50.
const uint64_t totals[] = {
1, 2, 50, 100, 37938, 1000000ULL,
(1ULL << 30), (1ULL << 40), (1ULL << 62), U64MAX
};
const uint64_t payouts[] = {
0, 1, 100, 5000000000ULL, (1ULL << 40), (1ULL << 50) - 1
};
for (uint64_t total : totals) {
// weight sampled across [0, total]: 0, 1, total/2, total-1, total.
const uint64_t weights[] = {
0, 1, total / 2, total > 0 ? total - 1 : 0, total
};
for (uint64_t w : weights)
for (uint64_t wp : payouts)
for (bool v36 : {false, true}) {
EXPECT_EQ(payout_share_native(w, wp, total, v36),
payout_share_portable(w, wp, total, v36))
<< "w=" << w << " wp=" << wp
<< " total=" << total << " v36=" << v36;
}
}
}

TEST(DashCoinbaseMuldiv, NativeEqualsPortableFuzz) {
using dash::payout::payout_share_native;
Lcg rng(0x9E3779B97F4A7C15ULL);
int compared = 0;
for (int i = 0; i < 200000; ++i) {
uint64_t total = rng.next();
if (total == 0) total = 1;
const uint64_t w = rng.next() % total; // weight in [0, total)
const uint64_t wp = rng.next() & ((1ULL << 50) - 1); // worker_payout < 2^50
const bool v36 = (rng.next() & 1) != 0;
++compared;
ASSERT_EQ(payout_share_native(w, wp, total, v36),
payout_share_portable(w, wp, total, v36))
<< "fuzz i=" << i << " w=" << w << " wp=" << wp
<< " total=" << total << " v36=" << v36;
}
EXPECT_GT(compared, 1000);
std::cerr << "dash coinbase muldiv KAT: native==portable over "
<< compared << " fuzz vectors\n";
}
#else
TEST(DashCoinbaseMuldiv, NoInt128KnownAnswerLayerOnly) {
std::cerr << "dash coinbase muldiv KAT: no __int128 (MSVC) -- "
"known-answer layer only\n";
SUCCEED();
}
#endif
Loading