Skip to content

Commit d1ddef1

Browse files
committed
btc(coin): faithful won-block reconstructor closure — reconstruct_won_block (reconstructor slice 5/7)
Compose the landed sub-slices into the run-loop WonBlockReconstructor the mirroring p2pool data.py Share.as_block: * select_won_block_merkle_link — segwit link-selection SSOT (share_check.hpp:674-692): segwit-activated shares with segwit_data walk segwit_data.txid_merkle_link, legacy shares walk merkle_link. The framer (block_assembly.hpp, slice 4) takes an already-resolved MerkleLink, so the selection lives here in the caller — no new merkle path downstream. * reconstruct_won_block — select link + assemble_won_block, taking the captured GBT template (slice 3) as the non-coinbase tx source (NOT the share tx_hash_refs); version-agnostic, coinbase-only on a capture miss. * make_reconstruct_closure — the fail-closed run-loop reconstructor: any unknown share / malformed gentx / body-header merkle mismatch is caught, logged loudly, and yields std::nullopt (never throws out of the compute thread, never broadcasts a partial/wrong block; submitblock RPC fallback still attempts independently). Seam-first: share-field lookup, gentx regen, and the template-tx snapshot are injected callables, so the whole closure is unit-testable with no live ShareTracker / TemplateCapture. main_btc run-loop binding is slice 7. reconstruct_test.cpp: 12 KATs — link selection (segwit/legacy/no-segwit-data), body round-trip through the live BlockType codec, segwit-link-is-walked, mismatch throw, closure fail-closed on malformed gentx + merkle mismatch, and end-to-end wiring into make_on_block_found (both dual-path arms carry the byte-identical block). btc_share_test 94/94 green; diff btc-tree-only. Per-coin isolation: src/impl/btc/ only. p2pool-merged-v36 surface: NONE.
1 parent 7685b39 commit d1ddef1

3 files changed

Lines changed: 646 additions & 1 deletion

File tree

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
#pragma once
3+
// ---------------------------------------------------------------------------
4+
// btc::coin::reconstruct_won_block / make_reconstruct_closure -- the won-block
5+
// reconstructor's (#744 dispatch arc, slice 5/7) BODY + run-loop closure: the
6+
// single composition the dispatch handler (won_block_dispatch.hpp,
7+
// make_on_block_found) injects as its WonBlockReconstructor. It ties the landed
8+
// sub-slices into one C++ implementation of the 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+
// Composition (each step is its own KAT'd SSOT slice):
16+
// 1. select_won_block_merkle_link (this file)
17+
// share_version + merkle_link + optional txid_merkle_link --segwit gate-->
18+
// the merkle link the framer must walk gentx_hash up to reproduce the
19+
// share-committed header root (share_check.hpp:674-692). SEGWIT LINK
20+
// SELECTION LIVES HERE (the caller), not in the framer -- block_assembly.hpp
21+
// takes an already-resolved MerkleLink so the sealed link math is reused
22+
// with NO new merkle path introduced downstream.
23+
// 2. unpack_gentx_coinbase (gentx_unpack.hpp, slice 1 / #822)
24+
// the share's SSOT non-witness gentx bytes --> {MutableTransaction, txid}
25+
// injected at block tx index 0.
26+
// 3. assemble_won_block (block_assembly.hpp, as_block FRAMING / #838)
27+
// small_header + gentx + resolved merkle_link + other_txs --> {bytes, hex}
28+
// (merkle_root recomputed from gentx_hash up the link; txs = [gentx] ++
29+
// other_txs; body/header consistency guard fails closed; BlockType codec
30+
// is the live submitblock path).
31+
//
32+
// ---- BTC won-block tx source: the CAPTURED GBT TEMPLATE, not tx_hash_refs ----
33+
// The broadcast block's non-coinbase tx set is the GBT template snapshot the
34+
// miner was handed at job hand-out time (template_capture.hpp, slice 3), NOT the
35+
// share's transaction_hash_refs. The ref-walk (DGB other_tx_resolver) is a
36+
// share-CHAIN peer-propagation mechanism; it is never the block-broadcast
37+
// source. That is why this path is version-AGNOSTIC: pre-v34 vs v34+/v36 makes
38+
// no difference, because the share never carried the block tx set in the first
39+
// place. The template txs are INJECTED as a callable (template_other_txs_fn), so
40+
// this whole reconstruction is unit-testable against a synthetic template set
41+
// with NO live TemplateCapture / mempool. On a capture MISS slice 3 replays an
42+
// EMPTY transactions[] -> other_txs = {} -> a valid coinbase-only block (the
43+
// full subsidy), which the body/header guard accepts iff the won share committed
44+
// to an empty merkle link (see block_assembly.hpp's template_capture note).
45+
//
46+
// FAIL-CLOSED (integrator 2026-06-19/20, REWARD-PATH CRITICAL): make_on_block_found
47+
// fires this callback from the compute thread on a won share. It NEVER throws out
48+
// of the callback and NEVER emits a partial/wrong block: ANY missing share,
49+
// gentx-unpack failure, or body/header merkle mismatch is caught, logged LOUDLY,
50+
// and yields std::nullopt -- the share is announced + audited, the submitblock
51+
// RPC fallback still attempts independently (block_broadcast.hpp), and no
52+
// malformed block reaches the network. A wrong reconstruction would hash to the
53+
// wrong merkle root and be daemon-rejected anyway, so failing closed here is
54+
// strictly safer than emitting it.
55+
//
56+
// SEAM-FIRST (mirrors every #744 sub-slice): the share-field lookup, the gentx
57+
// regeneration, and the template-tx snapshot are INJECTED as callables, so the
58+
// whole closure -- including its fail-closed posture -- is unit-testable against
59+
// a synthetic share set with NO live ShareTracker / TemplateCapture. In the
60+
// run-loop (main_btc, slice 7) they bind to:
61+
// share_fields_fn = chain.get_share(h) -> { m_min_header, VERSION,
62+
// m_merkle_link, m_segwit_data->m_txid_merkle_link }
63+
// gentx_bytes_fn = generate_share_transaction(share, ...).bytes (SSOT)
64+
// -> unpack_gentx_coinbase -> { tx, txid }
65+
// template_other_txs_fn = TemplateCapture replay for the won share's job
66+
// (slice 3), in template order
67+
//
68+
// Per-coin isolation: src/impl/btc/ only. p2pool-merged-v36 surface: NONE --
69+
// pure composition of already-validated share_info + the captured template + the
70+
// proven BlockType serializer; no share format, PoW, coinbase commitment, or
71+
// PPLNS math is touched.
72+
// ---------------------------------------------------------------------------
73+
74+
#include <exception>
75+
#include <functional>
76+
#include <optional>
77+
#include <string>
78+
#include <utility>
79+
#include <vector>
80+
81+
#include <core/log.hpp>
82+
#include <core/uint256.hpp>
83+
84+
#include "block_assembly.hpp" // assemble_won_block, SmallBlockHeaderType, MutableTransaction
85+
#include "gentx_unpack.hpp" // unpack_gentx_coinbase, UnpackedGentx
86+
#include "won_block_dispatch.hpp" // WonBlockReconstructor
87+
#include "../share_types.hpp" // btc::MerkleLink, btc::is_segwit_activated
88+
89+
namespace btc
90+
{
91+
namespace coin
92+
{
93+
94+
// The reconstructed parent block, ready for broadcast_block_for_connect's dual
95+
// path:
96+
// bytes : the pre-serialized blob the embedded P2P relay sends
97+
// hex : the same block for the external submitblock (RPC) fallback
98+
struct ReconstructedWonBlock
99+
{
100+
std::vector<unsigned char> bytes;
101+
std::string hex;
102+
};
103+
104+
// The per-share inputs the closure resolves before framing -- everything that is
105+
// NOT the gentx or the template tx set.
106+
// small_header : share.m_min_header (version|prev|time|bits|nonce)
107+
// share_version : the share format VERSION (segwit-activation gate input)
108+
// merkle_link : share.m_merkle_link (legacy gentx->root branch)
109+
// txid_merkle_link : share.m_segwit_data->m_txid_merkle_link if the share
110+
// carried segwit_data, else std::nullopt
111+
struct WonShareReconstructFields
112+
{
113+
SmallBlockHeaderType small_header;
114+
uint64_t share_version{0};
115+
::btc::MerkleLink merkle_link;
116+
std::optional<::btc::MerkleLink> txid_merkle_link;
117+
};
118+
119+
// Segwit merkle-link SELECTION SSOT -- mirrors share_check.hpp:674-692 verbatim
120+
// at runtime. A segwit-activated share that carried segwit_data committed its
121+
// header merkle_root over segwit_data.txid_merkle_link; a legacy share (or a
122+
// segwit-active share with no segwit_data sentinel) committed over merkle_link.
123+
// The framer must walk gentx_hash up the SAME link the share committed to, or
124+
// the reconstructed header hashes to a different root than the share's PoW-valid
125+
// header and the daemon rejects the block. Returns a reference into one of the
126+
// two arguments (no copy).
127+
inline const ::btc::MerkleLink&
128+
select_won_block_merkle_link(uint64_t share_version,
129+
const ::btc::MerkleLink& merkle_link,
130+
const std::optional<::btc::MerkleLink>& txid_merkle_link)
131+
{
132+
if (::btc::is_segwit_activated(share_version) && txid_merkle_link.has_value())
133+
return txid_merkle_link.value();
134+
return merkle_link;
135+
}
136+
137+
// Reconstruct the full serialized parent block for a won share.
138+
// small_header : share.m_min_header (version|prev|time|bits|nonce)
139+
// share_version : the share format VERSION (segwit-activation gate)
140+
// merkle_link : share.m_merkle_link (legacy path)
141+
// txid_merkle_link : share.m_segwit_data->m_txid_merkle_link, or nullopt
142+
// gentx : the share's ALREADY witness-committed coinbase (tx 0)
143+
// gentx_hash : its txid (double-SHA256 of the non-witness bytes)
144+
// template_other_txs : the captured template's non-coinbase txs, template order
145+
// Returns {bytes, hex} with hex == HexStr(bytes). Throws (via assemble_won_block)
146+
// if the assembled body's merkle root does not match the share-committed header
147+
// root -- fail-closed against a bad-txnmrklroot block.
148+
inline ReconstructedWonBlock
149+
reconstruct_won_block(const SmallBlockHeaderType& small_header,
150+
uint64_t share_version,
151+
const ::btc::MerkleLink& merkle_link,
152+
const std::optional<::btc::MerkleLink>& txid_merkle_link,
153+
const MutableTransaction& gentx,
154+
const uint256& gentx_hash,
155+
const std::vector<MutableTransaction>& template_other_txs)
156+
{
157+
// 1. segwit-resolve the link the share committed its header root to.
158+
const ::btc::MerkleLink& link =
159+
select_won_block_merkle_link(share_version, merkle_link, txid_merkle_link);
160+
161+
// 2. frame [gentx] ++ template_other_txs via the assemble_won_block SSOT
162+
// (recompute header root up `link`; body/header guard; BlockType codec).
163+
auto framed = assemble_won_block(small_header, gentx, gentx_hash, link,
164+
template_other_txs);
165+
return ReconstructedWonBlock{std::move(framed.first), std::move(framed.second)};
166+
}
167+
168+
// Build the run-loop WonBlockReconstructor the dispatch handler
169+
// (make_on_block_found) installs. See the header note for the run-loop bindings
170+
// of each injected seam. The returned callable is FAIL-CLOSED: it returns
171+
// std::nullopt (never throws, never a partial block) on ANY error -- an unknown
172+
// share, a malformed gentx serialization, or a body/header merkle mismatch.
173+
inline WonBlockReconstructor
174+
make_reconstruct_closure(
175+
std::function<WonShareReconstructFields(const uint256&)> share_fields_fn,
176+
std::function<std::vector<unsigned char>(const uint256&)> gentx_bytes_fn,
177+
std::function<std::vector<MutableTransaction>(const uint256&)> template_other_txs_fn)
178+
{
179+
return [share_fields_fn = std::move(share_fields_fn),
180+
gentx_bytes_fn = std::move(gentx_bytes_fn),
181+
template_other_txs_fn = std::move(template_other_txs_fn)](
182+
const uint256& share_hash)
183+
-> std::optional<std::pair<std::vector<unsigned char>, std::string>>
184+
{
185+
try
186+
{
187+
// 1. share-level fields (header / version / merkle links).
188+
const WonShareReconstructFields f = share_fields_fn(share_hash);
189+
190+
// 2. regenerate + unpack the share's SSOT gentx (block tx index 0);
191+
// unpack throws on a malformed (trailing-byte) serialization.
192+
const UnpackedGentx ug = unpack_gentx_coinbase(gentx_bytes_fn(share_hash));
193+
194+
// 3. the captured-GBT template's non-coinbase set (template order;
195+
// empty on a capture miss => a valid coinbase-only block).
196+
const std::vector<MutableTransaction> other_txs =
197+
template_other_txs_fn(share_hash);
198+
199+
// 4. select segwit link + frame the block (guard fails closed).
200+
ReconstructedWonBlock r = reconstruct_won_block(
201+
f.small_header, f.share_version, f.merkle_link, f.txid_merkle_link,
202+
ug.tx, ug.txid, other_txs);
203+
204+
return std::make_pair(std::move(r.bytes), std::move(r.hex));
205+
}
206+
catch (const std::exception& e)
207+
{
208+
// Fail closed: announce + audit, never broadcast a partial/wrong
209+
// block. The submitblock RPC fallback still attempts independently.
210+
LOG_WARNING << "[EMB-BTC] won share " << share_hash.GetHex().substr(0, 16)
211+
<< " -- reconstruct FAILED CLOSED (" << e.what()
212+
<< "); NOT broadcast on P2P arm (submitblock RPC fallback still attempts).";
213+
return std::nullopt;
214+
}
215+
};
216+
}
217+
218+
} // namespace coin
219+
} // namespace btc

src/impl/btc/test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
if (BUILD_TESTING AND GTest_FOUND)
22
# btc twin of ltc share_test — uniquely named to avoid the CMP0002 target
33
# collision with src/impl/ltc/test (both subdirs build in the same tree).
4-
add_executable(btc_share_test share_test.cpp f11_donation_invariance_test.cpp f10b_version_punish_removal_test.cpp g01_share_format_parity_test.cpp auto_ratchet_sim_test.cpp block_broadcast_guard_test.cpp block_broadcast_connect_test.cpp regtest_sharechain_isolation_test.cpp stratum_merkle_branch_test.cpp witness_commitment_merkle_test.cpp finder_fee_integer_exact_test.cpp rpc_conf_test.cpp genesis_check_test.cpp won_share_dualpath_test.cpp gentx_unpack_test.cpp block_assembly_test.cpp gentx_coinbase_test.cpp template_capture_test.cpp template_other_txs_test.cpp)
4+
add_executable(btc_share_test share_test.cpp f11_donation_invariance_test.cpp f10b_version_punish_removal_test.cpp g01_share_format_parity_test.cpp auto_ratchet_sim_test.cpp block_broadcast_guard_test.cpp block_broadcast_connect_test.cpp regtest_sharechain_isolation_test.cpp stratum_merkle_branch_test.cpp witness_commitment_merkle_test.cpp finder_fee_integer_exact_test.cpp rpc_conf_test.cpp genesis_check_test.cpp won_share_dualpath_test.cpp gentx_unpack_test.cpp block_assembly_test.cpp gentx_coinbase_test.cpp template_capture_test.cpp template_other_txs_test.cpp reconstruct_test.cpp)
55
target_link_libraries(btc_share_test PRIVATE
66
GTest::gtest_main GTest::gtest
77
core btc

0 commit comments

Comments
 (0)