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
80 changes: 56 additions & 24 deletions src/impl/dgb/config_coin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,34 +72,66 @@ class CoinParams
static constexpr uint32_t DUMB_SCRYPT_DIFF = 65536; // 2^16

// -----------------------------------------------------------------------
// DGB subsidy schedule (3-phase decay)
// Source: p2pool-dgb-scrypt oracle bitcoin/networks/digibyte.py
// DGB subsidy schedule -- EXACT replica of the p2pool-dgb-scrypt oracle
// get_subsidy() (bitcoin/networks/digibyte.py, IDENTIFIER 4B62545B1A631AFE).
// V36 conformance = parity with DGB's OWN canonical reference, so the
// oracle behaviour IS the spec -- including COIN=1e6 (NOT 1e8) and the
// weeks/months + 1 decay count. These are not quirks to "fix" (card #156).
// 3-bucket: COMPAT (pre-v36 per-coin baseline, temporary for the crossing;
// NOT a v36-native cross-coin standardization).
//
// Integer math mirrors the oracle bit-for-bit: each decay step is
// q -= q / N (truncating division), NOT q * (N-1) / N -- the two round
// differently for non-divisible q, and the oracle rounding is consensus.
// -----------------------------------------------------------------------
static uint64_t subsidy(uint32_t height)
{
static constexpr uint64_t COIN = 100'000'000; // 1 DGB = 10^8 satoshis

// Phase 1: Pre-DigiShield fixed rewards
if (height < 1440) return 72'000 * COIN;
if (height < 5760) return 16'000 * COIN;
if (height < 67'200) return 8'000 * COIN;

// Phase 2: -0.5% decay every 10,080 blocks
if (height < 400'000) {
uint64_t base = 8'000 * COIN;
uint32_t periods = (height - 67'200) / 10'080;
// Apply 0.995^periods via integer math
for (uint32_t i = 0; i < periods && base > COIN; ++i)
base = base * 995 / 1000;
return std::max(base, COIN);
static constexpr uint64_t COIN = 1'000'000; // oracle COIN = 1e6
static constexpr uint32_t nDiffChangeTarget = 67'200;
static constexpr uint32_t alwaysUpdateDiffChangeTarget = 400'000;
static constexpr uint32_t patchBlockRewardDuration = 10'080;
static constexpr uint32_t workComputationChangeTarget = 1'430'000;
static constexpr uint32_t patchBlockRewardDuration2 = 80'160;

uint64_t nSubsidy = COIN;
if (height < nDiffChangeTarget) {
// Phase 1: pre-DigiShield fixed rewards.
nSubsidy = 8'000 * COIN;
if (height < 1'440)
nSubsidy = 72'000 * COIN;
else if (height < 5'760)
nSubsidy = 16'000 * COIN;
} else if (height < alwaysUpdateDiffChangeTarget) {
// Phase 2: -0.5% per (weeks+1), weeks = blocks / 10080.
uint64_t qSubsidy = 8'000 * COIN;
uint32_t blocks = height - nDiffChangeTarget;
uint32_t weeks = (blocks / patchBlockRewardDuration) + 1;
for (uint32_t i = 0; i < weeks; ++i)
qSubsidy -= qSubsidy / 200;
nSubsidy = qSubsidy;
} else if (height < workComputationChangeTarget) {
// Phase 3: -1% per (weeks+1), weeks = blocks / 80160.
uint64_t qSubsidy = 2'459 * COIN;
uint32_t blocks = height - alwaysUpdateDiffChangeTarget;
uint32_t weeks = (blocks / patchBlockRewardDuration2) + 1;
for (uint32_t i = 0; i < weeks; ++i)
qSubsidy -= qSubsidy / 100;
nSubsidy = qSubsidy;
} else {
// Phase 4: monthly decay (x98884/100000) after work-computation change.
uint64_t qSubsidy = 2'157 * COIN / 2;
uint32_t blocks = height - workComputationChangeTarget;
uint32_t months = static_cast<uint32_t>(
static_cast<uint64_t>(blocks) * 15 / (3600 * 24 * 365 / 12));
for (uint32_t i = 0; i < months; ++i) {
qSubsidy *= 98'884;
qSubsidy /= 100'000;
}
nSubsidy = qSubsidy;
}

// Phase 3: -1% decay every 80,160 blocks
uint64_t base = 2'459 * COIN;
uint32_t periods = (height - 400'000) / 80'160;
for (uint32_t i = 0; i < periods && base > COIN; ++i)
base = base * 99 / 100;
return std::max(base, COIN);
if (nSubsidy < COIN)
nSubsidy = COIN;
return nSubsidy;
}

// GBT_ALGO: multi-algo coin requires specifying which algorithm
Expand Down
12 changes: 12 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
if (BUILD_TESTING AND GTest_FOUND)
# DGB block subsidy oracle-conformance (card #156). config_coin.hpp
# subsidy() is header-only/static; no dgb runtime link required.
add_executable(test_dgb_subsidy test_dgb_subsidy.cpp)
target_link_libraries(test_dgb_subsidy PRIVATE
GTest::gtest_main GTest::gtest
btclibs
yaml-cpp::yaml-cpp
nlohmann_json::nlohmann_json
${Boost_LIBRARIES}
)
gtest_discover_tests(test_dgb_subsidy DISCOVERY_MODE PRE_TEST)

add_executable(test_coin_broadcaster test_coin_broadcaster.cpp)
target_link_libraries(test_coin_broadcaster PRIVATE
GTest::gtest_main GTest::gtest
Expand Down
62 changes: 62 additions & 0 deletions test/test_dgb_subsidy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Regression test: DGB block subsidy vs the coin's OWN canonical oracle,
// p2pool-dgb-scrypt (bitcoin/networks/digibyte.py :: get_subsidy).
//
// Operator decision, card #156 (2026-06-17): V36 = parity with DGB's own
// reference, so the oracle behaviour IS the spec -- including COIN=1e6 and the
// weeks/months + 1 decay count. These are NOT quirks to "fix". 3-bucket: COMPAT
// (pre-v36 per-coin baseline, temporary for the crossing-soak).
//
// Vectors generated deterministically by executing the oracle get_subsidy() at
// the boundary and interior of every reward phase (all four phases + floor).

#include <gtest/gtest.h>
#include <cstdint>

#include <impl/dgb/config_coin.hpp>

namespace {

struct SubsidyVec { uint32_t height; uint64_t expected; };

// Oracle p2pool-dgb-scrypt get_subsidy() reference vectors (satoshis, COIN=1e6).
constexpr SubsidyVec kOracleVectors[] = {
{0, 72000000000ULL}, // phase 1: 72000 DGB
{1, 72000000000ULL},
{1439, 72000000000ULL},
{1440, 16000000000ULL}, // phase 1 step -> 16000
{5759, 16000000000ULL},
{5760, 8000000000ULL}, // phase 1 step -> 8000
{67199, 8000000000ULL},
{67200, 7960000000ULL}, // phase 2 in: weeks+1 = 1 -> -0.5%
{77280, 7920200000ULL},
{87359, 7920200000ULL},
{87360, 7880599000ULL},
{399999, 6746441103ULL},
{400000, 2434410000ULL}, // phase 3 in: base 2459, weeks+1 = 1 -> -1%
{480159, 2434410000ULL},
{480160, 2410065900ULL},
{1429999, 2157824200ULL},
{1430000, 1078500000ULL}, // phase 4 in: base 2157/2
{4057999, 921689224ULL},
{4058000, 911403172ULL},
{20000000, 331934836ULL},
};

} // namespace

TEST(DgbSubsidy, MatchesOracleVectors) {
for (const auto& v : kOracleVectors) {
EXPECT_EQ(dgb::CoinParams::subsidy(v.height), v.expected)
<< "subsidy(" << v.height << ") diverged from the p2pool-dgb-scrypt oracle";
}
}

TEST(DgbSubsidy, NeverBelowCoinFloor) {
// Oracle clamps: if nSubsidy < COIN -> COIN. COIN = 1e6 sat.
// Sweep deep into the monthly-decay region where the reward is floored.
for (uint32_t h = 0; h < 80u; ++h) {
const uint32_t height = 130000000u + h * 1000000u;
EXPECT_GE(dgb::CoinParams::subsidy(height), 1000000ULL)
<< "subsidy floor breached at height " << height;
}
}
Loading