Skip to content

Commit 7f1495a

Browse files
authored
Merge pull request #280 from frstrtr/dgb/82-faithful-reconstruct-closure
dgb: make_reconstruct_closure_from_template — version-agnostic #82 closure builder
2 parents b3d6afc + d31ac0f commit 7f1495a

2 files changed

Lines changed: 197 additions & 0 deletions

File tree

src/impl/dgb/coin/reconstruct_closure.hpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
#include "reconstruct_won_block.hpp" // reconstruct_won_block, ReconstructedWonBlock, SmallBlockHeaderType, MutableTransaction
5757
#include "gentx_unpack.hpp" // unpack_gentx_coinbase, UnpackedGentx
5858
#include "won_block_dispatch.hpp" // WonBlockReconstructor
59+
#include "won_share_inputs.hpp" // WonShareInputs, won_share_inputs (#279)
5960

6061
namespace dgb
6162
{
@@ -122,5 +123,81 @@ make_reconstruct_closure(
122123
};
123124
}
124125

126+
127+
// ---------------------------------------------------------------------------
128+
// make_reconstruct_closure_from_template -- the version-AGNOSTIC #82 closure,
129+
// and the one the run-loop should install. reconstruct_won_block_from_template
130+
// (reconstruct_won_block.hpp) is the CORRECT won-block tx source per the
131+
// work.py @42ccca53 audit: the broadcast block's non-coinbase set is the GBT
132+
// TEMPLATE the miner was handed at job hand-out (current_work transactions[]),
133+
// NOT the share's transaction_hash_refs. That dissolves the "v34+ carries no
134+
// m_tx_info -> reconstruct fails" concern entirely -- the share never carried
135+
// the block tx set for ANY version. This builder composes the three version-
136+
// agnostic inputs into the run-loop WonBlockReconstructor:
137+
// won_share_fields_fn = won_share_inputs(chain.get_share(h)) (#279) ->
138+
// { small_header = share.m_min_header,
139+
// merkle_link = share.m_merkle_link }
140+
// gentx_bytes_fn = generate_share_transaction(share,...).bytes (#173)
141+
// -> unpack_gentx_coinbase -> { tx, txid }
142+
// template_other_txs_fn = the captured-GBT template's non-coinbase txs in
143+
// template order (#271). Empty today (mempool tx-
144+
// selection pending) => a valid coinbase-only block,
145+
// correct-and-empty, NOT fail-closed. It fills with
146+
// NO change to this seam as tx-selection lands.
147+
//
148+
// Same FAIL-CLOSED posture as make_reconstruct_closure: fires on the compute
149+
// thread for a won share, NEVER throws and NEVER emits a partial/wrong block --
150+
// any error is caught, logged LOUDLY, and yields std::nullopt (announce +
151+
// audit; the RPC submitblock fallback still attempts independently).
152+
//
153+
// Prefer THIS over make_reconstruct_closure for the run-loop install: the
154+
// ref-walk variant is a share-CHAIN peer-propagation mechanism, never the
155+
// block-broadcast source. Per-coin isolation: src/impl/dgb/ only.
156+
// p2pool-merged-v36 surface: NONE.
157+
// ---------------------------------------------------------------------------
158+
inline WonBlockReconstructor
159+
make_reconstruct_closure_from_template(
160+
std::function<WonShareInputs(const uint256&)> won_share_fields_fn,
161+
std::function<std::vector<unsigned char>(const uint256&)> gentx_bytes_fn,
162+
std::function<std::vector<MutableTransaction>(const uint256&)> template_other_txs_fn)
163+
{
164+
return [won_share_fields_fn = std::move(won_share_fields_fn),
165+
gentx_bytes_fn = std::move(gentx_bytes_fn),
166+
template_other_txs_fn = std::move(template_other_txs_fn)](
167+
const uint256& share_hash)
168+
-> std::optional<std::pair<std::vector<unsigned char>, std::string>>
169+
{
170+
try
171+
{
172+
// 1. share-side inputs the won share carries verbatim (#279).
173+
const WonShareInputs si = won_share_fields_fn(share_hash);
174+
175+
// 2. regenerate + unpack the share's SSOT gentx (block tx index 0).
176+
const UnpackedGentx ug = unpack_gentx_coinbase(gentx_bytes_fn(share_hash));
177+
178+
// 3. the captured-GBT template's non-coinbase set (template order).
179+
const std::vector<MutableTransaction> other_txs =
180+
template_other_txs_fn(share_hash);
181+
182+
// 4. frame [gentx] ++ template_other_txs via the assemble_won_block
183+
// SSOT (version-agnostic; empty other_txs => coinbase-only).
184+
ReconstructedWonBlock r = reconstruct_won_block_from_template(
185+
si.small_header, si.merkle_link, ug.tx, ug.txid, other_txs);
186+
187+
return std::make_pair(std::move(r.bytes), std::move(r.hex));
188+
}
189+
catch (const std::exception& e)
190+
{
191+
// Fail closed: announce + audit, never broadcast a partial/wrong
192+
// block. The RPC submitblock fallback still attempts independently.
193+
std::cout << "[DGB-POOL-BLOCK] won share " << share_hash.GetHex().substr(0, 16)
194+
<< " -- reconstruct (from-template) FAILED CLOSED (" << e.what()
195+
<< "); NOT broadcast on P2P arm (RPC fallback still attempts)"
196+
<< std::endl;
197+
return std::nullopt;
198+
}
199+
};
200+
}
201+
125202
} // namespace coin
126203
} // namespace dgb

src/impl/dgb/test/reconstruct_closure_test.cpp

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ using dgb::coin::unpack_gentx_coinbase;
4141
using dgb::coin::SmallBlockHeaderType;
4242
using dgb::coin::MutableTransaction;
4343
using 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

4548
namespace {
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

Comments
 (0)