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)