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 @@ -69,7 +69,7 @@ jobs:
test_address_resolution test_compute_share_target \
test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_block_assembly_test \
dgb_gentx_coinbase_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test dgb_gentx_share_path_test dgb_other_tx_resolver_test \
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_gentx_unpack_test dgb_work_source_test \
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test \
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test \
v37_test \
Expand Down Expand Up @@ -202,7 +202,7 @@ jobs:
test_address_resolution test_compute_share_target \
test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_block_assembly_test \
dgb_gentx_coinbase_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test dgb_gentx_share_path_test dgb_other_tx_resolver_test \
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_gentx_unpack_test dgb_work_source_test \
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test \
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test \
test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
Expand Down
112 changes: 112 additions & 0 deletions src/impl/dgb/coin/embedded_coin_node.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#pragma once
// ===========================================================================
// dgb::coin::EmbeddedCoinNode -- embedded (no external digibyted) work source.
//
// Implements CoinNodeInterface::getwork() (coin/template_builder.hpp) by
// assembling a GBT-compatible work template ENTIRELY from in-process embedded
// chain state -- the HeaderChain (coin/header_chain.hpp) plus the coin's
// subsidy schedule -- with NO external RPC. This is the SECOND caller of
// dgb::coin::build_work_template(): the stratum work source
// (DGBWorkSource::get_current_work_template, stratum/work_source.cpp) is the
// first. Routing BOTH the miner-facing stratum path and the embedded
// CoinNodeInterface path through the one build_work_template SSOT is what makes
// the "the two paths cannot emit a divergent template" guarantee CONCRETE
// rather than theoretical (integrator directive, 2026-06-19).
//
// CONSENSUS DISCIPLINE (identical to build_work_template + the stratum caller):
// * coinbasevalue is resolved through the #207 resolve_coinbase_value SSOT.
// On the embedded path there is no external GBT figure, so it is derived
// as subsidy_func(height) + total_fees via embedded_coinbase_value.hpp.
// The external-daemon GBT fallback (NodeRPC::getwork) is a SEPARATE path
// and is untouched -- this node is the no-external-daemon source only.
// * version pins the Scrypt lane (build_work_template, DGB_BLOCK_VERSION_SCRYPT).
// * transactions[] stays empty and total_fees is 0: embedded mempool tx
// selection is not wired yet, so the template fabricates no transactions
// (the same truthful-absence discipline the stratum caller keeps).
// * previousblockhash is emitted ONLY when HeaderChain::tip_hash() carries a
// real tip id, rendered through the coin/hash_format.hpp SSOT so it is
// byte-identical to the stratum caller's previousblockhash.
// * bits is HELD BACK (MultiShield V4 next-target is a 5-algo global window
// == V37; a Scrypt-only walk would emit a known-wrong difficulty). It
// becomes a GBT pass-through once an external-daemon bits source is plumbed.
//
// Header-only + pure w.r.t. its inputs: make_inputs(curtime) is split out so
// the assembly is deterministically unit-testable without a wall clock or a
// running node; getwork() supplies std::time(nullptr) in production exactly as
// the stratum caller does.
// ===========================================================================

#include <ctime>
#include <cstdint>
#include <optional>

#include <core/pow.hpp> // core::SubsidyFunc

#include "header_chain.hpp" // c2pool::dgb::HeaderChain
#include "hash_format.hpp" // u256_be_display_hex SSOT
#include "embedded_coinbase_value.hpp" // resolve_coinbase_value SSOT (#207)
#include "template_builder.hpp" // build_work_template SSOT + CoinNodeInterface
#include "rpc_data.hpp" // rpc::WorkData

namespace dgb::coin
{

class EmbeddedCoinNode : public CoinNodeInterface
{
public:
EmbeddedCoinNode(c2pool::dgb::HeaderChain& chain,
core::SubsidyFunc subsidy_func)
: m_chain(chain), m_subsidy_func(std::move(subsidy_func))
{
}

// Assemble the build_work_template inputs from the live embedded chain
// state. Split from getwork() so the curtime is injectable for
// deterministic tests (the stratum caller does the same with std::time).
WorkTemplateInputs make_inputs(int64_t curtime) const
{
const uint32_t height = m_chain.next_block_height();

WorkTemplateInputs in;
in.next_height = height;
// Embedded path: no external GBT value -> derive subsidy(height)+fees.
// total_fees is 0 until embedded mempool tx selection is wired.
in.coinbasevalue = resolve_coinbase_value(m_subsidy_func, height,
/*total_fees=*/0,
/*gbt_coinbasevalue=*/std::nullopt);
in.median_time_past = m_chain.median_time_past();
in.curtime = curtime;
if (auto th = m_chain.tip_hash())
in.previousblockhash = u256_be_display_hex(*th);
return in;
}

// CoinNodeInterface: produce a WorkData carrying the embedded template.
// m_hashes stays empty (transactions[] is empty), m_latency 0 (no RPC).
rpc::WorkData getwork() override
{
rpc::WorkData wd;
wd.m_data = build_work_template(make_inputs(std::time(nullptr)));
return wd;
}

// Minimal chain-info shaped like getblockchaininfo's relevant fields. The
// embedded chain reports its absolute tip height (0 on an empty chain).
nlohmann::json getblockchaininfo() override
{
nlohmann::json info = nlohmann::json::object();
info["blocks"] = m_chain.tip_height().value_or(0);
return info;
}

// Not synced until the embedded P2P header-download ingest is wired (it is
// what populates HeaderSample.block_hash and gives real UTXO depth). A
// truthful false, never an optimistic claim of readiness.
bool is_synced() const override { return false; }

private:
c2pool::dgb::HeaderChain& m_chain;
core::SubsidyFunc m_subsidy_func;
};

} // namespace dgb::coin
41 changes: 41 additions & 0 deletions src/impl/dgb/coin/hash_format.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once
// ---------------------------------------------------------------------------
// dgb::coin::u256_be_display_hex -- SSOT for rendering a u256 as the
// GBT-conventional big-endian block-hash display hex (most-significant limb
// first, 64 lowercase hex digits, no 0x prefix; mirrors uint256::GetHex()
// ordering for a hash stored with limb[0] least-significant).
//
// Header-only u256 (coin/dgb_arith256.hpp) has no GetHex(), and the consuming
// TUs must not depend on btclibs' uint256, so the limbs are formatted directly.
//
// SSOT rationale: the stratum work source (stratum/work_source.cpp,
// previousblockhash + get_current_gbt_prevhash) and the embedded work path
// (coin/embedded_coin_node.hpp) both render the tip hash for the GBT template.
// Drawing both from ONE formatter means the two callers of build_work_template
// can never emit a previousblockhash in a divergent byte-encoding -- the same
// "cannot diverge" intent build_work_template itself enforces for the template
// body.
// ---------------------------------------------------------------------------

#include <cstdint>
#include <string>

#include "dgb_arith256.hpp" // dgb::coin::u256

namespace dgb::coin
{

inline std::string u256_be_display_hex(const u256& v)
{
static constexpr char H[] = "0123456789abcdef";
std::string out;
out.reserve(64);
for (int li = 3; li >= 0; --li) {
const uint64_t w = v.limb[li];
for (int sh = 60; sh >= 0; sh -= 4)
out.push_back(H[(w >> sh) & 0xF]);
}
return out;
}

} // namespace dgb::coin
26 changes: 22 additions & 4 deletions src/impl/dgb/coin/header_chain.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,11 @@ using ::dgb::coin::u256;
// == 0 is the default for "scrypt(header) not evaluated here" (trivially
// satisfies any target); the daemon port fills it.
struct HeaderSample {
int32_t n_version = 0;
int64_t n_time = 0;
u256 target = 0; // expanded PoW target (smaller == more work)
u256 pow_hash = 0; // scrypt(header) digest; hash <= target == valid PoW
int32_t n_version = 0;
int64_t n_time = 0;
u256 target = 0; // expanded PoW target (smaller == more work)
u256 pow_hash = 0; // scrypt(header) digest; hash <= target == valid PoW
u256 block_hash = 0; // sha256d(header) block id; 0 == not populated here
};

// Outcome of validating + ingesting one header.
Expand Down Expand Up @@ -358,6 +359,23 @@ class HeaderChain {
return static_cast<uint32_t>(m_base_height + (m_chain.size() - 1));
}

// Block hash of the newest header, or nullopt when the chain is empty OR
// the tip carries no hash. DGB's block id is sha256d over the 80-byte
// header (params.hpp block_hash_func == sha256d) -- distinct from pow_hash,
// which is the scrypt(header) PoW digest. HeaderSample stores it as a u256
// and the work-template emitter renders the GBT-conventional big-endian
// display hex. block_hash == 0 is the "not populated here" sentinel (the
// SAME convention pow_hash uses): the embedded-daemon header-ingest port
// fills it at the validate_and_append boundary, so until that lands this
// returns nullopt and get_current_work_template holds previousblockhash
// back -- a truthful absence, never a fabricated hash.
std::optional<u256> tip_hash() const
{
if (m_chain.empty() || m_chain.back().block_hash.is_zero())
return std::nullopt;
return m_chain.back().block_hash;
}

// Absolute height of the block the next template builds on top of the tip
// == tip_height()+1, or m_base_height for an empty chain. This is the
// template builder's `next_h` (btc template_builder.hpp: tip.height + 1).
Expand Down
77 changes: 77 additions & 0 deletions src/impl/dgb/coin/template_builder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@
// matching config_coin.hpp (namespace dgb) and the btc::coin / ltc::coin
// pattern the family-1 seam binds against.

#include <cstdint>
#include <limits>
#include <optional>
#include <stdexcept>
#include <string>

#include <nlohmann/json.hpp>

#include "rpc_data.hpp"
#include "dgb_block_algo.hpp" // DGB_BLOCK_VERSION_SCRYPT (Scrypt lane pin)

namespace dgb
{
Expand Down Expand Up @@ -55,6 +60,78 @@ class CoinNodeInterface {
// verbatim from btc once coin/block.hpp is ported.
};


// ── Work-template assembly SSOT (Stage 4c) ──────────────────────────────────
// build_work_template() shapes the already-resolved field values into the
// GBT-compatible JSON template that DGBWorkSource::get_current_work_template()
// returns. Lifting the assembly here makes the stratum work source and the
// embedded path emit ONE template object -- they cannot diverge once both call
// this SSOT (the same intent as routing get_current_gbt_prevhash through
// tip_hash() in Stage 4b).
//
// NON-CONSENSUS: this function only SHAPES values; it never derives or alters
// the consensus-bearing coinbasevalue. That value is computed by the caller
// through the #207 resolve_coinbase_value -> subsidy_func SSOT and passed in
// verbatim. The builder fabricates nothing: transactions[] stays empty
// (embedded mempool tx selection is not wired, fees stay 0), previousblockhash
// is emitted ONLY when the caller supplies a real tip hash (truthful absence,
// never a fabricated id), and `bits` is held back entirely -- DGB Core's live
// next-target is MultiShield V4 (a global 5-algo window == V37), so a
// Scrypt-only walk would emit a known-wrong difficulty (the same fabrication
// the empty transactions[] deliberately avoids). bits becomes a GBT
// pass-through once the external-daemon path is plumbed in.
struct WorkTemplateInputs {
// Absolute height of the NEXT block (#209 next_block_height()).
uint32_t next_height = 0;
// Reward for the next block, already resolved via the #207 SSOT. Passed in
// verbatim; the builder never recomputes or scales it.
uint64_t coinbasevalue = 0;
// DGB Core ContextualCheckBlockHeader lower bound source (median-time-past).
// INT64_MIN means an empty chain (unconstrained) -> mintime emits 0.
int64_t median_time_past = std::numeric_limits<int64_t>::min();
// GBT suggested header nTime. Injected by the caller (work source: wall
// clock) so the assembly is deterministically testable.
int64_t curtime = 0;
// Tip block id as GBT big-endian display hex, already formatted by the
// caller (work source: u256_be_display_hex). nullopt -> previousblockhash
// omitted from the template.
std::optional<std::string> previousblockhash;
};

inline nlohmann::json build_work_template(const WorkTemplateInputs& in)
{
// version: BIP9 base | DGB Scrypt algo nibble (dgb_block_algo.hpp SSOT). A
// DGB template MUST pin the Scrypt lane -- the mining algo lives in 4
// nVersion bits and Scrypt is the all-zero codepoint (DGB_BLOCK_VERSION_SCRYPT
// == 0x0000); any other nibble is a non-Scrypt algo this V36 binary never
// emits a template for.
static constexpr uint32_t BIP9_BASE_VERSION = 0x20000000u;
const uint32_t version =
BIP9_BASE_VERSION |
static_cast<uint32_t>(DGB_BLOCK_VERSION_SCRYPT);

// mintime: median_time_past()+1 (DGB Core's nTime > MTP lower bound). An
// empty chain reports INT64_MIN (unconstrained) -> emit 0.
const int64_t mintime =
(in.median_time_past == std::numeric_limits<int64_t>::min())
? 0 : (in.median_time_past + 1);

nlohmann::json tmpl = nlohmann::json::object();
tmpl["height"] = in.next_height;
tmpl["coinbasevalue"] = in.coinbasevalue;
tmpl["version"] = version;
tmpl["curtime"] = in.curtime;
tmpl["mintime"] = mintime;
tmpl["transactions"] = nlohmann::json::array();

// previousblockhash: truthful conditional emit (see struct notes).
if (in.previousblockhash)
tmpl["previousblockhash"] = *in.previousblockhash;

return tmpl;
}


} // namespace coin

} // namespace dgb
Loading
Loading