Skip to content

Commit 23683bd

Browse files
committed
dgb(#82): faithful won-block reconstruct closure + fail-closed KAT
make_reconstruct_closure (coin/reconstruct_closure.hpp) is the run-loop WonBlockReconstructor main_dgb.cpp binds in place of the interim nullopt stub: it composes the landed slices into p2pool Share.as_block -- share-field + gentx-regen seams -> unpack_gentx_coinbase -> reconstruct_ won_block (resolve refs -> assemble other_txs -> frame block). All sharechain/mempool access is INJECTED so the closure is unit-testable with no live tracker. Reward-path fail-closed: the callback fires on the compute thread and a broad std::exception catch yields std::nullopt (never throws, never a partial block) on ANY missing share, missing known-tx, out-of-range ref-walk, gentx-regen failure, or malformed gentx -- announce + audit, RPC submitblock fallback still attempts. dgb_reconstruct_closure_test 6/6 (success == reconstruct_won_block on same inputs; 5 distinct error paths each -> nullopt + loud log). Wired into test CMake + both build.yml allowlists. src/impl/dgb/ only; p2pool-merged-v36 surface: none.
1 parent b8a992e commit 23683bd

4 files changed

Lines changed: 420 additions & 2 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ jobs:
6969
test_address_resolution test_compute_share_target \
7070
test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_block_assembly_test dgb_header_sample_build_test dgb_header_ingest_test dgb_mempool_ingest_test \
7171
dgb_gentx_coinbase_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test dgb_gentx_share_path_test dgb_other_tx_resolver_test \
72-
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test \
72+
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test \
7373
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
7474
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test \
7575
v37_test \
@@ -202,7 +202,7 @@ jobs:
202202
test_address_resolution test_compute_share_target \
203203
test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_block_assembly_test dgb_header_sample_build_test dgb_header_ingest_test dgb_mempool_ingest_test \
204204
dgb_gentx_coinbase_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test dgb_gentx_share_path_test dgb_other_tx_resolver_test \
205-
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test \
205+
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test \
206206
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
207207
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test \
208208
test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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

src/impl/dgb/test/CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,20 @@ if (BUILD_TESTING AND GTest_FOUND)
9494
dgb_coin pool sharechain)
9595
gtest_add_tests(dgb_reconstruct_won_block_test "" AUTO)
9696

97+
# --- #82: won-block reconstruct CLOSURE (run-loop WonBlockReconstructor) -
98+
# Pins coin/reconstruct_closure.hpp: make_reconstruct_closure composes the
99+
# share-field + gentx-regen seams + reconstruct_won_block under a fail-closed
100+
# catch (the closure main_dgb.cpp binds in place of the nullopt stub). Links
101+
# the dgb OBJECT lib like dgb_reconstruct_won_block_test (pulls block_assembly
102+
# via the closure). MUST also appear in the build.yml --target allowlist.
103+
add_executable(dgb_reconstruct_closure_test reconstruct_closure_test.cpp)
104+
target_link_libraries(dgb_reconstruct_closure_test PRIVATE
105+
GTest::gtest_main GTest::gtest
106+
core dgb
107+
c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage
108+
dgb_coin pool sharechain)
109+
gtest_add_tests(dgb_reconstruct_closure_test "" AUTO)
110+
97111
# --- M3 RPC-transport standalone regression guards ---------------------
98112
# Header-only guards over the external-daemon RPC coin-layer SSOTs
99113
# (rpc_request.hpp request-shape + version floor + genesis identity, and

0 commit comments

Comments
 (0)