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
20 changes: 20 additions & 0 deletions src/impl/dgb/coin/dgb_arith256.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,26 @@ struct u256 {

static u256 from_u64(uint64_t v) { u256 r; r.limb[0] = v; return r; }

// Construct from a 32-byte little-endian digest (the scrypt_1024_1_1_256
// PoW output): limb[0] holds the least-significant 8 bytes. Mirrors bitcoin
// UintToArith256 -- the Scrypt PoW hash is read little-endian for the
// hash <= target comparison, so the digest the embedded DigiByte Core port
// produces drops straight into HeaderSample::pow_hash at the ingest boundary
// in the SAME byte order the 256-bit satisfaction gate already compares.
// Depends only on <cstdint> / builtin unsigned char, so the standalone
// header guard keeps linking with NO btclibs scrypt dependency; the scrypt
// CALL itself lands at the ingest boundary in a following slice.
static u256 from_le_bytes(const unsigned char b[32]) {
u256 r;
for (int i = 0; i < 4; ++i) {
uint64_t v = 0;
for (int j = 0; j < 8; ++j)
v |= (uint64_t)b[i * 8 + j] << (8 * j);
r.limb[i] = v;
}
return r;
}

bool fits_u64() const { return limb[1] == 0 && limb[2] == 0 && limb[3] == 0; }
bool is_zero() const { return limb[0] == 0 && limb[1] == 0 && limb[2] == 0 && limb[3] == 0; }
uint64_t low64() const { return limb[0]; }
Expand Down
72 changes: 60 additions & 12 deletions src/impl/dgb/coin/header_chain.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@
// following slice — its inputs are pinned here so the multiply cannot reach a
// continuity header.

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <limits>
#include <vector>

#include <impl/dgb/coin/dgb_arith256.hpp>
Expand Down Expand Up @@ -232,18 +234,39 @@ class HeaderChain {
if (h.pow_hash > h.target)
return IngestResult::REJECTED;

// Consensus retarget gate: the declared target must equal the
// DigiShield next-target computed over the Scrypt-only window
// ending at the CURRENT tip (assembled BEFORE h is appended).
// expected == 0 means no Scrypt ancestor is in range yet
// (genesis/bootstrap) or the gate is unconfigured -- no retarget
// constraint is enforceable, so the header passes on its
// structural (non-zero) target alone. nBits-style exact match
// mirrors Bitcoin/DigiByte consensus: the header carries the
// required next-work value, not merely a target it satisfies.
const RetargetWindow rw = next_retarget_window(m_retarget_window);
const u256 expected = digishield_next_target(rw, m_ds_params);
if (!expected.is_zero() && !(h.target == expected))
// Parent-difficulty retarget gate: DEMOTED to a no-op for V36
// (integrator decision 2026-06-18, operator FYI'd). V36's defining
// constraint is p2pool-merged-v36 compatibility, and that reference
// NEVER re-derives parent difficulty -- it trusts the parent
// header's declared nBits and checks PoW against it. The two
// daemon-independent CheckProofOfWork halves above (pow_limit floor +
// scrypt(header) <= target) ARE the correct, complete V36
// parent-difficulty validation.
//
// Re-derivation is also structurally impossible here: DigiByte's
// live retarget is MultiShield V4, whose averaging window is GLOBAL
// across all 5 algos (Scrypt/SHA256d/Skein/Qubit/Odocrypt) with
// per-algo adjust + MedianTimePast deltas + /4 damping. A Scrypt-only
// header walk cannot reconstruct that window without full multi-algo
// header tracking == V37 (5-algo validation) by definition.
// digishield_next_target() / next_retarget_window() are retained as
// test scaffolding and a reference for the V37 embedded-daemon port;
// the ingest path deliberately does NOT call them here (an nBits-exact
// gate would only deepen the wrong single-algo retarget model). See
// V37 backlog: full V4 MultiShield recompute.

// MTP monotonicity (DigiByte Core ContextualCheckBlockHeader
// "time-too-old"): a Scrypt header's nTime must be STRICTLY GREATER
// than the median timestamp of the tip and its (up to) 10 nearest
// ancestors. This is the daemon-independent, Scrypt-only-walk-SAFE
// half of header time validation -- it reads ONLY timestamps already
// in the local header chain, with no difficulty re-derivation (the
// demoted V4-MultiShield recompute). It is also the standard
// anti-timewarp monotonicity floor the demoted nBits gate used to
// imply. Genesis (empty chain) is unconstrained: median_time_past()
// returns INT64_MIN, so the first header always passes. A rejection
// never mutates the chain (checked before push_back).
if (h.n_time <= median_time_past())
return IngestResult::REJECTED;

m_chain.push_back(h);
Expand Down Expand Up @@ -298,6 +321,31 @@ class HeaderChain {
uint64_t cumulative_work() const { return m_cumulative_work; }
std::size_t size() const { return m_chain.size(); }

// Bitcoin/DigiByte median-time-past span (ContextualCheckBlockHeader uses
// the tip + its 10 nearest ancestors == 11 timestamps).
static constexpr std::size_t MEDIAN_TIME_SPAN = 11;

// MedianTimePast: median nTime over the tip and its (up to) MEDIAN_TIME_SPAN
// nearest ancestors. Walks ALL appended headers regardless of algo --
// DigiByte Core's GetMedianTimePast walks the block index, which interleaves
// every algo, so continuity (non-Scrypt) headers DO contribute their
// timestamps to the median even though their PoW is never validated. Sorts a
// small fixed-size (<= 11) window and returns the upper-middle element
// (sorted[n/2]), matching DGB Core. Returns INT64_MIN for an empty chain so
// the genesis header is unconstrained.
int64_t median_time_past() const
{
if (m_chain.empty())
return std::numeric_limits<int64_t>::min();
const std::size_t n = std::min(m_chain.size(), MEDIAN_TIME_SPAN);
std::vector<int64_t> times;
times.reserve(n);
for (std::size_t i = 0; i < n; ++i)
times.push_back(m_chain[m_chain.size() - 1 - i].n_time);
std::sort(times.begin(), times.end());
return times[times.size() / 2];
}

private:
// Work proxy: inversely proportional to the target. The full-width field
// narrows to low64() for the proxy, keeping cumulative_work byte-identical
Expand Down
25 changes: 24 additions & 1 deletion src/impl/dgb/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ if (BUILD_TESTING AND GTest_FOUND)
nlohmann_json::nlohmann_json)
gtest_add_tests(dgb_work_source_test "" AUTO)

foreach(dgb_guard rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test)

foreach(dgb_guard rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test)
add_executable(${dgb_guard} ${dgb_guard}.cpp)
target_link_libraries(${dgb_guard} PRIVATE
GTest::gtest_main GTest::gtest nlohmann_json::nlohmann_json)
Expand Down Expand Up @@ -186,4 +187,26 @@ if (BUILD_TESTING AND GTest_FOUND)
dgb_coin pool sharechain)
gtest_add_tests(dgb_won_block_dispatch_test "" AUTO)


# header_chain_test compiles the btclibs Scrypt PoW TU set DIRECTLY (the
# same crypto/*.cpp sources btclibs builds, with the same default flags) so
# the M3 7b digest conversion is exercised against the REAL
# scrypt_1024_1_1_256 hash, not a synthetic digest. Only the scrypt + SHA256
# family is pulled in -- NOT the full btclibs archive (its siphash.o needs
# the removed base_uint<256> symbol) and NOT core/the dgb OBJECT lib. This
# is linking against an existing scrypt.cpp, no shared source modified: the
# single DGB smoke gate holds, no shared-base flag owed. Same target name,
# so it stays in BOTH build.yml --target allowlists unchanged.
set(_dgb_scrypt_tus
${CMAKE_SOURCE_DIR}/src/btclibs/crypto/scrypt.cpp
${CMAKE_SOURCE_DIR}/src/btclibs/crypto/sha256.cpp
${CMAKE_SOURCE_DIR}/src/btclibs/crypto/sha256_sse4.cpp
${CMAKE_SOURCE_DIR}/src/btclibs/crypto/sha256_sse41.cpp
${CMAKE_SOURCE_DIR}/src/btclibs/crypto/sha256_avx2.cpp
${CMAKE_SOURCE_DIR}/src/btclibs/crypto/sha256_shani.cpp)
add_executable(header_chain_test header_chain_test.cpp ${_dgb_scrypt_tus})
target_include_directories(header_chain_test PRIVATE ${CMAKE_SOURCE_DIR}/src/btclibs)
target_link_libraries(header_chain_test PRIVATE
GTest::gtest_main GTest::gtest nlohmann_json::nlohmann_json)
gtest_add_tests(header_chain_test "" AUTO)
endif()
Loading
Loading