Skip to content

Commit fd1efe3

Browse files
committed
dgb(#82): collapse inline gentx serializers onto SSOT assemble_gentx_coinbase
generate_share_transaction (verification path) and create_local_share (emission path) each open-coded the coinbase wire layout (version|vin|vouts|locktime) and the txid double-SHA256. Two byte paths pinned to the same oracle vector = proven-equivalent, not unified: a third edit could still diverge them. Collapse both onto dgb::coin::assemble_gentx_coinbase() (coin/gentx_coinbase.hpp, the #171 SSOT) so emission == verification == one source. Each call site now only builds its per-share inputs (coinbase script, optional segwit witness-commitment script, PPLNS payout outputs, donation, OP_RETURN ref commitment) and hands them to the assembler; the assembler owns wire framing + txid. PackStream tx is reconstructed from the SSOT bytes so the downstream V36 hash_link diagnostics are byte-for-byte unchanged. No oracle vector regeneration. Single-coin scope: src/impl/dgb/ only. dgb build EXIT=0; dgb_share_test 5/5; dgb_gentx_coinbase_test 3/3.
1 parent fd8f2e3 commit fd1efe3

1 file changed

Lines changed: 46 additions & 120 deletions

File tree

src/impl/dgb/share_check.hpp

Lines changed: 46 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>
@@ -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,18 @@ 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);
1363-
}
1364-
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));
13691300
}
13701301

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);
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;
13751312

13761313
// V36 hash_link cross-check: compute prefix hash_link from our coinbase
13771314
// and compare with the share's stored hash_link. If states differ,
@@ -1452,8 +1389,8 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const
14521389
LOG_WARNING << "[GENTX-DIAG] coinbase_hex=" << to_hex(cp, tx.size());
14531390
LOG_WARNING << "[GENTX-DIAG] pplns_outputs=" << payout_outputs.size()
14541391
<< " donation_amount=" << donation_amount
1455-
<< " n_outs=" << n_outs
1456-
<< " has_segwit=" << has_segwit;
1392+
<< " n_outs=" << (payout_outputs.size() + 2 + (segwit_commitment_script.has_value() ? 1u : 0u))
1393+
<< " has_segwit=" << segwit_commitment_script.has_value();
14571394
LOG_WARNING << "[GENTX-DIAG] total_weight=" << total_weight.GetHex()
14581395
<< " total_don_weight=" << total_donation_weight.GetHex();
14591396
for (size_t i = 0; i < payout_outputs.size(); ++i) {
@@ -2729,7 +2666,8 @@ uint256 create_local_share(
27292666
share.m_last_txout_nonce = static_cast<uint64_t>(std::time(nullptr)) ^
27302667
(static_cast<uint64_t>(min_header.m_nonce) << 32);
27312668

2732-
// --- Build the p2pool coinbase in the same format as generate_share_transaction ---
2669+
// --- Build the p2pool coinbase via the SSOT assemble_gentx_coinbase() ---
2670+
// (identical wire layout to generate_share_transaction; one byte path, not a twin)
27332671
// This is needed to compute hash_link and to verify the share locally.
27342672
// The coinbase format is: version(4) + vin(1 input) + vout(outputs...) + locktime(4)
27352673
//
@@ -2942,41 +2880,29 @@ uint256 create_local_share(
29422880
if (payout_outputs.size() > 4000)
29432881
payout_outputs.erase(payout_outputs.begin(), payout_outputs.end() - 4000);
29442882

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) {
2883+
// Build the p2pool coinbase via the SSOT assembler
2884+
// dgb::coin::assemble_gentx_coinbase() — identical wire layout to
2885+
// generate_share_transaction(). This is no longer a hand-rolled twin:
2886+
// both emission and verification route through the one byte path.
2887+
std::optional<std::vector<unsigned char>> segwit_opt;
2888+
if (share.m_segwit_data.has_value()) {
29622889
auto& sd = share.m_segwit_data.value();
29632890
std::vector<unsigned char> wscript = {0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed};
29642891
uint256 commitment = compute_p2pool_witness_commitment(sd.m_wtxid_merkle_root);
29652892
auto cb = commitment.GetChars();
29662893
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());
2894+
segwit_opt = std::move(wscript);
2895+
}
2896+
auto donation_script = params.donation_script_func(int64_t(36));
2897+
std::vector<unsigned char> op_return_vec;
2898+
op_return_vec.push_back(0x6a); op_return_vec.push_back(0x28);
2899+
op_return_vec.insert(op_return_vec.end(), ref_hash.data(), ref_hash.data() + 32);
2900+
{ uint64_t n = share.m_last_txout_nonce; auto* p = reinterpret_cast<const unsigned char*>(&n);
2901+
op_return_vec.insert(op_return_vec.end(), p, p + 8); }
2902+
auto _gc = dgb::coin::assemble_gentx_coinbase(
2903+
share.m_coinbase.m_data, segwit_opt, payout_outputs,
2904+
donation_amount, donation_script, op_return_vec);
2905+
coinbase_bytes_for_hashlink = _gc.bytes;
29802906
}
29812907

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

0 commit comments

Comments
 (0)