Skip to content

Commit da0c8dd

Browse files
committed
Refactor merged mining: extract shared coinbase/merkle helpers
Extract four shared static helpers from build_multiaddress_block: - build_pplns_coinbase_hex(): canonical coinbase TX construction - compute_tx_merkle_root(): merkle root from tx hash list - compute_merkle_link(): merkle proof branches for leaf→root - collect_tx_hashes(): coinbase_hash + template txids Both build_multiaddress_block (block submission) and build_merged_header_info (ref_hash frozen fields) now use these shared helpers, eliminating code duplication and ensuring the DOGE header in merged_coinbase_info matches what gets submitted.
1 parent 3f72f76 commit da0c8dd

2 files changed

Lines changed: 166 additions & 128 deletions

File tree

src/c2pool/merged/merged_mining.cpp

Lines changed: 146 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -867,98 +867,19 @@ MergedMiningManager::build_merged_header_info() const
867867
std::string bits_hex = tmpl.value("bits", std::string("1d00ffff"));
868868

869869
try {
870-
// Build full block hex (reuses existing tested code)
871870
uint256 state_root;
872871
if (m_state_root_provider) state_root = m_state_root_provider();
873-
// Need a dummy auxpow — but we don't have the parent data yet.
874-
// The header doesn't include auxpow — it's just the first 80 bytes.
875-
// We can build coinbase + compute merkle root + assemble header separately.
876-
877-
// Build coinbase TX hex (same logic as build_multiaddress_block)
878-
std::string coinbase_hex;
879-
{
880-
std::ostringstream coinb;
881-
coinb << "01000000" << "01"
882-
<< "0000000000000000000000000000000000000000000000000000000000000000"
883-
<< "ffffffff";
884-
// scriptSig: BIP34 height + tag
885-
std::ostringstream sig2;
886-
{ uint32_t h2 = info.block_height;
887-
// BIP34 height push: 03 + LE bytes (matches encode_height_pushdata)
888-
uint8_t hb[4]; for (int i=0;i<4;++i) hb[i]=(h2>>(8*i))&0xff;
889-
if (h2 < 0x100) { sig2 << "02" << to_hex(hb, 1) << "00"; }
890-
else if (h2 < 0x10000) { sig2 << "03" << to_hex(hb, 2) << "00"; }
891-
else { sig2 << "04" << to_hex(hb, 4); }
892-
}
893-
const std::string ctag = "/c2pool/";
894-
for (char ch : ctag) { uint8_t b2 = uint8_t(ch); sig2 << to_hex(&b2, 1); }
895-
if (!state_root.IsNull()) sig2 << to_hex(state_root.data(), 32);
896-
std::string sig_hex2 = sig2.str();
897-
coinb << varint_hex(sig_hex2.size()/2) << sig_hex2 << "ffffffff";
898-
// Outputs
899-
static const std::vector<unsigned char> CDON = {
900-
0xa9,0x14,0x8c,0x62,0x72,0x62,0x1d,0x89,0xe8,0xfa,
901-
0x52,0x6d,0xd8,0x6a,0xcf,0xf6,0x0c,0x71,0x36,0xbe,0x8e,0x85,0x87};
902-
std::vector<std::pair<std::vector<unsigned char>,uint64_t>> mouts;
903-
uint64_t don = 0;
904-
for (auto& [s,a] : payouts) { if (s == CDON) don += a; else if (a > 0) mouts.emplace_back(s,a); }
905-
if (don < 1 && !mouts.empty()) { mouts.back().second -= 1; don += 1; }
906-
std::string optext = "c2pool merged mining";
907-
coinb << varint_hex(mouts.size() + 2);
908-
for (auto& [s,a] : mouts) {
909-
uint8_t v[8]; for (int i=0;i<8;++i) v[i]=(a>>(8*i))&0xFF;
910-
coinb << to_hex(v,8) << varint_hex(s.size()) << to_hex(s.data(), s.size());
911-
}
912-
{ uint8_t z[8]={}; coinb << to_hex(z,8); // OP_RETURN value=0
913-
std::vector<uint8_t> ops; ops.push_back(0x6a); ops.push_back(uint8_t(optext.size()));
914-
ops.insert(ops.end(), optext.begin(), optext.end());
915-
coinb << varint_hex(ops.size()) << to_hex(ops.data(), ops.size()); }
916-
{ uint8_t v[8]; for (int i=0;i<8;++i) v[i]=(don>>(8*i))&0xFF;
917-
coinb << to_hex(v,8) << varint_hex(CDON.size()) << to_hex(CDON.data(), CDON.size()); }
918-
coinb << "00000000"; // locktime
919-
coinbase_hex = coinb.str();
920-
}
872+
873+
// Build coinbase, compute merkle root + link using shared helpers
874+
std::string coinbase_hex = build_pplns_coinbase_hex(
875+
info.block_height, payouts, state_root);
921876
if (coinbase_hex.empty()) continue;
922877

923878
auto coinbase_bytes = from_hex(coinbase_hex);
924879
uint256 cb_hash = Hash(coinbase_bytes);
925-
926-
// Tx hashes
927-
std::vector<uint256> tx_hashes;
928-
tx_hashes.push_back(cb_hash);
929-
if (tmpl.contains("transactions") && tmpl["transactions"].is_array()) {
930-
for (const auto& tx : tmpl["transactions"]) {
931-
std::string txid_str = tx.value("txid", "");
932-
if (!txid_str.empty()) { uint256 h; h.SetHex(txid_str); tx_hashes.push_back(h); }
933-
else { auto raw = from_hex(tx.value("data", "")); tx_hashes.push_back(Hash(raw)); }
934-
}
935-
}
936-
937-
// Merkle root
938-
auto layer = tx_hashes;
939-
while (layer.size() > 1) {
940-
if (layer.size() % 2) layer.push_back(layer.back());
941-
std::vector<uint256> next;
942-
for (size_t i = 0; i < layer.size(); i += 2)
943-
next.push_back(Hash(layer[i], layer[i+1]));
944-
layer = std::move(next);
945-
}
946-
uint256 merkle_root = layer.empty() ? cb_hash : layer[0];
947-
948-
// Coinbase merkle link (branches for proof: coinbase → root)
949-
{
950-
auto all = tx_hashes;
951-
size_t idx = 0; // coinbase is at index 0
952-
while (all.size() > 1) {
953-
if (all.size() % 2) all.push_back(all.back());
954-
info.coinbase_merkle_branches.push_back(all[idx ^ 1]);
955-
std::vector<uint256> next;
956-
for (size_t i = 0; i < all.size(); i += 2)
957-
next.push_back(Hash(all[i], all[i+1]));
958-
all = std::move(next);
959-
idx >>= 1;
960-
}
961-
}
880+
auto tx_hashes = collect_tx_hashes(cb_hash, tmpl);
881+
uint256 merkle_root = compute_tx_merkle_root(tx_hashes);
882+
info.coinbase_merkle_branches = compute_merkle_link(tx_hashes, 0);
962883

963884
// 80-byte header
964885
uint256 prev_hash;
@@ -1013,6 +934,131 @@ void MergedMiningManager::set_block_relay_fn(BlockRelayFn fn)
1013934
// The coinbase transaction is built fresh with the supplied payout outputs.
1014935
// Template transactions are included verbatim from getblocktemplate.
1015936

937+
// ─── Shared helpers ──────────────────────────────────────────────────────────
938+
939+
/*static*/ std::string MergedMiningManager::build_pplns_coinbase_hex(
940+
int height,
941+
const std::vector<std::pair<std::vector<unsigned char>, uint64_t>>& payouts,
942+
const uint256& the_state_root)
943+
{
944+
if (payouts.empty()) return {};
945+
946+
static const std::vector<unsigned char> COMBINED_DONATION = {
947+
0xa9, 0x14, 0x8c, 0x62, 0x72, 0x62, 0x1d, 0x89, 0xe8, 0xfa,
948+
0x52, 0x6d, 0xd8, 0x6a, 0xcf, 0xf6, 0x0c, 0x71, 0x36, 0xbe,
949+
0x8e, 0x85, 0x87
950+
};
951+
952+
std::ostringstream cb;
953+
cb << "01000000" << "01"
954+
<< "0000000000000000000000000000000000000000000000000000000000000000"
955+
<< "ffffffff";
956+
957+
// scriptSig: BIP34 height + "/c2pool/" + THE state root
958+
std::ostringstream sig;
959+
if (height > 0 && height <= 16) {
960+
uint8_t op = static_cast<uint8_t>(0x50 + height);
961+
sig << to_hex(&op, 1);
962+
} else {
963+
std::vector<uint8_t> hbytes;
964+
uint32_t h = static_cast<uint32_t>(height);
965+
while (h > 0) { hbytes.push_back(h & 0xff); h >>= 8; }
966+
if (!hbytes.empty() && (hbytes.back() & 0x80)) hbytes.push_back(0);
967+
uint8_t push_len = static_cast<uint8_t>(hbytes.size());
968+
sig << to_hex(&push_len, 1) << to_hex(hbytes.data(), hbytes.size());
969+
}
970+
const std::string ctag = "/c2pool/";
971+
for (char c : ctag) { uint8_t b = static_cast<uint8_t>(c); sig << to_hex(&b, 1); }
972+
if (!the_state_root.IsNull()) sig << to_hex(the_state_root.data(), 32);
973+
std::string sig_hex = sig.str();
974+
cb << varint_hex(sig_hex.size() / 2) << sig_hex << "ffffffff";
975+
976+
// Separate donation from miner outputs
977+
std::vector<std::pair<std::vector<unsigned char>, uint64_t>> miner_outs;
978+
uint64_t donation_amount = 0;
979+
for (const auto& [script, amount] : payouts) {
980+
if (script == COMBINED_DONATION) donation_amount += amount;
981+
else if (amount > 0) miner_outs.emplace_back(script, amount);
982+
}
983+
if (donation_amount < 1 && !miner_outs.empty()) {
984+
miner_outs.back().second -= 1;
985+
donation_amount += 1;
986+
}
987+
988+
const std::string op_return_text = "c2pool merged mining";
989+
std::vector<unsigned char> op_return_script;
990+
op_return_script.push_back(0x6a);
991+
op_return_script.push_back(static_cast<unsigned char>(op_return_text.size()));
992+
op_return_script.insert(op_return_script.end(), op_return_text.begin(), op_return_text.end());
993+
994+
cb << varint_hex(miner_outs.size() + 2);
995+
for (const auto& [script, amount] : miner_outs) {
996+
uint8_t vbuf[8]; for (int i = 0; i < 8; ++i) vbuf[i] = (amount >> (8*i)) & 0xFF;
997+
cb << to_hex(vbuf, 8) << varint_hex(script.size()) << to_hex(script.data(), script.size());
998+
}
999+
{ uint8_t z[8] = {}; cb << to_hex(z, 8)
1000+
<< varint_hex(op_return_script.size()) << to_hex(op_return_script.data(), op_return_script.size()); }
1001+
{ uint8_t vbuf[8]; for (int i = 0; i < 8; ++i) vbuf[i] = (donation_amount >> (8*i)) & 0xFF;
1002+
cb << to_hex(vbuf, 8) << varint_hex(COMBINED_DONATION.size())
1003+
<< to_hex(COMBINED_DONATION.data(), COMBINED_DONATION.size()); }
1004+
cb << "00000000";
1005+
return cb.str();
1006+
}
1007+
1008+
/*static*/ uint256 MergedMiningManager::compute_tx_merkle_root(
1009+
const std::vector<uint256>& tx_hashes)
1010+
{
1011+
if (tx_hashes.empty()) return uint256();
1012+
auto layer = tx_hashes;
1013+
while (layer.size() > 1) {
1014+
if (layer.size() % 2) layer.push_back(layer.back());
1015+
std::vector<uint256> next;
1016+
for (size_t i = 0; i < layer.size(); i += 2)
1017+
next.push_back(Hash(layer[i], layer[i+1]));
1018+
layer = std::move(next);
1019+
}
1020+
return layer[0];
1021+
}
1022+
1023+
/*static*/ std::vector<uint256> MergedMiningManager::compute_merkle_link(
1024+
const std::vector<uint256>& tx_hashes, size_t leaf_index)
1025+
{
1026+
std::vector<uint256> branches;
1027+
auto all = tx_hashes;
1028+
size_t idx = leaf_index;
1029+
while (all.size() > 1) {
1030+
if (all.size() % 2) all.push_back(all.back());
1031+
branches.push_back(all[idx ^ 1]);
1032+
std::vector<uint256> next;
1033+
for (size_t i = 0; i < all.size(); i += 2)
1034+
next.push_back(Hash(all[i], all[i+1]));
1035+
all = std::move(next);
1036+
idx >>= 1;
1037+
}
1038+
return branches;
1039+
}
1040+
1041+
/*static*/ std::vector<uint256> MergedMiningManager::collect_tx_hashes(
1042+
const uint256& coinbase_hash, const nlohmann::json& tmpl)
1043+
{
1044+
std::vector<uint256> tx_hashes;
1045+
tx_hashes.push_back(coinbase_hash);
1046+
if (tmpl.contains("transactions") && tmpl["transactions"].is_array()) {
1047+
for (const auto& tx : tmpl["transactions"]) {
1048+
std::string txid_str = tx.value("txid", "");
1049+
if (!txid_str.empty()) {
1050+
uint256 h; h.SetHex(txid_str); tx_hashes.push_back(h);
1051+
} else {
1052+
auto raw = from_hex(tx.value("data", ""));
1053+
tx_hashes.push_back(Hash(raw));
1054+
}
1055+
}
1056+
}
1057+
return tx_hashes;
1058+
}
1059+
1060+
// ─── build_multiaddress_block (refactored to use shared helpers) ─────────────
1061+
10161062
std::string MergedMiningManager::build_multiaddress_block(
10171063
const nlohmann::json& tmpl,
10181064
const std::vector<std::pair<std::vector<unsigned char>, uint64_t>>& payouts,
@@ -1022,19 +1068,20 @@ std::string MergedMiningManager::build_multiaddress_block(
10221068
if (payouts.empty() || !tmpl.contains("previousblockhash"))
10231069
return {};
10241070

1025-
// --- Parse template fields ---
1026-
// Set AuxPoW flag (bit 8) so the aux daemon recognizes this as a
1027-
// merge-mined block and parses the AuxPoW proof after the header.
1028-
// Reference: p2pool work.py: version=template['version'] | (1 << 8)
10291071
uint32_t version = tmpl.value("version", 0x20000002u) | 0x100;
10301072
std::string prev_hash_hex = tmpl.value("previousblockhash", "");
10311073
uint32_t curtime = tmpl.value("curtime", 0u);
10321074
std::string bits_hex = tmpl.value("bits", "1d00ffff");
10331075
int height = tmpl.value("height", 0);
10341076

1035-
// --- Build coinbase TX ---
1036-
std::ostringstream cb;
1077+
// --- Build coinbase TX using shared helper ---
1078+
std::string coinbase_hex = build_pplns_coinbase_hex(height, payouts, the_state_root);
1079+
if (coinbase_hex.empty()) return {};
10371080

1081+
// Skip old inline coinbase — now built by build_pplns_coinbase_hex above.
1082+
// Jump directly to merkle root + block assembly.
1083+
if (false) { // ---- BEGIN DEAD CODE (replaced by shared helper) ----
1084+
std::ostringstream cb;
10381085
// tx version (1, LE)
10391086
cb << "01000000";
10401087

@@ -1147,49 +1194,20 @@ std::string MergedMiningManager::build_multiaddress_block(
11471194
// locktime
11481195
cb << "00000000";
11491196

1150-
std::string coinbase_hex = cb.str();
1197+
std::string coinbase_hex_DEAD = cb.str();
1198+
} // ---- END DEAD CODE ----
11511199

1152-
// --- Coinbase txid (double-SHA256 of serialized coinbase) ---
1200+
// --- Coinbase txid + tx hashes + merkle root (shared helpers) ---
11531201
auto coinbase_bytes = from_hex(coinbase_hex);
11541202
uint256 cb_hash = Hash(coinbase_bytes);
1203+
auto tx_hashes = collect_tx_hashes(cb_hash, tmpl);
1204+
uint256 merkle_root = compute_tx_merkle_root(tx_hashes);
11551205

1156-
// --- Collect template transaction hashes ---
1157-
std::vector<uint256> tx_hashes;
1158-
tx_hashes.push_back(cb_hash);
1159-
1206+
// Collect raw tx data for block assembly
11601207
std::vector<std::string> tx_datas;
11611208
if (tmpl.contains("transactions") && tmpl["transactions"].is_array())
1162-
{
11631209
for (const auto& tx : tmpl["transactions"])
1164-
{
1165-
std::string txdata = tx.value("data", "");
1166-
tx_datas.push_back(txdata);
1167-
1168-
std::string txid_str = tx.value("txid", "");
1169-
if (!txid_str.empty()) {
1170-
uint256 txid;
1171-
txid.SetHex(txid_str);
1172-
tx_hashes.push_back(txid);
1173-
} else {
1174-
// Compute txid from data
1175-
auto raw = from_hex(txdata);
1176-
tx_hashes.push_back(Hash(raw));
1177-
}
1178-
}
1179-
}
1180-
1181-
// --- Merkle root ---
1182-
auto merkle_layer = tx_hashes;
1183-
while (merkle_layer.size() > 1)
1184-
{
1185-
if (merkle_layer.size() % 2 != 0)
1186-
merkle_layer.push_back(merkle_layer.back());
1187-
std::vector<uint256> next;
1188-
for (size_t i = 0; i < merkle_layer.size(); i += 2)
1189-
next.push_back(Hash(merkle_layer[i], merkle_layer[i + 1]));
1190-
merkle_layer = std::move(next);
1191-
}
1192-
uint256 merkle_root = merkle_layer.empty() ? cb_hash : merkle_layer[0];
1210+
tx_datas.push_back(tx.value("data", ""));
11931211

11941212
// --- Block header (80 bytes) ---
11951213
std::ostringstream hdr;

src/c2pool/merged/merged_mining.hpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,26 @@ class MergedMiningManager
280280
void refresh_aux_work();
281281

282282
public:
283+
// ─── Shared helpers (used by both build_multiaddress_block and build_merged_header_info) ───
284+
285+
// Build canonical merged coinbase TX hex from PPLNS payouts.
286+
// Matches p2pool's build_canonical_merged_coinbase output ordering.
287+
static std::string build_pplns_coinbase_hex(
288+
int height,
289+
const std::vector<std::pair<std::vector<unsigned char>, uint64_t>>& payouts,
290+
const uint256& the_state_root);
291+
292+
// Compute merkle root from a list of tx hashes (first = coinbase).
293+
static uint256 compute_tx_merkle_root(const std::vector<uint256>& tx_hashes);
294+
295+
// Compute merkle link (proof branches) from leaf at index to root.
296+
static std::vector<uint256> compute_merkle_link(
297+
const std::vector<uint256>& tx_hashes, size_t leaf_index);
298+
299+
// Collect tx hashes from block template JSON + coinbase hash.
300+
static std::vector<uint256> collect_tx_hashes(
301+
const uint256& coinbase_hash, const nlohmann::json& tmpl);
302+
283303
// Build a complete aux block in multiaddress mode from getblocktemplate
284304
// result, PPLNS payout outputs, AuxPoW proof hex, and THE state root
285305
// for sharechain anchoring (embedded in coinbase scriptSig).

0 commit comments

Comments
 (0)