@@ -41,6 +41,9 @@ using dgb::coin::unpack_gentx_coinbase;
4141using dgb::coin::SmallBlockHeaderType;
4242using dgb::coin::MutableTransaction;
4343using dgb::TxHashRefs;
44+ using dgb::coin::make_reconstruct_closure_from_template;
45+ using dgb::coin::reconstruct_won_block_from_template;
46+ using dgb::coin::WonShareInputs;
4447
4548namespace {
4649
@@ -276,3 +279,120 @@ TEST(DgbReconstructClosure, MalformedGentxBytesFailsClosed)
276279
277280 EXPECT_FALSE (closure (f.won ).has_value ());
278281}
282+
283+
284+ // =============================================================================
285+ // from-template closure (#82 version-AGNOSTIC path, #271 source + #279 share
286+ // fields). This is the closure the run-loop installs: its non-coinbase tx set
287+ // is the captured GBT template, NOT the share's tx_hash_refs, so there is no
288+ // ancestry walk and no known-tx store -- only the two share-side fields, the
289+ // gentx, and the template's other txs.
290+ // =============================================================================
291+
292+ // won_share_fields_fn knows ONLY the won share; throws for any other hash
293+ // (the run-loop's chain.get_share miss).
294+ static std::function<WonShareInputs(const uint256&)>
295+ won_fields (const uint256& won, SmallBlockHeaderType sh, ::dgb::MerkleLink link)
296+ {
297+ return [won, sh, link](const uint256& h) -> WonShareInputs {
298+ if (h != won) throw std::out_of_range (" won_share_fields: unknown share" );
299+ return WonShareInputs{sh, link};
300+ };
301+ }
302+
303+ // --- Test 7: from-template success == reconstruct_won_block_from_template -----
304+ TEST (DgbReconstructClosure, FromTemplateSuccessComposesIdenticalBlock)
305+ {
306+ auto sh = make_small_header ();
307+ ::dgb::MerkleLink link;
308+ uint256 won = H (" a0" );
309+ auto gentx_bytes = noseg_bytes (make_gentx ());
310+ auto ug = unpack_gentx_coinbase (gentx_bytes);
311+ std::vector<MutableTransaction> other = { make_tx (11 ), make_tx (22 ) };
312+
313+ auto expected =
314+ reconstruct_won_block_from_template (sh, link, ug.tx , ug.txid , other);
315+
316+ auto closure = make_reconstruct_closure_from_template (
317+ won_fields (won, sh, link),
318+ [gentx_bytes](const uint256&) { return gentx_bytes; },
319+ [other](const uint256&) { return other; });
320+
321+ auto got = closure (won);
322+ ASSERT_TRUE (got.has_value ());
323+ EXPECT_FALSE (got->first .empty ());
324+ EXPECT_EQ (got->first , expected.bytes ); // closure adds seams, not bytes
325+ EXPECT_EQ (got->second , expected.hex );
326+ }
327+
328+ // --- Test 8: empty template => valid coinbase-only block (NOT fail-closed) ----
329+ TEST (DgbReconstructClosure, FromTemplateEmptyOtherTxsIsCoinbaseOnly)
330+ {
331+ auto sh = make_small_header ();
332+ ::dgb::MerkleLink link;
333+ uint256 won = H (" a0" );
334+ auto gentx_bytes = noseg_bytes (make_gentx ());
335+ auto ug = unpack_gentx_coinbase (gentx_bytes);
336+
337+ auto expected = reconstruct_won_block_from_template (
338+ sh, link, ug.tx , ug.txid , std::vector<MutableTransaction>{});
339+
340+ auto closure = make_reconstruct_closure_from_template (
341+ won_fields (won, sh, link),
342+ [gentx_bytes](const uint256&) { return gentx_bytes; },
343+ [](const uint256&) { return std::vector<MutableTransaction>{}; });
344+
345+ auto got = closure (won);
346+ ASSERT_TRUE (got.has_value ()); // correct-and-empty, not nullopt
347+ EXPECT_EQ (got->first , expected.bytes );
348+ }
349+
350+ // --- Test 9: unknown share => nullopt (won_share_fields miss) -----------------
351+ TEST (DgbReconstructClosure, FromTemplateUnknownShareFailsClosed)
352+ {
353+ auto sh = make_small_header ();
354+ ::dgb::MerkleLink link;
355+ uint256 won = H (" a0" );
356+ auto gentx_bytes = noseg_bytes (make_gentx ());
357+
358+ auto closure = make_reconstruct_closure_from_template (
359+ won_fields (won, sh, link),
360+ [gentx_bytes](const uint256&) { return gentx_bytes; },
361+ [](const uint256&) { return std::vector<MutableTransaction>{}; });
362+
363+ EXPECT_FALSE (closure (H (" ff" )).has_value ()); // not the won share
364+ }
365+
366+ // --- Test 10: malformed gentx bytes => nullopt (unpack out_of_range) ----------
367+ TEST (DgbReconstructClosure, FromTemplateMalformedGentxFailsClosed)
368+ {
369+ auto sh = make_small_header ();
370+ ::dgb::MerkleLink link;
371+ uint256 won = H (" a0" );
372+ auto bad = noseg_bytes (make_gentx ());
373+ bad.push_back (0xff ); // trailing byte => unpack throws
374+
375+ auto closure = make_reconstruct_closure_from_template (
376+ won_fields (won, sh, link),
377+ [bad](const uint256&) { return bad; },
378+ [](const uint256&) { return std::vector<MutableTransaction>{}; });
379+
380+ EXPECT_FALSE (closure (won).has_value ());
381+ }
382+
383+ // --- Test 11: template source throws (non-out_of_range) => still nullopt ------
384+ TEST (DgbReconstructClosure, FromTemplateOtherTxsThrowFailsClosed)
385+ {
386+ auto sh = make_small_header ();
387+ ::dgb::MerkleLink link;
388+ uint256 won = H (" a0" );
389+ auto gentx_bytes = noseg_bytes (make_gentx ());
390+
391+ auto closure = make_reconstruct_closure_from_template (
392+ won_fields (won, sh, link),
393+ [gentx_bytes](const uint256&) { return gentx_bytes; },
394+ [](const uint256&) -> std::vector<MutableTransaction> {
395+ throw std::runtime_error (" template boom" ); });
396+
397+ EXPECT_FALSE (closure (won).has_value ()); // broad catch, not just out_of_range
398+ }
0 commit comments