|
| 1 | +#pragma once |
| 2 | +// --------------------------------------------------------------------------- |
| 3 | +// dgb::coin::make_reconstruct_closure -- the run-loop's faithful |
| 4 | +// WonBlockReconstructor (#82): the closure main_dgb.cpp binds into |
| 5 | +// make_on_block_found, replacing the interim `return nullopt` stub. It is the |
| 6 | +// open half of DGB's #82 broadcaster dual-path -- until this is non-stub a |
| 7 | +// pool-won DGB block is announced + audited but never reconstructed, so neither |
| 8 | +// the P2P-relay arm (#260) nor the submitblock RPC fallback ever fires on a |
| 9 | +// real won share. |
| 10 | +// |
| 11 | +// It composes the already-landed, individually-KAT'd slices into one faithful |
| 12 | +// port of p2pool data.py Share.as_block(tracker, known_txs): |
| 13 | +// gentx = unpack_gentx_coinbase(generate_share_transaction(share).bytes) |
| 14 | +// other_txs = [known_txs[h] for h in get_other_tx_hashes(tracker)] |
| 15 | +// block = reconstruct_won_block(header, link, gentx, ..., refs, ...) |
| 16 | +// |
| 17 | +// SEAM-FIRST (mirrors every #82 sub-slice): the share lookup, the gentx |
| 18 | +// regeneration, and the three tracker/mempool walks are INJECTED as callables, |
| 19 | +// so the whole closure -- including its fail-closed posture -- is unit-testable |
| 20 | +// against a synthetic share set with NO live ShareTracker / mempool. In the |
| 21 | +// run-loop they bind to: |
| 22 | +// share_fields_fn = chain.get_share(h) -> {m_min_header, m_merkle_link, |
| 23 | +// m_tx_info.m_transaction_hash_refs} |
| 24 | +// gentx_bytes_fn = generate_share_transaction(share, tracker, params, |
| 25 | +// false, v36_active=false, &gc) -> gc.bytes (#173 SSOT) |
| 26 | +// nth_parent_fn = chain.get_nth_parent_via_skip(h, n) |
| 27 | +// new_tx_hashes_fn = chain.get_share(h)->m_tx_info.m_new_transaction_hashes |
| 28 | +// known_txs_fn = mempool/known-tx find_tx(hash) -> *MutableTransaction |
| 29 | +// |
| 30 | +// FAIL-CLOSED (integrator 2026-06-19/20, REWARD-PATH CRITICAL): this callback |
| 31 | +// fires from the compute thread on a won share. It NEVER throws out of the |
| 32 | +// callback and NEVER emits a partial/wrong block: ANY missing share, missing |
| 33 | +// known-tx, out-of-range ref-walk, or gentx-regen failure is caught, logged |
| 34 | +// LOUDLY, and yields std::nullopt -- the share is announced + audited, the RPC |
| 35 | +// submitblock fallback still attempts independently, and no malformed block |
| 36 | +// reaches the network. A wrong reconstruction would hash to the wrong merkle |
| 37 | +// root and be daemon-rejected anyway, so failing closed here is strictly safer |
| 38 | +// than emitting it. |
| 39 | +// |
| 40 | +// Per-coin isolation: src/impl/dgb/ only. p2pool-merged-v36 surface: NONE -- |
| 41 | +// pure composition of already-validated share_info + already-relayed txs + the |
| 42 | +// proven BlockType serializer. DGB-Scrypt is a STANDALONE parent in the V36 |
| 43 | +// default build (no merged-coinbase leg). |
| 44 | +// --------------------------------------------------------------------------- |
| 45 | + |
| 46 | +#include <exception> |
| 47 | +#include <functional> |
| 48 | +#include <iostream> |
| 49 | +#include <optional> |
| 50 | +#include <string> |
| 51 | +#include <utility> |
| 52 | +#include <vector> |
| 53 | + |
| 54 | +#include <core/uint256.hpp> |
| 55 | + |
| 56 | +#include "reconstruct_won_block.hpp" // reconstruct_won_block, ReconstructedWonBlock, SmallBlockHeaderType, MutableTransaction |
| 57 | +#include "gentx_unpack.hpp" // unpack_gentx_coinbase, UnpackedGentx |
| 58 | +#include "won_block_dispatch.hpp" // WonBlockReconstructor |
| 59 | + |
| 60 | +namespace dgb |
| 61 | +{ |
| 62 | +namespace coin |
| 63 | +{ |
| 64 | + |
| 65 | +// The per-share inputs the closure pulls from the sharechain to drive |
| 66 | +// reconstruct_won_block: everything that is NOT the gentx or the ancestry walk. |
| 67 | +// small_header : share.m_min_header (version|prev|time|bits|nonce) |
| 68 | +// merkle_link : share.m_merkle_link (gentx -> merkle root branch) |
| 69 | +// refs : share.m_tx_info.m_transaction_hash_refs, in order |
| 70 | +struct ShareReconstructFields |
| 71 | +{ |
| 72 | + SmallBlockHeaderType small_header; |
| 73 | + ::dgb::MerkleLink merkle_link; |
| 74 | + std::vector<TxHashRefs> refs; |
| 75 | +}; |
| 76 | + |
| 77 | +// Build the run-loop WonBlockReconstructor. See header note for the run-loop |
| 78 | +// bindings of each injected seam. The returned callable is fail-closed: it |
| 79 | +// returns std::nullopt (never throws, never a partial block) on ANY error. |
| 80 | +inline WonBlockReconstructor |
| 81 | +make_reconstruct_closure( |
| 82 | + std::function<ShareReconstructFields(const uint256&)> share_fields_fn, |
| 83 | + std::function<std::vector<unsigned char>(const uint256&)> gentx_bytes_fn, |
| 84 | + std::function<uint256(const uint256&, uint64_t)> nth_parent_fn, |
| 85 | + std::function<const std::vector<uint256>&(const uint256&)> new_tx_hashes_fn, |
| 86 | + std::function<const MutableTransaction*(const uint256&)> known_txs_fn) |
| 87 | +{ |
| 88 | + return [share_fields_fn = std::move(share_fields_fn), |
| 89 | + gentx_bytes_fn = std::move(gentx_bytes_fn), |
| 90 | + nth_parent_fn = std::move(nth_parent_fn), |
| 91 | + new_tx_hashes_fn = std::move(new_tx_hashes_fn), |
| 92 | + known_txs_fn = std::move(known_txs_fn)](const uint256& share_hash) |
| 93 | + -> std::optional<std::pair<std::vector<unsigned char>, std::string>> |
| 94 | + { |
| 95 | + try |
| 96 | + { |
| 97 | + // 1. share-level fields (header / merkle_link / tx refs). |
| 98 | + const ShareReconstructFields f = share_fields_fn(share_hash); |
| 99 | + |
| 100 | + // 2. regenerate the share's SSOT gentx bytes, then unpack to the |
| 101 | + // MutableTransaction (+ canonical txid) reconstruct injects at |
| 102 | + // block tx index 0. unpack throws on a malformed serialization. |
| 103 | + const UnpackedGentx ug = unpack_gentx_coinbase(gentx_bytes_fn(share_hash)); |
| 104 | + |
| 105 | + // 3. compose: resolve refs -> assemble other_txs -> frame the block. |
| 106 | + ReconstructedWonBlock r = reconstruct_won_block( |
| 107 | + f.small_header, f.merkle_link, ug.tx, ug.txid, share_hash, f.refs, |
| 108 | + nth_parent_fn, new_tx_hashes_fn, known_txs_fn); |
| 109 | + |
| 110 | + return std::make_pair(std::move(r.bytes), std::move(r.hex)); |
| 111 | + } |
| 112 | + catch (const std::exception& e) |
| 113 | + { |
| 114 | + // Fail closed: announce + audit, never broadcast a partial/wrong |
| 115 | + // block. The RPC submitblock fallback still attempts independently. |
| 116 | + std::cout << "[DGB-POOL-BLOCK] won share " << share_hash.GetHex().substr(0, 16) |
| 117 | + << " -- reconstruct FAILED CLOSED (" << e.what() |
| 118 | + << "); NOT broadcast on P2P arm (RPC fallback still attempts)" |
| 119 | + << std::endl; |
| 120 | + return std::nullopt; |
| 121 | + } |
| 122 | + }; |
| 123 | +} |
| 124 | + |
| 125 | +} // namespace coin |
| 126 | +} // namespace dgb |
0 commit comments