|
| 1 | +// SPDX-License-Identifier: AGPL-3.0-or-later |
| 2 | +#pragma once |
| 3 | +// --------------------------------------------------------------------------- |
| 4 | +// btc::coin::assemble_won_block -- the share->block "as_block" reassembly that |
| 5 | +// the BTC won-block reconstructor (#744 dispatch arc, slice 4/7) feeds to the |
| 6 | +// dual-path broadcaster (P2P relay + submitblock RPC fallback). |
| 7 | +// |
| 8 | +// This is the BTC port of the FRAMING half of p2pool data.py |
| 9 | +// Share.as_block(tracker, known_txs): |
| 10 | +// |
| 11 | +// gentx = self.check(tracker, known_txs) # the coinbase tx |
| 12 | +// other_txs = [known_txs[h] for h in transaction_hashes] |
| 13 | +// return dict(header=self.header, txs=[gentx]+other_txs) |
| 14 | +// |
| 15 | +// It mirrors the landed DGB slice (src/impl/dgb/coin/block_assembly.hpp) but is |
| 16 | +// deliberately NOT DGB-identical in one consensus-relevant respect -- see |
| 17 | +// "BTC divergence" below. |
| 18 | +// |
| 19 | +// Two consensus-relevant facts this captures (shared with DGB): |
| 20 | +// 1. p2pool stores only a SmallBlockHeaderType on the share (version|prev| |
| 21 | +// time|bits|nonce -- NO merkle_root). The full block header's merkle_root |
| 22 | +// is RECONSTRUCTED as check_merkle_link(gentx_hash, share merkle link), |
| 23 | +// exactly as verification recomputed the share's PoW root at check() time |
| 24 | +// (share_check.hpp:674-692 / :2500). We MUST reproduce it the same way, or |
| 25 | +// the block hashes to something other than the share's PoW-valid header and |
| 26 | +// the daemon rejects it. |
| 27 | +// 2. Block tx order is [gentx] ++ other_txs, gentx (coinbase) always index 0, |
| 28 | +// other_txs in the captured template's transactions[] order (the set the |
| 29 | +// share committed to, replayed by template_capture.hpp -- slice 3). |
| 30 | +// |
| 31 | +// ---- BTC divergence from the DGB slice (the reviewer-load-bearing part) ---- |
| 32 | +// BTC commits the BIP141 witness root INSIDE the coinbase at gentx-BUILD time |
| 33 | +// (share_check.hpp:2166 / :2296 -- generate_share_transaction). So slices 1-3 |
| 34 | +// hand this framer an ALREADY witness-committed gentx. We therefore do NOT |
| 35 | +// re-inject a witness commitment here. Re-injecting DGB-style |
| 36 | +// add_witness_commitment would MUTATE the coinbase, change its txid, invalidate |
| 37 | +// the share-committed header merkle_root, and open a SECOND merkle path -- which |
| 38 | +// the sealed BTC merkle family (#570 / #574 / #579) forbids. The DGB lane adds |
| 39 | +// its commitment downstream only because it does not commit at gentx-time; BTC |
| 40 | +// does, so the correct BTC action is: reuse the committed gentx verbatim. Do NOT |
| 41 | +// "fix" this back to the DGB add_witness_commitment shape. |
| 42 | +// |
| 43 | +// The segwit-vs-legacy merkle-LINK SELECTION (segwit shares use |
| 44 | +// segwit_data.txid_merkle_link, legacy shares use merkle_link -- share_check.hpp |
| 45 | +// :674-692) stays in the CALLER (the reconstruct closure, slice 5): this framer |
| 46 | +// takes an already-resolved MerkleLink, exactly like the DGB slice, so the |
| 47 | +// sealed link math is reused with NO new merkle path introduced here. |
| 48 | +// |
| 49 | +// Body/header self-check (item 3): because BTC hands us the full template tx |
| 50 | +// set, we can -- and do -- verify the assembled body against the share-committed |
| 51 | +// header BEFORE emitting bytes. body_merkle_root() folds [gentx_hash] ++ the |
| 52 | +// non-witness txids of other_txs through the sealed btc::coin::compute_merkle_root |
| 53 | +// SSOT (template_builder.hpp); for a body that matches the merkle link the share |
| 54 | +// committed to, this equals reconstruct_block_header()'s check_merkle_link root |
| 55 | +// by the definition of a coinbase (leaf-0) merkle branch. assemble_won_block |
| 56 | +// FAILS CLOSED (throws) on a mismatch rather than emit a bad-txnmrklroot block, |
| 57 | +// consistent with the whole slice arc's "never broadcast a malformed block" |
| 58 | +// posture (cf. gentx_unpack.hpp). This is a self-check over the EXISTING sealed |
| 59 | +// compute_merkle_root, NOT a new merkle path or a second commitment. |
| 60 | +// |
| 61 | +// Interaction with template_capture.hpp (slice 3): on a capture MISS slice 3 |
| 62 | +// replays an EMPTY transactions[] -> other_txs = {}. If the won share mined a |
| 63 | +// coinbase-only template (empty merkle link), body_root == header_root == gentx |
| 64 | +// txid: the guard passes and the coinbase-only block broadcasts, carrying the |
| 65 | +// full subsidy exactly as template_capture documents. If instead the share |
| 66 | +// committed to fee txs (non-empty link), a coinbase-only body is a |
| 67 | +// bad-txnmrklroot block the daemon would reject anyway; the guard throws so the |
| 68 | +// reconstruct closure fails closed (announce + audit) instead of broadcasting a |
| 69 | +// doomed block. Nothing broadcastable is lost -- the guard only tightens the |
| 70 | +// non-empty-link miss case, which was never a valid block. |
| 71 | +// |
| 72 | +// Wire encoding is BlockType::Serialize -> TX_WITH_WITNESS(m_txs): the standard |
| 73 | +// Bitcoin-Core CONDITIONAL serializer -- it emits the per-tx witness marker/flag |
| 74 | +// iff some tx HasWitness(), legacy otherwise. The block's witness shape is thus |
| 75 | +// governed by whether the (already-committed) gentx carries a witness, NOT an |
| 76 | +// unconditional witness branch here. This is the identical path |
| 77 | +// NodeRPC::submit_block uses (rpc.cpp: pack<BlockType>(block) -> submitblock), so |
| 78 | +// the reconstructed block round-trips and the daemon accepts. |
| 79 | +// |
| 80 | +// Per-coin isolation: src/impl/btc/ only. p2pool-merged-v36 surface: NONE -- |
| 81 | +// block framing reuses BlockType + check_merkle_link + compute_merkle_root |
| 82 | +// verbatim; no share format, PoW hash, coinbase commitment, or PPLNS math is |
| 83 | +// touched. |
| 84 | +// --------------------------------------------------------------------------- |
| 85 | + |
| 86 | +#include <stdexcept> |
| 87 | +#include <string> |
| 88 | +#include <utility> |
| 89 | +#include <vector> |
| 90 | + |
| 91 | +#include <core/hash.hpp> |
| 92 | +#include <core/pack.hpp> |
| 93 | +#include <core/uint256.hpp> |
| 94 | +#include <btclibs/util/strencodings.h> // HexStr |
| 95 | + |
| 96 | +#include "block.hpp" // SmallBlockHeaderType, BlockHeaderType, BlockType |
| 97 | +#include "transaction.hpp" // MutableTransaction, TX_NO_WITNESS |
| 98 | +#include "template_builder.hpp" // btc::coin::compute_merkle_root (sealed body-root SSOT) |
| 99 | +#include "../share_check.hpp" // btc::check_merkle_link (SSOT merkle-branch walk) |
| 100 | +#include "../share_types.hpp" // btc::MerkleLink |
| 101 | + |
| 102 | +namespace btc |
| 103 | +{ |
| 104 | +namespace coin |
| 105 | +{ |
| 106 | + |
| 107 | +// Reconstruct the full block header from the share's stored SmallBlockHeader |
| 108 | +// plus the gentx txid + (already segwit-resolved) merkle link. Mirrors the |
| 109 | +// merkle_root recomputation in share_check.hpp verification (SmallBlockHeader |
| 110 | +// omits merkle_root); the caller passes txid_merkle_link for segwit shares and |
| 111 | +// merkle_link for legacy ones (slice 5), so no link selection happens here. |
| 112 | +inline BlockHeaderType |
| 113 | +reconstruct_block_header(const SmallBlockHeaderType& small_header, |
| 114 | + const uint256& gentx_hash, |
| 115 | + const ::btc::MerkleLink& merkle_link) |
| 116 | +{ |
| 117 | + BlockHeaderType header; |
| 118 | + header.m_version = small_header.m_version; |
| 119 | + header.m_previous_block = small_header.m_previous_block; |
| 120 | + header.m_timestamp = small_header.m_timestamp; |
| 121 | + header.m_bits = small_header.m_bits; |
| 122 | + header.m_nonce = small_header.m_nonce; |
| 123 | + // SmallBlockHeader has no merkle_root -- recompute it from the coinbase |
| 124 | + // (gentx) txid walked up the share's merkle branch, exactly as the share's |
| 125 | + // PoW hash was computed at verification time. |
| 126 | + header.m_merkle_root = ::btc::check_merkle_link(gentx_hash, merkle_link); |
| 127 | + return header; |
| 128 | +} |
| 129 | + |
| 130 | +// The merkle leaf (txid) of an other-tx: double-SHA256 of its NON-WITNESS |
| 131 | +// serialization, identical to the gentx_hash derivation in gentx_unpack.hpp. |
| 132 | +// The BTC merkle tree (header root, and the segwit txid_merkle_link) commits to |
| 133 | +// TXIDs, never wtxids, so witness data is excluded here regardless of the tx's |
| 134 | +// witness shape. |
| 135 | +inline uint256 other_tx_txid(const MutableTransaction& tx) |
| 136 | +{ |
| 137 | + return Hash(pack(TX_NO_WITNESS(tx)).get_span()); |
| 138 | +} |
| 139 | + |
| 140 | +// The body's merkle root: compute_merkle_root over [gentx_hash] ++ other-tx |
| 141 | +// txids, using the sealed btc::coin::compute_merkle_root SSOT (#570/#574/#579). |
| 142 | +// For a body that matches the merkle link the share committed to, this equals |
| 143 | +// reconstruct_block_header()'s check_merkle_link root. |
| 144 | +inline uint256 |
| 145 | +body_merkle_root(const uint256& gentx_hash, |
| 146 | + const std::vector<MutableTransaction>& other_txs) |
| 147 | +{ |
| 148 | + std::vector<uint256> txids; |
| 149 | + txids.reserve(1 + other_txs.size()); |
| 150 | + txids.push_back(gentx_hash); // coinbase is leaf 0 |
| 151 | + for (const auto& tx : other_txs) |
| 152 | + txids.push_back(other_tx_txid(tx)); |
| 153 | + return compute_merkle_root(std::move(txids)); |
| 154 | +} |
| 155 | + |
| 156 | +// Assemble the full serialized parent block for a won share. |
| 157 | +// small_header : share.m_min_header (version|prev|time|bits|nonce) |
| 158 | +// gentx : the reconstructed, ALREADY witness-committed coinbase (tx 0) |
| 159 | +// gentx_hash : its txid (double-SHA256 of the non-witness bytes) |
| 160 | +// merkle_link : the share's segwit-resolved gentx->root branch (caller-chosen) |
| 161 | +// other_txs : the captured template's non-coinbase txs, in template order |
| 162 | +// Returns {block_bytes, block_hex}: block_bytes is the blob the embedded P2P |
| 163 | +// relay sends; block_hex is the same block for the external submitblock fallback |
| 164 | +// (block_hex == HexStr(block_bytes)). Throws std::runtime_error if the assembled |
| 165 | +// body's merkle root does not match the share-committed header root (fail-closed |
| 166 | +// against a bad-txnmrklroot block -- see the header comment's template_capture |
| 167 | +// interaction note). |
| 168 | +inline std::pair<std::vector<unsigned char>, std::string> |
| 169 | +assemble_won_block(const SmallBlockHeaderType& small_header, |
| 170 | + const MutableTransaction& gentx, |
| 171 | + const uint256& gentx_hash, |
| 172 | + const ::btc::MerkleLink& merkle_link, |
| 173 | + const std::vector<MutableTransaction>& other_txs) |
| 174 | +{ |
| 175 | + BlockType block; |
| 176 | + static_cast<BlockHeaderType&>(block) = |
| 177 | + reconstruct_block_header(small_header, gentx_hash, merkle_link); |
| 178 | + |
| 179 | + // Fail-closed body/header consistency guard: the share-committed header root |
| 180 | + // (the merkle_link walk) MUST equal the root the assembled body actually |
| 181 | + // hashes to, or the daemon rejects the block as bad-txnmrklroot. Reuses the |
| 182 | + // sealed compute_merkle_root SSOT -- NOT a new merkle path or a re-inject. |
| 183 | + const uint256 body_root = body_merkle_root(gentx_hash, other_txs); |
| 184 | + if (body_root != block.m_merkle_root) |
| 185 | + throw std::runtime_error( |
| 186 | + "assemble_won_block: assembled body merkle root " + body_root.GetHex() + |
| 187 | + " != share-committed header root " + block.m_merkle_root.GetHex() + |
| 188 | + " -- refusing to broadcast a bad-txnmrklroot block"); |
| 189 | + |
| 190 | + // txs = [gentx] ++ other_txs (coinbase first; data.py as_block ordering). |
| 191 | + block.m_txs.reserve(1 + other_txs.size()); |
| 192 | + block.m_txs.push_back(gentx); |
| 193 | + for (const auto& tx : other_txs) |
| 194 | + block.m_txs.push_back(tx); |
| 195 | + |
| 196 | + PackStream packed = pack<BlockType>(block); |
| 197 | + auto sp = packed.get_span(); |
| 198 | + std::vector<unsigned char> bytes( |
| 199 | + reinterpret_cast<const unsigned char*>(sp.data()), |
| 200 | + reinterpret_cast<const unsigned char*>(sp.data()) + sp.size()); |
| 201 | + std::string hex = HexStr(sp); |
| 202 | + return {std::move(bytes), std::move(hex)}; |
| 203 | +} |
| 204 | + |
| 205 | +} // namespace coin |
| 206 | +} // namespace btc |
0 commit comments