Skip to content

Commit 68caa8a

Browse files
authored
dgb: pin embedded Mempool->shaper->template e2e assembly conformance (Phase B FEED) (#278)
The shaper (make_mempool_tx_source) and build_work_template are each pinned in isolation; nothing pinned that the two seams COMPOSE faithfully. Add an end-to-end KAT over the production embedded chain: Mempool -> make_mempool_tx_source() fee-sorted selection -> WorkTemplateInputs -> build_work_template(), asserting (a) tmpl[transactions] equals the shaper output byte-for-byte and in feerate-descending order (no reorder/drop/reshape across the seam), (b) every entry keeps the full p2pool-dgb-scrypt GBT {data,txid,hash,fee} shape, and (c) the same selections total_fees folds into tmpl[coinbasevalue] verbatim via the embedded_coinbase_value SSOT (no double count). Test-only, single-coin, no src/impl/dgb consensus delta; target already in both build.yml allowlists. Co-authored-by: frstrtr <frstrtr@users.noreply.github.com>
1 parent fac36c3 commit 68caa8a

1 file changed

Lines changed: 70 additions & 0 deletions

File tree

src/impl/dgb/test/embedded_tx_select_test.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@
2525
#include <impl/dgb/coin/embedded_tx_select.hpp>
2626
#include <impl/dgb/coin/mempool.hpp>
2727
#include <impl/dgb/coin/transaction.hpp>
28+
#include <impl/dgb/coin/template_builder.hpp>
29+
#include <impl/dgb/coin/embedded_coinbase_value.hpp>
2830

2931
#include <core/pack.hpp>
3032
#include <core/hash.hpp>
33+
#include <core/pow.hpp>
3134
#include <btclibs/util/strencodings.h>
3235

3336
using dgb::coin::Mempool;
@@ -219,3 +222,70 @@ TEST(DgbEmbeddedTxSelect, WeightCapSkipsHeavyButPacksLighter)
219222
EXPECT_EQ(sel.transactions[0]["txid"], compute_txid(light).GetHex());
220223
EXPECT_EQ(sel.total_fees, 100u);
221224
}
225+
226+
// ---------------------------------------------------------------------------
227+
// END-TO-END ASSEMBLY CONFORMANCE: the full embedded path the production
228+
// EmbeddedCoinNode wires -- Mempool -> make_mempool_tx_source() selection ->
229+
// WorkTemplateInputs.transactions + (subsidy + total_fees) coinbasevalue ->
230+
// build_work_template(). The isolated shaper tests above and the isolated
231+
// build_work_template pass-through test (dgb_template_builder_test) each pin
232+
// HALF; nothing pins that the two seams COMPOSE faithfully -- that the shaper's
233+
// fee-sorted entries survive into tmpl["transactions"] byte-for-byte and IN
234+
// ORDER, and that the SAME selection's total_fees folds into
235+
// tmpl["coinbasevalue"] through the #188/#207 embedded_coinbase_value SSOT.
236+
// This is the seam where a reorder/drop/double-count would silently desync the
237+
// embedded template from the frstrtr/p2pool-dgb-scrypt GBT consumer.
238+
// ---------------------------------------------------------------------------
239+
TEST(DgbEmbeddedTxSelect, EndToEndTemplateAssemblyConformance)
240+
{
241+
Mempool pool;
242+
MutableTransaction lo = tagged_tx(11, 0); // lowest feerate
243+
MutableTransaction mid = tagged_tx(22, 1);
244+
MutableTransaction hi = tagged_tx(33, 2); // highest feerate
245+
ASSERT_TRUE(pool.add_tx(lo));
246+
ASSERT_TRUE(pool.add_tx(mid));
247+
ASSERT_TRUE(pool.add_tx(hi));
248+
pool.set_tx_fee(compute_txid(lo), 250);
249+
pool.set_tx_fee(compute_txid(mid), 500);
250+
pool.set_tx_fee(compute_txid(hi), 900);
251+
252+
// 1) production shaper selects + shapes the fee-sorted transactions[].
253+
auto source = make_mempool_tx_source(pool, /*max_weight=*/4'000'000);
254+
const auto sel = source();
255+
ASSERT_EQ(sel.transactions.size(), 3u);
256+
EXPECT_EQ(sel.total_fees, 1650u); // 250 + 500 + 900
257+
258+
// 2) coinbasevalue folds total_fees through the embedded SSOT (#188) -- the
259+
// SAME definition the external-daemon GBT path resolves against.
260+
const uint32_t height = 1'000'000;
261+
const uint64_t subsidy = 64'200'000'000ull; // fixed subsidy for the KAT
262+
core::SubsidyFunc subsidy_func = [&](uint32_t) -> uint64_t { return subsidy; };
263+
const uint64_t coinbasevalue =
264+
dgb::coin::embedded_coinbase_value(subsidy_func, height, sel.total_fees);
265+
EXPECT_EQ(coinbasevalue, subsidy + 1650ull);
266+
267+
// 3) assemble the work template from that one selection.
268+
dgb::coin::WorkTemplateInputs in;
269+
in.next_height = height;
270+
in.coinbasevalue = coinbasevalue;
271+
in.curtime = 1'700'000'000;
272+
in.transactions = sel.transactions;
273+
const nlohmann::json tmpl = dgb::coin::build_work_template(in);
274+
275+
// transactions[] survive the seam byte-for-byte AND in feerate-desc order.
276+
ASSERT_TRUE(tmpl["transactions"].is_array());
277+
EXPECT_EQ(tmpl["transactions"], sel.transactions);
278+
ASSERT_EQ(tmpl["transactions"].size(), 3u);
279+
EXPECT_EQ(tmpl["transactions"][0]["txid"], compute_txid(hi).GetHex());
280+
EXPECT_EQ(tmpl["transactions"][1]["txid"], compute_txid(mid).GetHex());
281+
EXPECT_EQ(tmpl["transactions"][2]["txid"], compute_txid(lo).GetHex());
282+
// every entry keeps the full p2pool GBT {data,txid,hash,fee} shape.
283+
for (const auto& e : tmpl["transactions"]) {
284+
EXPECT_TRUE(e.contains("data"));
285+
EXPECT_TRUE(e.contains("txid"));
286+
EXPECT_TRUE(e.contains("hash"));
287+
EXPECT_TRUE(e.contains("fee"));
288+
}
289+
// coinbasevalue carries the folded fee total verbatim (no double count).
290+
EXPECT_EQ(tmpl["coinbasevalue"].get<uint64_t>(), subsidy + 1650ull);
291+
}

0 commit comments

Comments
 (0)