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_header_sample_build_test dgb_header_ingest_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_template_builder_test dgb_embedded_coin_node_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 dgb_embedded_tx_select_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_header_sample_build_test dgb_header_ingest_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_template_builder_test dgb_embedded_coin_node_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 dgb_embedded_tx_select_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
32 changes: 26 additions & 6 deletions src/c2pool/main_dgb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <impl/dgb/coin/mempool.hpp>
#include <impl/dgb/coin/coin_node.hpp>
#include <impl/dgb/coin/embedded_coin_node.hpp>
#include <impl/dgb/coin/embedded_tx_select.hpp> // make_mempool_tx_source (EmbeddedTxSource)
#include <impl/dgb/coin/won_block_dispatch.hpp>
#include <impl/dgb/stratum/work_source.hpp>

Expand Down Expand Up @@ -195,12 +196,31 @@ int run_node(const core::CoinParams& params, bool testnet,
// the same non-owning ref.
c2pool::dgb::HeaderChain header_chain;

// Embedded mempool — the in-process pool the work template selects from.
// Declared HERE (ahead of embedded_coin) because the injected
// EmbeddedTxSource below captures it by reference and MUST be outlived by
// it; reverse-order destruction tears embedded_coin (and its source) down
// before mempool. FEED: wire_mempool_ingest (coin/mempool_ingest.hpp, #245)
// subscribes this pool to an ::dgb::interfaces::Node new_tx relay — but no
// embedded coin-daemon P2P node is constructed in this run-loop yet (the M3
// embedded port; header_chain is likewise still unfed), so nothing calls
// add_tx and the pool stays empty until that node lands. The selection
// below is therefore byte-identical to the subsidy-only #237 baseline today.
dgb::coin::Mempool mempool;

// Embedded in-process work source: assembles GBT-compatible templates
// ENTIRELY from embedded chain state + the coin subsidy schedule (no
// external digibyted). coinbasevalue resolves through the #207 ->
// subsidy_func SSOT; transactions[]/bits are held back truthfully until
// the embedded mempool + next-target source are plumbed.
dgb::coin::EmbeddedCoinNode embedded_coin(header_chain, params.subsidy_func);
// subsidy_func SSOT; bits are held back truthfully until the next-target
// source is plumbed. transactions[] + the fee total come from an injected
// make_mempool_tx_source over the embedded mempool (#244 seam): fee-sorted
// txs up to BLOCK_MAX_WEIGHT with their fees folded into coinbasevalue via
// the #207 SSOT. The source returns an EMPTY selection while the mempool is
// unfed (see above), so the served template stays at the #237 baseline
// until live `tx` ingest lands.
dgb::coin::EmbeddedCoinNode embedded_coin(
header_chain, params.subsidy_func,
dgb::coin::make_mempool_tx_source(mempool, dgb::PoolConfig::BLOCK_MAX_WEIGHT));

// CoinNode seam — embedded-preferred work source (embedded_coin) with the
// external-digibyted submitblock FALLBACK leg of the #82 dual-path
Expand Down Expand Up @@ -263,9 +283,9 @@ int run_node(const core::CoinParams& params, bool testnet,
// emitted — the standup proves the StratumServer<->IWorkSource wiring
// end-to-end. Real work-gen / Scrypt share-validation land in 4b/4c.
// This mirrors btc::stratum standing its skeleton wiring up first.
// header_chain is declared above coin_node (it backs the EmbeddedCoinNode
// work source); only the unwired Mempool is declared here.
dgb::coin::Mempool mempool; // unwired (no UTXO/template feed yet)
// header_chain AND mempool are both declared above coin_node now — mempool
// backs the injected EmbeddedCoinNode tx source (declared there so it
// outlives the capturing source).

// submitblock-RPC arm of the #82 dual-path broadcaster, driven from the
// miner-facing Stratum path. Reuses the SAME coin_node seam declared above
Expand Down
3 changes: 3 additions & 0 deletions src/impl/dgb/coin/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ set(dgb_coin_interface
header_chain.hpp
mempool.hpp
template_builder.hpp
embedded_coinbase_value.hpp
embedded_coin_node.hpp
embedded_tx_select.hpp embedded_tx_select.cpp
)

add_library(dgb_coin ${dgb_coin_interface} ${dgb_coin_impl})
Expand Down
59 changes: 51 additions & 8 deletions src/impl/dgb/coin/embedded_coin_node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@
// 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).
// * transactions[] + the fee total come from an injected EmbeddedTxSource
// (default empty -> empty transactions[], 0 fees, byte-identical to the
// pre-tx-select call site). The production source (embedded_tx_select.cpp,
// make_mempool_tx_source) selects fee-sorted mempool txs; their fee total
// is folded into coinbasevalue via resolve_coinbase_value (#207), never
// fabricated, and transactions[] is passed through build_work_template
// verbatim.
// * 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.
Expand All @@ -39,6 +43,8 @@
#include <ctime>
#include <cstdint>
#include <optional>
#include <functional>
#include <utility>

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

Expand All @@ -51,12 +57,37 @@
namespace dgb::coin
{

// Caller-supplied embedded transaction selection. Decouples EmbeddedCoinNode
// from the heavy tx/UTXO codec (mempool.hpp -> transaction.hpp pulls the
// btclibs serialization templates -- the #143 SCC trap): the node consumes
// only the ALREADY-SHAPED GBT transactions[] array + the fee total, both plain
// JSON/integer, so this header stays codec-free and a guard-weight TU that
// reaches it never compiles the serialization templates. The production shaper
// lives out-of-line in embedded_tx_select.cpp (make_mempool_tx_source).
struct EmbeddedTxSelection
{
// GBT transactions[] array, per-tx {data,txid,hash,fee} -- build_work_template
// passes it through verbatim. Empty array = no txs selected.
nlohmann::json transactions = nlohmann::json::array();
// Sum of the known fees across the selected txs, folded into coinbasevalue
// via the #207 resolve_coinbase_value SSOT (NOT added to the template here).
uint64_t total_fees = 0;
};

// Injected transaction source. An empty std::function (the default) means NO
// transaction source is wired: the node emits an empty transactions[] and 0
// fees -- byte-identical to the pre-tx-select #237 call site. The production
// wiring injects make_mempool_tx_source(mempool, max_weight).
using EmbeddedTxSource = std::function<EmbeddedTxSelection()>;

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

Expand All @@ -67,15 +98,26 @@ class EmbeddedCoinNode : public CoinNodeInterface
{
const uint32_t height = m_chain.next_block_height();

// Pull the caller-supplied transaction selection (empty when no source
// is wired -> byte-identical to the pre-tx-select template). The heavy
// codec that shapes these lives out-of-line (embedded_tx_select.cpp);
// this node only folds the fee total into coinbasevalue and passes the
// transactions[] array through build_work_template.
EmbeddedTxSelection sel;
if (m_tx_source)
sel = m_tx_source();

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.
// Embedded path: no external GBT value -> derive subsidy(height)+fees
// via the #207 SSOT. total_fees comes from the selected txs (0 when no
// source is wired).
in.coinbasevalue = resolve_coinbase_value(m_subsidy_func, height,
/*total_fees=*/0,
/*total_fees=*/sel.total_fees,
/*gbt_coinbasevalue=*/std::nullopt);
in.median_time_past = m_chain.median_time_past();
in.curtime = curtime;
in.transactions = std::move(sel.transactions);
if (auto th = m_chain.tip_hash())
in.previousblockhash = u256_be_display_hex(*th);
return in;
Expand Down Expand Up @@ -107,6 +149,7 @@ class EmbeddedCoinNode : public CoinNodeInterface
private:
c2pool::dgb::HeaderChain& m_chain;
core::SubsidyFunc m_subsidy_func;
EmbeddedTxSource m_tx_source;
};

} // namespace dgb::coin
64 changes: 64 additions & 0 deletions src/impl/dgb/coin/embedded_tx_select.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// ===========================================================================
// embedded_tx_select.cpp -- out-of-line shaper for the embedded tx source.
//
// The tx/UTXO serialization codec (TX_WITH_WITNESS pack + Hash + HexStr) lives
// here, NOT in any header, so embedded_coin_node.hpp stays codec-free and the
// #143 btclibs SCC trap stays shut. Compiled into dgb_coin (which already links
// the full btclibs serialization), so the codec resolves at link time.
//
// Mirrors the per-tx GBT entry shape src/impl/btc/coin/template_builder.hpp
// emits, so c2pool-dgb and the p2pool-dgb-scrypt reference consume an identical
// transactions[] form (p2pool reads `fee` in helper.py:123 and adjusts subsidy
// in data.py:876-884 when a tx is excluded from the share).
// ===========================================================================

#include "embedded_tx_select.hpp"

#include "transaction.hpp" // TX_WITH_WITNESS, compute_txid is in mempool.hpp

#include <core/pack.hpp> // pack
#include <core/hash.hpp> // Hash (sha256d)
#include <btclibs/util/strencodings.h> // HexStr

#include <string>
#include <utility>

namespace dgb::coin
{

EmbeddedTxSource make_mempool_tx_source(Mempool& pool, uint32_t max_weight)
{
return [&pool, max_weight]() -> EmbeddedTxSelection {
auto [selected, total_fees] = pool.get_sorted_txs_with_fees(max_weight);

EmbeddedTxSelection out;
out.total_fees = total_fees;

nlohmann::json tx_array = nlohmann::json::array();
for (const auto& stx : selected)
{
// data = with-witness serialization hex (what a daemon submits)
// txid = legacy (non-witness) sha256d
// hash = wtxid (with-witness sha256d) for the witness merkle tree
const uint256 txid = compute_txid(stx.tx);
auto packed = pack(TX_WITH_WITNESS(stx.tx));
const std::string hex_data = HexStr(packed.get_span());
const uint256 wtxid = Hash(packed.get_span());

nlohmann::json entry;
entry["data"] = hex_data;
entry["txid"] = txid.GetHex();
entry["hash"] = wtxid.GetHex();
if (stx.fee_known)
entry["fee"] = static_cast<int64_t>(stx.fee);
else
entry["fee"] = nullptr; // JSON null -> p2pool base_subsidy fallback
tx_array.push_back(std::move(entry));
}

out.transactions = std::move(tx_array);
return out;
};
}

} // namespace dgb::coin
40 changes: 40 additions & 0 deletions src/impl/dgb/coin/embedded_tx_select.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#pragma once
// ===========================================================================
// embedded_tx_select.hpp -- production EmbeddedTxSource factory.
//
// Builds the dgb::coin::EmbeddedTxSource that EmbeddedCoinNode injects to fill
// the embedded work template's transactions[] from the in-process Mempool. The
// actual shaping (Mempool::SelectedTx -> {data,txid,hash,fee}) is the heavy
// tx/UTXO serialization codec, so it lives OUT-OF-LINE in embedded_tx_select.cpp
// (compiled into dgb_coin) -- NOT in this header and NOT in embedded_coin_node.hpp.
// Keeping the codec in a .cpp is what holds the #143 btclibs SCC trap shut: a
// guard-weight TU that reaches embedded_coin_node.hpp never compiles the
// transaction serialization templates.
//
// This header DOES pull mempool.hpp (-> transaction.hpp), so include it ONLY
// from TUs that already link the full dgb_coin codec (main_dgb.cpp + the codec
// conformance test), never from a guard-weight TU.
// ===========================================================================

#include <cstdint>

#include "embedded_coin_node.hpp" // EmbeddedTxSource / EmbeddedTxSelection
#include "mempool.hpp" // dgb::coin::Mempool

namespace dgb::coin
{

// Build an EmbeddedTxSource that, on each invocation, selects up to max_weight
// BIP141 weight units of fee-sorted transactions from `pool` and shapes them
// into the GBT transactions[] form build_work_template passes through verbatim:
// {data: hex(TX_WITH_WITNESS), txid: txid, hash: wtxid, fee: <int|null>}
// The returned selection's total_fees is the sum of the known fees across the
// selected txs (the SAME figure get_sorted_txs_with_fees reports), folded into
// coinbasevalue by EmbeddedCoinNode via resolve_coinbase_value (#207 SSOT) --
// NOT added to the template here.
//
// `pool` is captured by reference -- it MUST outlive the returned source (the
// embedded node and its mempool share a lifetime).
EmbeddedTxSource make_mempool_tx_source(Mempool& pool, uint32_t max_weight);

} // namespace dgb::coin
15 changes: 15 additions & 0 deletions src/impl/dgb/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,21 @@ if (BUILD_TESTING AND GTest_FOUND)
nlohmann_json::nlohmann_json)
gtest_add_tests(dgb_embedded_coin_node_test "" AUTO)

# dgb_embedded_tx_select_test: guards make_mempool_tx_source
# (embedded_tx_select.cpp), the production Mempool -> GBT transactions[]
# {data,txid,hash,fee} + fee-total shaper EmbeddedCoinNode injects. Compiles
# the tx serialization codec, so it links the same proven SCC set as
# dgb_embedded_coin_node_test. MUST also be in BOTH build.yml --target
# allowlists (#143 NOT_BUILT trap).
add_executable(dgb_embedded_tx_select_test embedded_tx_select_test.cpp)
target_link_libraries(dgb_embedded_tx_select_test PRIVATE
GTest::gtest_main GTest::gtest
core dgb dgb_stratum
c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage
dgb_coin pool sharechain
nlohmann_json::nlohmann_json)
gtest_add_tests(dgb_embedded_tx_select_test "" AUTO)


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)
Expand Down
41 changes: 41 additions & 0 deletions src/impl/dgb/test/embedded_coin_node_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,44 @@ TEST(DgbEmbeddedCoinNode, GetworkProducesTemplateWithNoFabrication)
EXPECT_TRUE(wd.m_hashes.empty()); // no tx hashes fabricated
EXPECT_FALSE(node.is_synced()); // truthful: not synced yet
}

// 5. Injected tx source: the fee total folds into coinbasevalue via the #207
// SSOT and transactions[] passes through build_work_template verbatim.
// Codec-free: a stub source supplies an ALREADY-SHAPED selection, exactly
// as make_mempool_tx_source would, so this guard never compiles the tx
// serialization codec (the embedded_tx_select.cpp / dgb_embedded_tx_select_test
// pins the real shaping over a Mempool).
TEST(DgbEmbeddedCoinNode, InjectedTxSourceFoldsFeesAndPassesTransactions)
{
HeaderChain hc;
hc.validate_and_append(HeaderSample{SCRYPT, 1000, 100});

nlohmann::json txs = nlohmann::json::array();
nlohmann::json e1; e1["data"]="00"; e1["txid"]="aa"; e1["hash"]="aa"; e1["fee"]=700;
nlohmann::json e2; e2["data"]="01"; e2["txid"]="bb"; e2["hash"]="bb"; e2["fee"]=300;
txs.push_back(e1); txs.push_back(e2);
const uint64_t fees = 1000;

EmbeddedCoinNode node(hc, make_subsidy(),
[txs, fees]() -> dgb::coin::EmbeddedTxSelection { return {txs, fees}; });

const uint32_t h = hc.next_block_height();
const nlohmann::json t = build_work_template(node.make_inputs(7));
EXPECT_EQ(t["coinbasevalue"].get<uint64_t>(), 1000ull + h + fees); // subsidy(h)+fees
EXPECT_EQ(t["transactions"], txs); // verbatim pass-through
EXPECT_EQ(t["transactions"].size(), 2u);
}

// 6. Empty (default) tx source == byte-identical to the no-source template
// (the #237 call-site invariant: a node with no tx source fabricates nothing).
TEST(DgbEmbeddedCoinNode, EmptyTxSourceByteIdenticalToNoSource)
{
HeaderChain hc;
hc.validate_and_append(HeaderSample{SCRYPT, 1000, 100});

EmbeddedCoinNode no_src(hc, make_subsidy());
EmbeddedCoinNode empty_src(hc, make_subsidy(), dgb::coin::EmbeddedTxSource{});

EXPECT_EQ(build_work_template(empty_src.make_inputs(5)).dump(),
build_work_template(no_src.make_inputs(5)).dump());
}
Loading
Loading