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
43 changes: 43 additions & 0 deletions src/impl/dgb/coin/reconstruct_won_block.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,48 @@ reconstruct_won_block(
return ReconstructedWonBlock{std::move(framed.first), std::move(framed.second)};
}

// ---------------------------------------------------------------------------
// reconstruct_won_block_from_template -- the CORRECT won-block tx source, per
// the p2pool-merged-v36 block-assembly audit (work.py @ 42ccca53):
//
// transactions = self.current_work.value['transactions'] # ~2486
// tx_map = dict(zip(transaction_hashes, transactions)) # ~2489
// other_transactions = [tx_map[h] for h in other_transaction_hashes] # ~2638
// submit txs = [new_hexed_gentx]
// + [tx['data'] for tx in other_transactions] # ~2728
//
// The broadcast block's non-coinbase tx set is the GBT TEMPLATE snapshot the
// miner was handed at job hand-out time (current_work), NOT the share's
// transaction_hash_refs. The ref-walk above (reconstruct_won_block) is a
// share-CHAIN peer-propagation mechanism; it is never the block-broadcast
// source. That is why this path is version-AGNOSTIC: pre-v34 vs v34+/v36 makes
// no difference, because the share never carried the block tx set in the first
// place (this dissolves the "v34+ carries no m_tx_info -> reconstruct fails"
// concern -- there is nothing to source from the share for any version).
//
// template_other_txs : the captured template's non-coinbase txs, in template
// order (current_work transactions[]). Empty today --
// the embedded path emits transactions[]==[] until
// mempool tx-selection wires -- which yields a valid
// coinbase-only block (correct-and-empty, NOT
// fail-closed). It fills automatically as tx-selection
// lands, with no change to this seam.
//
// Frames [gentx] ++ template_other_txs via the same assemble_won_block SSOT
// (identical merkle-root math + BlockType codec the live submitblock path
// uses), so the result round-trips and the daemon accepts.
inline ReconstructedWonBlock
reconstruct_won_block_from_template(
const SmallBlockHeaderType& small_header,
const ::dgb::MerkleLink& merkle_link,
const MutableTransaction& gentx,
const uint256& gentx_hash,
const std::vector<MutableTransaction>& template_other_txs)
{
auto framed = assemble_won_block(small_header, gentx, gentx_hash, merkle_link,
template_other_txs);
return ReconstructedWonBlock{std::move(framed.first), std::move(framed.second)};
}

} // namespace coin
} // namespace dgb
60 changes: 60 additions & 0 deletions src/impl/dgb/test/reconstruct_won_block_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "../coin/reconstruct_won_block.hpp"

using dgb::coin::reconstruct_won_block;
using dgb::coin::reconstruct_won_block_from_template;
using dgb::coin::assemble_won_block;
using dgb::coin::resolve_other_tx_hashes;
using dgb::coin::assemble_other_txs;
Expand Down Expand Up @@ -254,4 +255,63 @@ TEST(DgbReconstructWonBlock, UnknownKnownTxThrows)
std::out_of_range);
}

// --- Test 5: template path == ref path for the SAME tx set (equivalence) ------
// Proves reconstruct_won_block_from_template (the CORRECT block-broadcast
// source: captured GBT template txs) frames a byte-identical block to the
// ref-walk path when fed the same non-coinbase tx set -- i.e. the re-scope is a
// pure SOURCE swap, the framing/merkle/codec are unchanged.
TEST(DgbReconstructWonBlock, TemplatePathMatchesRefPathForSameTxSet)
{
Fixture f;
auto sh = make_small_header();
auto gentx = make_gentx();
auto gh = fixed_gentx_hash();
::dgb::MerkleLink link;
std::vector<TxHashRefs> refs = { TxHashRefs(0, 1), TxHashRefs(1, 0) };

// Ref-walk path: resolve hashes -> assemble the other_txs the share refs to.
auto hashes = resolve_other_tx_hashes(f.won, refs, f.nth_parent(), f.new_tx());
auto template_txs = assemble_other_txs(hashes, f.known_fn());

auto ref_got = reconstruct_won_block(sh, link, gentx, gh, f.won, refs,
f.nth_parent(), f.new_tx(), f.known_fn());
auto tmpl_got = reconstruct_won_block_from_template(sh, link, gentx, gh,
template_txs);

EXPECT_EQ(tmpl_got.bytes, ref_got.bytes);
EXPECT_EQ(tmpl_got.hex, ref_got.hex);

PackStream ps(tmpl_got.bytes);
BlockType blk;
ps >> blk;
ASSERT_EQ(blk.m_txs.size(), 3u); // gentx + 2 template txs
EXPECT_EQ(blk.m_txs[0].vin[0].prevout.index, 0xffffffffu);
EXPECT_EQ(blk.m_txs[1].vout[0].value, 11); // template order preserved
EXPECT_EQ(blk.m_txs[2].vout[0].value, 22);
}

// --- Test 6: empty captured template => valid coinbase-only block -------------
// Today's embedded path emits transactions[]==[]; reconstruct must yield a
// valid gentx-only block (correct-and-empty), never throw / fail closed.
TEST(DgbReconstructWonBlock, EmptyTemplateGentxOnly)
{
auto sh = make_small_header();
auto gentx = make_gentx();
auto gh = fixed_gentx_hash();
::dgb::MerkleLink link;
std::vector<MutableTransaction> template_txs; // captured template was empty

auto got = reconstruct_won_block_from_template(sh, link, gentx, gh, template_txs);

PackStream ps(got.bytes);
BlockType blk;
ps >> blk;
ASSERT_EQ(blk.m_txs.size(), 1u);
EXPECT_EQ(blk.m_txs[0].vin[0].prevout.index, 0xffffffffu);
EXPECT_EQ(blk.m_merkle_root, gh); // empty link => root == gentx_hash
auto sp = std::span<const std::byte>(
reinterpret_cast<const std::byte*>(got.bytes.data()), got.bytes.size());
EXPECT_EQ(got.hex, HexStr(sp));
}

} // namespace
Loading