From 0f52ac050dba3893fab357aa8d44104bc2212a4c Mon Sep 17 00:00:00 2001 From: frstrtr Date: Fri, 19 Jun 2026 21:28:36 +0000 Subject: [PATCH] dgb: make embedded template transactions[] a caller-supplied input (SSOT seam) build_work_template() hardcoded transactions[] to an empty array, baking the "no tx source wired" state into the SSOT itself. Lift it to a WorkTemplateInputs field (default empty json array) and pass it through verbatim, mirroring the truthful-conditional shape previousblockhash already holds. Behaviour is byte-identical today: the embedded node and stratum work source both construct WorkTemplateInputs without setting transactions, so an empty array is still emitted and nothing is fabricated. The fee total those txs carry is folded into coinbasevalue UPSTREAM via resolve_coinbase_value (#207 SSOT) -- the builder never derives the reward, so this field is display-only shape. This opens the seam for the follow-up mempool-source slice (get_sorted_txs_with_fees -> transactions[] + fee->coinbasevalue) without re-touching the SSOT. Fenced to src/impl/dgb/ only; no shared base, no other-coin tree, no build.yml. +1 KAT (TransactionsPassThroughVerbatim): a shaped array round-trips verbatim, an unset input stays an empty array. dgb_template_builder_test 8/8, dgb_embedded_coin_node_test 5/5 (default-empty preserved). --- src/impl/dgb/coin/template_builder.hpp | 18 ++++++++++-- src/impl/dgb/test/template_builder_test.cpp | 31 ++++++++++++++++++++- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/impl/dgb/coin/template_builder.hpp b/src/impl/dgb/coin/template_builder.hpp index ccacfb00f..64e4a36c2 100644 --- a/src/impl/dgb/coin/template_builder.hpp +++ b/src/impl/dgb/coin/template_builder.hpp @@ -72,8 +72,10 @@ class CoinNodeInterface { // 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 +// verbatim. The builder fabricates nothing: transactions[] is a caller-supplied +// pass-through (empty by default until a mempool tx source is wired -- truthful +// absence, never a fabricated tx; the fee total is folded into coinbasevalue +// UPSTREAM via resolve_coinbase_value, not here), 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 @@ -96,6 +98,16 @@ struct WorkTemplateInputs { // caller (work source: u256_be_display_hex). nullopt -> previousblockhash // omitted from the template. std::optional previousblockhash; + // GBT transactions[] array, already shaped by the caller (per-tx + // {data,txid,hash,fee} objects, the same shape btc's template_builder + // emits). The builder passes it through VERBATIM and shapes nothing -- the + // identical truthful-absence discipline as previousblockhash: it defaults + // to an empty array, so a caller that has wired no transaction source (the + // current embedded + stratum paths) emits an empty transactions[] and + // fabricates nothing. The fee total those txs carry is folded into + // coinbasevalue UPSTREAM via resolve_coinbase_value(total_fees) (#207 SSOT); + // the builder never derives the reward, so this field is display-only shape. + nlohmann::json transactions = nlohmann::json::array(); }; inline nlohmann::json build_work_template(const WorkTemplateInputs& in) @@ -122,7 +134,7 @@ inline nlohmann::json build_work_template(const WorkTemplateInputs& in) tmpl["version"] = version; tmpl["curtime"] = in.curtime; tmpl["mintime"] = mintime; - tmpl["transactions"] = nlohmann::json::array(); + tmpl["transactions"] = in.transactions; // caller-supplied; empty by default // previousblockhash: truthful conditional emit (see struct notes). if (in.previousblockhash) diff --git a/src/impl/dgb/test/template_builder_test.cpp b/src/impl/dgb/test/template_builder_test.cpp index 8b21422aa..691f5e4dc 100644 --- a/src/impl/dgb/test/template_builder_test.cpp +++ b/src/impl/dgb/test/template_builder_test.cpp @@ -86,7 +86,8 @@ TEST(DgbTemplateBuilder, EmptyChainMintimeIsZero) EXPECT_EQ(t.at("mintime").get(), 0); } -// transactions[] is always an empty array (embedded tx selection unwired); +// transactions[] is an empty array BY DEFAULT (no tx source wired into the +// inputs); bits is NEVER emitted (MultiShield V4 next-target is V37 -- the // bits is NEVER emitted (MultiShield V4 next-target is V37 — a Scrypt-only // walk would be a known-wrong difficulty). TEST(DgbTemplateBuilder, EmptyTransactionsAndNoBits) @@ -112,6 +113,34 @@ TEST(DgbTemplateBuilder, PreviousblockhashConditionalEmit) EXPECT_FALSE(without.contains("previousblockhash")); } +// transactions[] is a caller-supplied pass-through: a shaped array round-trips +// into the template VERBATIM (the seam a wired mempool source emits through), +// while an unset input stays an empty array (truthful absence by default). This +// is the same conditional-shape contract previousblockhash holds. +TEST(DgbTemplateBuilder, TransactionsPassThroughVerbatim) +{ + auto in = make_inputs(); + nlohmann::json tx = nlohmann::json::object(); + tx["data"] = "0100000001abcd"; + tx["txid"] = std::string(64, '1'); + tx["hash"] = std::string(64, '1'); + tx["fee"] = 4200; + in.transactions = nlohmann::json::array(); + in.transactions.push_back(tx); + + auto t = build_work_template(in); + ASSERT_TRUE(t.at("transactions").is_array()); + ASSERT_EQ(t.at("transactions").size(), 1u); + // byte-identical pass-through: the builder shapes nothing of its own. + EXPECT_EQ(t.at("transactions").dump(), in.transactions.dump()); + EXPECT_EQ(t.at("transactions")[0].at("fee").get(), 4200); + + // Unset -> empty array (default): never absent, never fabricated. + auto def = build_work_template(make_inputs()); + ASSERT_TRUE(def.at("transactions").is_array()); + EXPECT_TRUE(def.at("transactions").empty()); +} + // Divergence guard: identical inputs -> byte-identical template. This is the // whole point of the SSOT — the work source and embedded path cannot diverge. TEST(DgbTemplateBuilder, DeterministicForIdenticalInputs)