Skip to content

Commit 7b1f57a

Browse files
committed
dgb(#82): expose SSOT gentx bytes for won-block reconstruction
generate_share_transaction computes the coinbase via assemble_gentx_coinbase (the SSOT proven by #172) but returned only the gentx_hash, discarding the bytes. reconstruct_won_block needs the exact non-witness coinbase bytes -- it is block tx[0] and its txid is the merkle leaf -- not just the hash. Add an optional out_gentx (GentxCoinbase*) param, default nullptr: zero change for the verification callers, surfaces {bytes,txid} for the reconstructor. KAT proves the exposed bytes hash to the returned gentx_hash and deserialize into the coinbase MutableTransaction that assemble_won_block frames, round-tripping byte-identically (non-witness). Single-coin (src/impl/dgb/ only); reuses the existing #172 dgb_gentx_share_path_test target (no new allowlist entry).
1 parent cadec97 commit 7b1f57a

2 files changed

Lines changed: 50 additions & 1 deletion

File tree

src/impl/dgb/share_check.hpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -933,7 +933,7 @@ inline std::vector<unsigned char> get_share_script(const auto* obj)
933933
// Reference: frstrtr/p2pool-merged-v36 p2pool/data.py generate_transaction()
934934
// ============================================================================
935935
template <typename ShareT, typename TrackerT>
936-
uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const core::CoinParams& params, bool dump_diag = false, bool v36_active = false)
936+
uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const core::CoinParams& params, bool dump_diag = false, bool v36_active = false, dgb::coin::GentxCoinbase* out_gentx = nullptr)
937937
{
938938
auto gst_t0 = std::chrono::steady_clock::now();
939939
constexpr int64_t ver = ShareT::version;
@@ -1310,6 +1310,11 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const
13101310
reinterpret_cast<const std::byte*>(_gc.bytes.data()), _gc.bytes.size()));
13111311
auto txid = _gc.txid;
13121312

1313+
// Expose the SSOT gentx (non-witness bytes + txid) for the won-block
1314+
// reconstructor (#82): block tx[0] is this coinbase and gentx_hash is
1315+
// its merkle leaf. Default nullptr => zero change for verification callers.
1316+
if (out_gentx) *out_gentx = _gc;
1317+
13131318
// V36 hash_link cross-check: compute prefix hash_link from our coinbase
13141319
// and compare with the share's stored hash_link. If states differ,
13151320
// the prefix (outputs/amounts) differs from what p2pool built.

src/impl/dgb/test/gentx_share_path_test.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include <impl/dgb/share_check.hpp>
3232
#include <impl/dgb/params.hpp>
3333
#include <impl/dgb/coin/gentx_coinbase.hpp>
34+
#include <impl/dgb/coin/transaction.hpp>
3435

3536
#include <core/pack.hpp>
3637
#include <core/hash.hpp>
@@ -261,4 +262,47 @@ TEST(DgbGentxSharePath, CoinbaseInputsAreLoadBearing)
261262
EXPECT_NE(h0, h2) << "coinbase script did not reach the gentx vin";
262263
}
263264

265+
// --- Test 4: SSOT gentx bytes are exposed for won-block reconstruction (#82) ---
266+
// reconstruct_won_block needs the EXACT coinbase bytes whose txid is the merkle
267+
// leaf -- not just the hash. The out_gentx param surfaces them straight from the
268+
// SSOT assembler; they must (a) hash to the returned gentx_hash and (b)
269+
// deserialize into the coinbase MutableTransaction that assemble_won_block frames
270+
// as block tx[0], round-tripping byte-identically.
271+
TEST(DgbGentxSharePath, SsotGentxBytesExposedForReconstruct)
272+
{
273+
auto params = dgb::make_coin_params(false);
274+
dgb::ShareTracker tracker;
275+
auto share = make_v36_share();
276+
277+
dgb::coin::GentxCoinbase gc;
278+
uint256 gentx_hash = dgb::generate_share_transaction(
279+
share, tracker, params, /*dump_diag=*/false, /*v36_active=*/true, &gc);
280+
281+
ASSERT_FALSE(gc.bytes.empty()) << "out_gentx not populated";
282+
EXPECT_EQ(gc.txid, gentx_hash)
283+
<< "exposed gentx bytes do not match the returned gentx_hash";
284+
auto sp = std::span<const unsigned char>(gc.bytes.data(), gc.bytes.size());
285+
EXPECT_EQ(Hash(sp), gentx_hash)
286+
<< "double-SHA(exposed bytes) != gentx_hash (merkle leaf would mismatch)";
287+
288+
// Deserialize into the coinbase tx that assemble_won_block consumes.
289+
dgb::coin::MutableTransaction gentx;
290+
{
291+
PackStream ps(gc.bytes);
292+
dgb::coin::UnserializeTransaction(gentx, ps, dgb::coin::TX_NO_WITNESS);
293+
}
294+
// Coinbase shape: single input spending the null outpoint at 0xffffffff.
295+
ASSERT_EQ(gentx.vin.size(), 1u);
296+
EXPECT_TRUE(gentx.vin[0].prevout.hash.IsNull());
297+
EXPECT_EQ(gentx.vin[0].prevout.index, 0xffffffffu);
298+
299+
// Re-serialize non-witness => byte-identical to the SSOT bytes, so the
300+
// reconstructed block tx[0] is faithful and the daemon-side block hashes.
301+
auto repacked = pack(dgb::coin::TX_NO_WITNESS(gentx));
302+
std::vector<unsigned char> rebytes(
303+
reinterpret_cast<const unsigned char*>(repacked.data()),
304+
reinterpret_cast<const unsigned char*>(repacked.data()) + repacked.size());
305+
EXPECT_EQ(rebytes, gc.bytes) << "coinbase tx does not round-trip the SSOT bytes";
306+
}
307+
264308
} // namespace

0 commit comments

Comments
 (0)