Skip to content

Commit 4c9d8d3

Browse files
authored
Merge pull request #173 from frstrtr/dgb/won-block-gentx-expose
dgb(#82): expose SSOT gentx bytes for won-block reconstruction
2 parents 345e88a + 7b1f57a commit 4c9d8d3

4 files changed

Lines changed: 377 additions & 124 deletions

File tree

.github/workflows/build.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ jobs:
6767
test_phase4_embedded \
6868
test_mweb_builder \
6969
test_address_resolution test_compute_share_target \
70-
test_utxo test_dgb_subsidy dgb_share_test dgb_block_assembly_test dgb_gentx_coinbase_test \
71-
nmc_auxpow_merkle_test \
70+
test_utxo test_dgb_subsidy dgb_share_test dgb_block_assembly_test \
71+
dgb_gentx_coinbase_test nmc_auxpow_merkle_test dgb_gentx_share_path_test \
7272
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
7373
v37_test \
7474
-j$(nproc)
@@ -198,8 +198,8 @@ jobs:
198198
test_phase4_embedded \
199199
test_mweb_builder \
200200
test_address_resolution test_compute_share_target \
201-
test_utxo test_dgb_subsidy dgb_share_test dgb_block_assembly_test dgb_gentx_coinbase_test \
202-
nmc_auxpow_merkle_test \
201+
test_utxo test_dgb_subsidy dgb_share_test dgb_block_assembly_test \
202+
dgb_gentx_coinbase_test nmc_auxpow_merkle_test dgb_gentx_share_path_test \
203203
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
204204
test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
205205
v37_test \

src/impl/dgb/share_check.hpp

Lines changed: 51 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#include "share_messages.hpp"
99
#include "share_types.hpp"
1010

11+
#include <impl/dgb/coin/gentx_coinbase.hpp>
12+
1113
#include <core/coin_params.hpp>
1214
#include <core/hash.hpp>
1315
#include <core/pack.hpp>
@@ -931,7 +933,7 @@ inline std::vector<unsigned char> get_share_script(const auto* obj)
931933
// Reference: frstrtr/p2pool-merged-v36 p2pool/data.py generate_transaction()
932934
// ============================================================================
933935
template <typename ShareT, typename TrackerT>
934-
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)
935937
{
936938
auto gst_t0 = std::chrono::steady_clock::now();
937939
constexpr int64_t ver = ShareT::version;
@@ -1133,81 +1135,22 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const
11331135
payout_outputs.erase(payout_outputs.begin(), payout_outputs.end() - MAX_OUTPUTS);
11341136

11351137
// --- 4. Serialise the coinbase transaction ---
1136-
// Non-witness serialization (for txid computation):
1137-
// version(4) + vin_count(varint) + vin + vout_count(varint) + vouts + locktime(4)
1138-
PackStream tx;
1139-
1140-
// tx version = 1
1141-
uint32_t tx_version = 1;
1142-
tx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&tx_version), 4));
1143-
1144-
// vin count = 1
1145-
{
1146-
unsigned char one = 1;
1147-
tx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&one), 1));
1148-
}
1149-
1150-
// vin[0]: prev_output = 0...0:ffffffff, script = coinbase, sequence = 0
1151-
{
1152-
// prev_hash (32 zero bytes)
1153-
uint256 zero_hash;
1154-
tx << zero_hash;
1155-
// prev_index (0xffffffff)
1156-
uint32_t prev_idx = 0xffffffff;
1157-
tx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&prev_idx), 4));
1158-
// script (VarStr)
1159-
tx << share.m_coinbase;
1160-
// sequence (0xffffffff — standard coinbase sequence, matches Python)
1161-
uint32_t seq = 0xffffffff;
1162-
tx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&seq), 4));
1163-
}
1164-
1165-
// Count total outputs
1166-
size_t n_outs = payout_outputs.size() + 1 /* donation */ + 1 /* OP_RETURN commitment */;
1167-
// Segwit commitment output (if applicable)
1168-
bool has_segwit = false;
1138+
// Wire layout (version|vin|vouts|locktime) and txid are produced by the
1139+
// SSOT assembler dgb::coin::assemble_gentx_coinbase() so that emission and
1140+
// verification can never diverge on a byte. This function only builds the
1141+
// per-share inputs (coinbase script, optional segwit commitment, payout
1142+
// outputs, donation, OP_RETURN ref commitment).
1143+
1144+
// Segwit witness-commitment output (value=0) — present iff this share
1145+
// carries segwit data on a segwit-active version. The script is
1146+
// 0x6a24aa21a9ed + SHA256d(wtxid_merkle_root || P2POOL_WITNESS_NONCE).
1147+
std::optional<std::vector<unsigned char>> segwit_commitment_script;
11691148
if constexpr (ver >= dgb::SEGWIT_ACTIVATION_VERSION)
11701149
{
11711150
if constexpr (requires { share.m_segwit_data; })
11721151
{
11731152
if (share.m_segwit_data.has_value())
11741153
{
1175-
has_segwit = true;
1176-
n_outs += 1;
1177-
}
1178-
}
1179-
}
1180-
1181-
// vout count (varint — for < 253 outputs, it's a single byte)
1182-
if (n_outs < 253)
1183-
{
1184-
uint8_t cnt = static_cast<uint8_t>(n_outs);
1185-
tx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&cnt), 1));
1186-
}
1187-
else
1188-
{
1189-
uint8_t marker = 0xfd;
1190-
tx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&marker), 1));
1191-
uint16_t cnt = static_cast<uint16_t>(n_outs);
1192-
tx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&cnt), 2));
1193-
}
1194-
1195-
// Helper to write a single tx_out: value(8LE) + script(VarStr)
1196-
auto write_txout = [&](uint64_t value, const std::vector<unsigned char>& script) {
1197-
tx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&value), 8));
1198-
BaseScript bs;
1199-
bs.m_data = script;
1200-
tx << bs;
1201-
};
1202-
1203-
// Segwit commitment output (value=0, script=OP_RETURN + witness_commitment)
1204-
if (has_segwit)
1205-
{
1206-
if constexpr (requires { share.m_segwit_data; })
1207-
{
1208-
if (share.m_segwit_data.has_value())
1209-
{
1210-
// witness commitment: 0x6a24aa21a9ed + SHA256d(wtxid_merkle_root || '[P2Pool]'*4)
12111154
std::vector<unsigned char> wscript;
12121155
wscript.push_back(0x6a); // OP_RETURN
12131156
wscript.push_back(0x24); // PUSH 36
@@ -1219,7 +1162,7 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const
12191162
uint256 commitment = compute_p2pool_witness_commitment(sd.m_wtxid_merkle_root);
12201163
auto commitment_bytes = commitment.GetChars();
12211164
wscript.insert(wscript.end(), commitment_bytes.begin(), commitment_bytes.end());
1222-
write_txout(0, wscript);
1165+
segwit_commitment_script = std::move(wscript);
12231166
if (dump_diag) {
12241167
LOG_INFO << "[WC-GST] wtxid_root=" << sd.m_wtxid_merkle_root.GetHex()
12251168
<< " commitment=" << commitment.GetHex();
@@ -1228,17 +1171,13 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const
12281171
}
12291172
}
12301173

1231-
// PPLNS payout outputs
1232-
for (auto& [script, amount] : payout_outputs)
1233-
write_txout(amount, script);
1234-
12351174
// Donation output — V35 shares always use P2PK DONATION_SCRIPT,
12361175
// V36 shares always use P2SH COMBINED_DONATION_SCRIPT.
12371176
// Each share was created with the donation script matching its version.
12381177
auto donation_script = params.donation_script_func(ver);
1239-
write_txout(donation_amount, donation_script);
12401178

12411179
// OP_RETURN commitment: value=0, script = 0x6a28 + ref_hash(32) + last_txout_nonce(8)
1180+
std::vector<unsigned char> op_return_script;
12421181
{
12431182
// We need the ref_hash — recompute it from the share the same way share_init_verify does
12441183
PackStream ref_stream;
@@ -1350,7 +1289,6 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const
13501289
uint256 ref_hash = check_merkle_link(hash_ref, share.m_ref_merkle_link);
13511290

13521291
// Build OP_RETURN script: 0x6a 0x28 + ref_hash(32) + last_txout_nonce(8)
1353-
std::vector<unsigned char> op_return_script;
13541292
op_return_script.push_back(0x6a); // OP_RETURN
13551293
op_return_script.push_back(0x28); // PUSH 40 bytes
13561294
op_return_script.insert(op_return_script.end(), ref_hash.data(), ref_hash.data() + 32);
@@ -1359,19 +1297,23 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const
13591297
auto* p = reinterpret_cast<const unsigned char*>(&nonce);
13601298
op_return_script.insert(op_return_script.end(), p, p + 8);
13611299
}
1362-
write_txout(0, op_return_script);
13631300
}
13641301

1365-
// lock_time = 0
1366-
{
1367-
uint32_t locktime = 0;
1368-
tx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&locktime), 4));
1369-
}
1302+
// --- 5. Assemble + compute txid via the SSOT (double-SHA256 of
1303+
// non-witness serialization). Reconstruct the PackStream tx so the
1304+
// downstream V36 hash_link diagnostics keep working unchanged.
1305+
auto _gc = dgb::coin::assemble_gentx_coinbase(
1306+
share.m_coinbase.m_data, segwit_commitment_script, payout_outputs,
1307+
donation_amount, donation_script, op_return_script);
1308+
PackStream tx;
1309+
tx.write(std::span<const std::byte>(
1310+
reinterpret_cast<const std::byte*>(_gc.bytes.data()), _gc.bytes.size()));
1311+
auto txid = _gc.txid;
13701312

1371-
// --- 5. Compute txid (double-SHA256 of non-witness serialization) ---
1372-
auto tx_span = std::span<const unsigned char>(
1373-
reinterpret_cast<const unsigned char*>(tx.data()), tx.size());
1374-
auto txid = Hash(tx_span);
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;
13751317

13761318
// V36 hash_link cross-check: compute prefix hash_link from our coinbase
13771319
// and compare with the share's stored hash_link. If states differ,
@@ -1452,8 +1394,8 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const
14521394
LOG_WARNING << "[GENTX-DIAG] coinbase_hex=" << to_hex(cp, tx.size());
14531395
LOG_WARNING << "[GENTX-DIAG] pplns_outputs=" << payout_outputs.size()
14541396
<< " donation_amount=" << donation_amount
1455-
<< " n_outs=" << n_outs
1456-
<< " has_segwit=" << has_segwit;
1397+
<< " n_outs=" << (payout_outputs.size() + 2 + (segwit_commitment_script.has_value() ? 1u : 0u))
1398+
<< " has_segwit=" << segwit_commitment_script.has_value();
14571399
LOG_WARNING << "[GENTX-DIAG] total_weight=" << total_weight.GetHex()
14581400
<< " total_don_weight=" << total_donation_weight.GetHex();
14591401
for (size_t i = 0; i < payout_outputs.size(); ++i) {
@@ -2729,7 +2671,8 @@ uint256 create_local_share(
27292671
share.m_last_txout_nonce = static_cast<uint64_t>(std::time(nullptr)) ^
27302672
(static_cast<uint64_t>(min_header.m_nonce) << 32);
27312673

2732-
// --- Build the p2pool coinbase in the same format as generate_share_transaction ---
2674+
// --- Build the p2pool coinbase via the SSOT assemble_gentx_coinbase() ---
2675+
// (identical wire layout to generate_share_transaction; one byte path, not a twin)
27332676
// This is needed to compute hash_link and to verify the share locally.
27342677
// The coinbase format is: version(4) + vin(1 input) + vout(outputs...) + locktime(4)
27352678
//
@@ -2942,41 +2885,29 @@ uint256 create_local_share(
29422885
if (payout_outputs.size() > 4000)
29432886
payout_outputs.erase(payout_outputs.begin(), payout_outputs.end() - 4000);
29442887

2945-
PackStream gentx;
2946-
{ uint32_t v = 1; gentx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&v), 4)); }
2947-
{ unsigned char one = 1; gentx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&one), 1)); }
2948-
{ uint256 z; gentx << z; }
2949-
{ uint32_t idx = 0xffffffff; gentx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&idx), 4)); }
2950-
gentx << share.m_coinbase;
2951-
{ uint32_t seq = 0xffffffff; gentx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&seq), 4)); }
2952-
size_t n_outs = payout_outputs.size() + 1 + 1;
2953-
bool has_segwit_fb = share.m_segwit_data.has_value();
2954-
if (has_segwit_fb) n_outs += 1;
2955-
if (n_outs < 253) { uint8_t cnt = (uint8_t)n_outs; gentx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&cnt), 1)); }
2956-
else { uint8_t m = 0xfd; gentx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&m), 1)); uint16_t cnt = (uint16_t)n_outs; gentx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&cnt), 2)); }
2957-
auto write_txout = [&](uint64_t value, const std::vector<unsigned char>& script) {
2958-
gentx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&value), 8));
2959-
BaseScript bs; bs.m_data = script; gentx << bs;
2960-
};
2961-
if (has_segwit_fb) {
2888+
// Build the p2pool coinbase via the SSOT assembler
2889+
// dgb::coin::assemble_gentx_coinbase() — identical wire layout to
2890+
// generate_share_transaction(). This is no longer a hand-rolled twin:
2891+
// both emission and verification route through the one byte path.
2892+
std::optional<std::vector<unsigned char>> segwit_opt;
2893+
if (share.m_segwit_data.has_value()) {
29622894
auto& sd = share.m_segwit_data.value();
29632895
std::vector<unsigned char> wscript = {0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed};
29642896
uint256 commitment = compute_p2pool_witness_commitment(sd.m_wtxid_merkle_root);
29652897
auto cb = commitment.GetChars();
29662898
wscript.insert(wscript.end(), cb.begin(), cb.end());
2967-
write_txout(0, wscript);
2968-
}
2969-
for (auto& [script, amount] : payout_outputs) write_txout(amount, script);
2970-
write_txout(donation_amount, params.donation_script_func(int64_t(36)));
2971-
{ std::vector<unsigned char> op; op.push_back(0x6a); op.push_back(0x28);
2972-
op.insert(op.end(), ref_hash.data(), ref_hash.data() + 32);
2973-
uint64_t n = share.m_last_txout_nonce; auto* p = reinterpret_cast<const unsigned char*>(&n);
2974-
op.insert(op.end(), p, p + 8); write_txout(0, op); }
2975-
{ uint32_t lt = 0; gentx.write(std::span<const std::byte>(reinterpret_cast<const std::byte*>(&lt), 4)); }
2976-
2977-
coinbase_bytes_for_hashlink.assign(
2978-
reinterpret_cast<const unsigned char*>(gentx.data()),
2979-
reinterpret_cast<const unsigned char*>(gentx.data()) + gentx.size());
2899+
segwit_opt = std::move(wscript);
2900+
}
2901+
auto donation_script = params.donation_script_func(int64_t(36));
2902+
std::vector<unsigned char> op_return_vec;
2903+
op_return_vec.push_back(0x6a); op_return_vec.push_back(0x28);
2904+
op_return_vec.insert(op_return_vec.end(), ref_hash.data(), ref_hash.data() + 32);
2905+
{ uint64_t n = share.m_last_txout_nonce; auto* p = reinterpret_cast<const unsigned char*>(&n);
2906+
op_return_vec.insert(op_return_vec.end(), p, p + 8); }
2907+
auto _gc = dgb::coin::assemble_gentx_coinbase(
2908+
share.m_coinbase.m_data, segwit_opt, payout_outputs,
2909+
donation_amount, donation_script, op_return_vec);
2910+
coinbase_bytes_for_hashlink = _gc.bytes;
29802911
}
29812912

29822913
// The split point: everything before ref_hash + last_txout_nonce + locktime

src/impl/dgb/test/CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,20 @@ if (BUILD_TESTING AND GTest_FOUND)
5454
dgb_coin pool sharechain)
5555
gtest_add_tests(dgb_gentx_coinbase_test "" AUTO)
5656

57+
# --- #172 share-path gentx KAT (collapse consumption proof) ------------
58+
# Proves generate_share_transaction (the verification SSOT call site #172
59+
# rewired) EMITS assemble_gentx_coinbase framing for a real v36 share, via
60+
# a round-trip equivalence (the op_return ref_hash is share-derived, so it
61+
# cannot be pinned to a fixed oracle vector). Links the dgb OBJECT lib like
62+
# dgb_share_test. MUST also be in the build.yml --target allowlist (#143).
63+
add_executable(dgb_gentx_share_path_test gentx_share_path_test.cpp)
64+
target_link_libraries(dgb_gentx_share_path_test PRIVATE
65+
GTest::gtest_main GTest::gtest
66+
core dgb
67+
c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage
68+
dgb_coin pool sharechain)
69+
gtest_add_tests(dgb_gentx_share_path_test "" AUTO)
70+
5771
foreach(dgb_guard rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test)
5872
add_executable(${dgb_guard} ${dgb_guard}.cpp)
5973
target_link_libraries(${dgb_guard} PRIVATE

0 commit comments

Comments
 (0)