Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions src/impl/dgb/coin/reconstruct_closure.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
#include "reconstruct_won_block.hpp" // reconstruct_won_block, ReconstructedWonBlock, SmallBlockHeaderType, MutableTransaction
#include "gentx_unpack.hpp" // unpack_gentx_coinbase, UnpackedGentx
#include "won_block_dispatch.hpp" // WonBlockReconstructor
#include "won_share_inputs.hpp" // WonShareInputs, won_share_inputs (#279)

namespace dgb
{
Expand Down Expand Up @@ -122,5 +123,81 @@ make_reconstruct_closure(
};
}


// ---------------------------------------------------------------------------
// make_reconstruct_closure_from_template -- the version-AGNOSTIC #82 closure,
// and the one the run-loop should install. reconstruct_won_block_from_template
// (reconstruct_won_block.hpp) is the CORRECT won-block tx source per the
// work.py @42ccca53 audit: the broadcast block's non-coinbase set is the GBT
// TEMPLATE the miner was handed at job hand-out (current_work transactions[]),
// NOT the share's transaction_hash_refs. That dissolves the "v34+ carries no
// m_tx_info -> reconstruct fails" concern entirely -- the share never carried
// the block tx set for ANY version. This builder composes the three version-
// agnostic inputs into the run-loop WonBlockReconstructor:
// won_share_fields_fn = won_share_inputs(chain.get_share(h)) (#279) ->
// { small_header = share.m_min_header,
// merkle_link = share.m_merkle_link }
// gentx_bytes_fn = generate_share_transaction(share,...).bytes (#173)
// -> unpack_gentx_coinbase -> { tx, txid }
// template_other_txs_fn = the captured-GBT template's non-coinbase txs in
// template order (#271). Empty today (mempool tx-
// selection pending) => a valid coinbase-only block,
// correct-and-empty, NOT fail-closed. It fills with
// NO change to this seam as tx-selection lands.
//
// Same FAIL-CLOSED posture as make_reconstruct_closure: fires on the compute
// thread for a won share, NEVER throws and NEVER emits a partial/wrong block --
// any error is caught, logged LOUDLY, and yields std::nullopt (announce +
// audit; the RPC submitblock fallback still attempts independently).
//
// Prefer THIS over make_reconstruct_closure for the run-loop install: the
// ref-walk variant is a share-CHAIN peer-propagation mechanism, never the
// block-broadcast source. Per-coin isolation: src/impl/dgb/ only.
// p2pool-merged-v36 surface: NONE.
// ---------------------------------------------------------------------------
inline WonBlockReconstructor
make_reconstruct_closure_from_template(
std::function<WonShareInputs(const uint256&)> won_share_fields_fn,
std::function<std::vector<unsigned char>(const uint256&)> gentx_bytes_fn,
std::function<std::vector<MutableTransaction>(const uint256&)> template_other_txs_fn)
{
return [won_share_fields_fn = std::move(won_share_fields_fn),
gentx_bytes_fn = std::move(gentx_bytes_fn),
template_other_txs_fn = std::move(template_other_txs_fn)](
const uint256& share_hash)
-> std::optional<std::pair<std::vector<unsigned char>, std::string>>
{
try
{
// 1. share-side inputs the won share carries verbatim (#279).
const WonShareInputs si = won_share_fields_fn(share_hash);

// 2. regenerate + unpack the share's SSOT gentx (block tx index 0).
const UnpackedGentx ug = unpack_gentx_coinbase(gentx_bytes_fn(share_hash));

// 3. the captured-GBT template's non-coinbase set (template order).
const std::vector<MutableTransaction> other_txs =
template_other_txs_fn(share_hash);

// 4. frame [gentx] ++ template_other_txs via the assemble_won_block
// SSOT (version-agnostic; empty other_txs => coinbase-only).
ReconstructedWonBlock r = reconstruct_won_block_from_template(
si.small_header, si.merkle_link, ug.tx, ug.txid, other_txs);

return std::make_pair(std::move(r.bytes), std::move(r.hex));
}
catch (const std::exception& e)
{
// Fail closed: announce + audit, never broadcast a partial/wrong
// block. The RPC submitblock fallback still attempts independently.
std::cout << "[DGB-POOL-BLOCK] won share " << share_hash.GetHex().substr(0, 16)
<< " -- reconstruct (from-template) FAILED CLOSED (" << e.what()
<< "); NOT broadcast on P2P arm (RPC fallback still attempts)"
<< std::endl;
return std::nullopt;
}
};
}

} // namespace coin
} // namespace dgb
120 changes: 120 additions & 0 deletions src/impl/dgb/test/reconstruct_closure_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ using dgb::coin::unpack_gentx_coinbase;
using dgb::coin::SmallBlockHeaderType;
using dgb::coin::MutableTransaction;
using dgb::TxHashRefs;
using dgb::coin::make_reconstruct_closure_from_template;
using dgb::coin::reconstruct_won_block_from_template;
using dgb::coin::WonShareInputs;

namespace {

Expand Down Expand Up @@ -276,3 +279,120 @@ TEST(DgbReconstructClosure, MalformedGentxBytesFailsClosed)

EXPECT_FALSE(closure(f.won).has_value());
}


// =============================================================================
// from-template closure (#82 version-AGNOSTIC path, #271 source + #279 share
// fields). This is the closure the run-loop installs: its non-coinbase tx set
// is the captured GBT template, NOT the share's tx_hash_refs, so there is no
// ancestry walk and no known-tx store -- only the two share-side fields, the
// gentx, and the template's other txs.
// =============================================================================

// won_share_fields_fn knows ONLY the won share; throws for any other hash
// (the run-loop's chain.get_share miss).
static std::function<WonShareInputs(const uint256&)>
won_fields(const uint256& won, SmallBlockHeaderType sh, ::dgb::MerkleLink link)
{
return [won, sh, link](const uint256& h) -> WonShareInputs {
if (h != won) throw std::out_of_range("won_share_fields: unknown share");
return WonShareInputs{sh, link};
};
}

// --- Test 7: from-template success == reconstruct_won_block_from_template -----
TEST(DgbReconstructClosure, FromTemplateSuccessComposesIdenticalBlock)
{
auto sh = make_small_header();
::dgb::MerkleLink link;
uint256 won = H("a0");
auto gentx_bytes = noseg_bytes(make_gentx());
auto ug = unpack_gentx_coinbase(gentx_bytes);
std::vector<MutableTransaction> other = { make_tx(11), make_tx(22) };

auto expected =
reconstruct_won_block_from_template(sh, link, ug.tx, ug.txid, other);

auto closure = make_reconstruct_closure_from_template(
won_fields(won, sh, link),
[gentx_bytes](const uint256&) { return gentx_bytes; },
[other](const uint256&) { return other; });

auto got = closure(won);
ASSERT_TRUE(got.has_value());
EXPECT_FALSE(got->first.empty());
EXPECT_EQ(got->first, expected.bytes); // closure adds seams, not bytes
EXPECT_EQ(got->second, expected.hex);
}

// --- Test 8: empty template => valid coinbase-only block (NOT fail-closed) ----
TEST(DgbReconstructClosure, FromTemplateEmptyOtherTxsIsCoinbaseOnly)
{
auto sh = make_small_header();
::dgb::MerkleLink link;
uint256 won = H("a0");
auto gentx_bytes = noseg_bytes(make_gentx());
auto ug = unpack_gentx_coinbase(gentx_bytes);

auto expected = reconstruct_won_block_from_template(
sh, link, ug.tx, ug.txid, std::vector<MutableTransaction>{});

auto closure = make_reconstruct_closure_from_template(
won_fields(won, sh, link),
[gentx_bytes](const uint256&) { return gentx_bytes; },
[](const uint256&) { return std::vector<MutableTransaction>{}; });

auto got = closure(won);
ASSERT_TRUE(got.has_value()); // correct-and-empty, not nullopt
EXPECT_EQ(got->first, expected.bytes);
}

// --- Test 9: unknown share => nullopt (won_share_fields miss) -----------------
TEST(DgbReconstructClosure, FromTemplateUnknownShareFailsClosed)
{
auto sh = make_small_header();
::dgb::MerkleLink link;
uint256 won = H("a0");
auto gentx_bytes = noseg_bytes(make_gentx());

auto closure = make_reconstruct_closure_from_template(
won_fields(won, sh, link),
[gentx_bytes](const uint256&) { return gentx_bytes; },
[](const uint256&) { return std::vector<MutableTransaction>{}; });

EXPECT_FALSE(closure(H("ff")).has_value()); // not the won share
}

// --- Test 10: malformed gentx bytes => nullopt (unpack out_of_range) ----------
TEST(DgbReconstructClosure, FromTemplateMalformedGentxFailsClosed)
{
auto sh = make_small_header();
::dgb::MerkleLink link;
uint256 won = H("a0");
auto bad = noseg_bytes(make_gentx());
bad.push_back(0xff); // trailing byte => unpack throws

auto closure = make_reconstruct_closure_from_template(
won_fields(won, sh, link),
[bad](const uint256&) { return bad; },
[](const uint256&) { return std::vector<MutableTransaction>{}; });

EXPECT_FALSE(closure(won).has_value());
}

// --- Test 11: template source throws (non-out_of_range) => still nullopt ------
TEST(DgbReconstructClosure, FromTemplateOtherTxsThrowFailsClosed)
{
auto sh = make_small_header();
::dgb::MerkleLink link;
uint256 won = H("a0");
auto gentx_bytes = noseg_bytes(make_gentx());

auto closure = make_reconstruct_closure_from_template(
won_fields(won, sh, link),
[gentx_bytes](const uint256&) { return gentx_bytes; },
[](const uint256&) -> std::vector<MutableTransaction> {
throw std::runtime_error("template boom"); });

EXPECT_FALSE(closure(won).has_value()); // broad catch, not just out_of_range
}
Loading