Skip to content

Commit e674c1e

Browse files
authored
Merge pull request #167 from frstrtr/dgb/won-block-dispatch-wiring
dgb(#82): won-block dispatch handler (m_on_block_found wiring) + test
2 parents b8aea72 + b0630dd commit e674c1e

4 files changed

Lines changed: 249 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 dgb_share_test \
7171
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
72-
dgb_coin_node_seam_test dgb_block_broadcast_test \
72+
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test \
7373
v37_test \
7474
-j$(nproc)
7575
@@ -200,7 +200,7 @@ jobs:
200200
test_address_resolution test_compute_share_target \
201201
test_utxo test_dgb_subsidy dgb_share_test \
202202
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
203-
dgb_coin_node_seam_test dgb_block_broadcast_test \
203+
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test \
204204
test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
205205
v37_test \
206206
-j$(nproc)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#pragma once
2+
// ---------------------------------------------------------------------------
3+
// dgb::coin::make_on_block_found -- the won-block DISPATCH handler the run-loop
4+
// installs as dgb::ShareTracker::m_on_block_found.
5+
//
6+
// This is the wiring half between the dispatcher (#166, broadcast_won_block)
7+
// and the live run-loop: on a won block the tracker fires m_on_block_found with
8+
// the winning share hash. This handler turns that share hash into a fully
9+
// serialized parent block (via an injected WonBlockReconstructor -- the
10+
// share->block "as_block" step) and then dispatches it down BOTH broadcast
11+
// paths via broadcast_won_block (P2P primary + external digibyted submitblock
12+
// fallback, always-fire dual-path rule).
13+
//
14+
// The reconstruct step is injected as a std::function rather than called inline
15+
// so this handler is build-verifiable and run-tested NOW, before the faithful
16+
// share->block reassembly (gentx_before_refhash + ref/merkle link + tx lookup,
17+
// mirroring p2pool data.py Share.as_block) is ported. The run-loop binds the
18+
// real reconstructor + the live P2P relay sink + the CoinNode seam when the
19+
// embedded NodeP2P lands; until then a stub reconstructor + the external-RPC
20+
// fallback already carry a won block onto the network.
21+
//
22+
// Contract:
23+
// * reconstruct(share_hash) -> nullopt => UNKNOWN/unassemblable share: log a
24+
// warning and broadcast NOTHING (never fabricate or relay a partial block).
25+
// * reconstruct(share_hash) -> {bytes,hex} => fire broadcast_won_block, whose
26+
// own dual-path guards decide which sink(s) carry it.
27+
//
28+
// DGB-Scrypt is a STANDALONE parent in the V36 default build -- no merged-
29+
// coinbase leg (DOGE aux is -DAUX_DOGE=ON stretch, a separate parent path).
30+
// p2pool-merged-v36 surface: NONE -- block dispatch only; no PoW hash, share
31+
// format, coinbase commitment, or PPLNS math is touched. Per-coin isolation:
32+
// src/impl/dgb/coin/ only.
33+
// ---------------------------------------------------------------------------
34+
35+
#include <functional>
36+
#include <optional>
37+
#include <string>
38+
#include <utility>
39+
#include <vector>
40+
41+
#include <core/coin/node_iface.hpp>
42+
#include <core/log.hpp>
43+
#include <core/uint256.hpp>
44+
45+
#include "block_broadcast.hpp"
46+
47+
namespace dgb
48+
{
49+
namespace coin
50+
{
51+
52+
// Reconstructs the full serialized parent block for a won share.
53+
// share_hash -> {block_bytes, block_hex}, or nullopt if the share is unknown
54+
// or cannot be assembled (e.g. a referenced tx is missing).
55+
// block_bytes is the pre-serialized blob the embedded P2P relay sends; block_hex
56+
// is the same block for the external submitblock fallback. Mirrors p2pool
57+
// data.py Share.as_block; the faithful body lands in a follow-up slice.
58+
using WonBlockReconstructor =
59+
std::function<std::optional<std::pair<std::vector<unsigned char>, std::string>>(const uint256&)>;
60+
61+
// Build the m_on_block_found handler. The run-loop assigns the returned closure
62+
// to tracker.m_on_block_found; `p2p_relay` may be empty (no embedded sink yet)
63+
// and `seam` may be null / RPC-less -- broadcast_won_block guards each leg.
64+
inline std::function<void(const uint256&)>
65+
make_on_block_found(WonBlockReconstructor reconstruct,
66+
P2pRelaySink p2p_relay,
67+
core::coin::ICoinNode* seam)
68+
{
69+
return [reconstruct = std::move(reconstruct),
70+
p2p_relay = std::move(p2p_relay),
71+
seam](const uint256& share_hash)
72+
{
73+
if (!reconstruct) {
74+
LOG_ERROR << "[EMB-DGB] won-block " << share_hash.GetHex().substr(0, 16)
75+
<< " -- no reconstructor wired; cannot broadcast.";
76+
return;
77+
}
78+
79+
auto blk = reconstruct(share_hash);
80+
if (!blk) {
81+
LOG_WARNING << "[EMB-DGB] won-block " << share_hash.GetHex().substr(0, 16)
82+
<< " could not be reconstructed -- NOT broadcast.";
83+
return;
84+
}
85+
86+
LOG_INFO << "[EMB-DGB] GOT BLOCK! share=" << share_hash.GetHex().substr(0, 16)
87+
<< " reconstructed " << blk->first.size()
88+
<< " bytes -- dispatching dual-path.";
89+
broadcast_won_block(p2p_relay, seam, blk->first, blk->second);
90+
};
91+
}
92+
93+
} // namespace coin
94+
} // namespace dgb

src/impl/dgb/test/CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,19 @@ if (BUILD_TESTING AND GTest_FOUND)
4242
dgb_coin pool sharechain)
4343
gtest_add_tests(dgb_block_broadcast_test "" AUTO)
4444

45+
# --- #82 broadcaster-gate: won-block DISPATCH handler --------------------
46+
# Header-only contract test for coin/won_block_dispatch.hpp (the handler the
47+
# run-loop wires tracker().m_on_block_found to: reconstruct share->block,
48+
# then fire broadcast_won_block dual-path). Links core for the ICoinNode seam
49+
# + LOG_* symbols; uses a fake ICoinNode + stub reconstructor, no OBJECT lib.
50+
add_executable(dgb_won_block_dispatch_test won_block_dispatch_test.cpp)
51+
target_link_libraries(dgb_won_block_dispatch_test PRIVATE
52+
GTest::gtest_main GTest::gtest
53+
core dgb
54+
c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage
55+
dgb_coin pool sharechain)
56+
gtest_add_tests(dgb_won_block_dispatch_test "" AUTO)
57+
4558
# --- M3 RPC-transport standalone regression guards ---------------------
4659
# Header-only guards over the external-daemon RPC coin-layer SSOTs
4760
# (rpc_request.hpp request-shape + version floor + genesis identity, and
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// ---------------------------------------------------------------------------
2+
// dgb::coin::make_on_block_found won-block dispatch wiring test (#82
3+
// broadcaster-gate, dispatch half).
4+
//
5+
// Drives the handler the run-loop installs as ShareTracker::m_on_block_found
6+
// END-TO-END: invoke the closure with a winning share hash and assert it (a)
7+
// calls the injected reconstructor for that hash, (b) on a reconstructed block
8+
// fires broadcast_won_block down BOTH paths (P2P primary + RPC fallback), and
9+
// (c) on an unknown/unassemblable share broadcasts NOTHING. This is the seam
10+
// that, once the live NodeP2P relay + faithful as_block reconstructor are
11+
// bound, makes a won block reach the network -- so it locks the dispatch
12+
// contract now while those pieces are still being ported.
13+
//
14+
// Uses a fake ICoinNode (the external-digibyted submitblock fallback is the
15+
// live leg today) + a stub reconstructor, so no embedded daemon / network is
16+
// needed. p2pool-merged-v36 surface: NONE.
17+
//
18+
// MUST appear in BOTH test/CMakeLists.txt AND the build.yml --target allowlist
19+
// or it becomes a #143-style NOT_BUILT sentinel that reds master.
20+
// ---------------------------------------------------------------------------
21+
22+
#include <gtest/gtest.h>
23+
24+
#include <optional>
25+
#include <string>
26+
#include <utility>
27+
#include <vector>
28+
29+
#include "../coin/won_block_dispatch.hpp"
30+
31+
namespace {
32+
33+
// Same controllable seam shape as block_broadcast_test: records the submitblock
34+
// call so the "always-fire fallback" rule is observable through the handler.
35+
class FakeSeam : public core::coin::ICoinNode {
36+
public:
37+
bool rpc_present = true;
38+
bool submit_ack = true;
39+
int submit_calls = 0;
40+
std::string last_hex;
41+
42+
core::coin::WorkView get_work_view() override { return {}; }
43+
bool submit_block_hex(const std::string& hex, bool) override {
44+
++submit_calls; last_hex = hex; return submit_ack;
45+
}
46+
bool is_embedded() const override { return false; }
47+
bool has_rpc() const override { return rpc_present; }
48+
};
49+
50+
const std::vector<unsigned char> kBytes(160, 0x37);
51+
const std::string kHex = "deadbeef";
52+
53+
// Reconstructor that returns a canned block for a single "known" share and
54+
// nullopt for anything else -- and records every hash it was asked about.
55+
struct StubReconstructor {
56+
uint256 known;
57+
std::vector<uint256> seen;
58+
std::optional<std::pair<std::vector<unsigned char>, std::string>>
59+
operator()(const uint256& h) {
60+
seen.push_back(h);
61+
if (h == known)
62+
return std::make_pair(kBytes, kHex);
63+
return std::nullopt;
64+
}
65+
};
66+
67+
uint256 hash_a() { return uint256S("00000000000000000000000000000000000000000000000000000000000000aa"); }
68+
uint256 hash_b() { return uint256S("00000000000000000000000000000000000000000000000000000000000000bb"); }
69+
70+
} // namespace
71+
72+
// 1) Known won share: reconstructor consulted for THAT hash, then both paths
73+
// fire (P2P primary relays the bytes, RPC fallback always fires the hex).
74+
TEST(DgbWonBlockDispatch, KnownShareFiresBothPaths) {
75+
std::vector<unsigned char> relayed;
76+
bool did_relay = false;
77+
auto relay = [&](const std::vector<unsigned char>& b) { did_relay = true; relayed = b; };
78+
79+
auto recon = std::make_shared<StubReconstructor>();
80+
recon->known = hash_a();
81+
FakeSeam seam; seam.rpc_present = true; seam.submit_ack = true;
82+
83+
auto handler = dgb::coin::make_on_block_found(
84+
[recon](const uint256& h) { return (*recon)(h); }, relay, &seam);
85+
86+
handler(hash_a());
87+
88+
ASSERT_EQ(recon->seen.size(), 1u);
89+
EXPECT_EQ(recon->seen[0], hash_a());
90+
EXPECT_TRUE(did_relay);
91+
EXPECT_EQ(relayed, kBytes); // P2P primary carried the reconstructed bytes
92+
EXPECT_EQ(seam.submit_calls, 1); // RPC fallback ALWAYS fired
93+
EXPECT_EQ(seam.last_hex, kHex); // ...with the reconstructed block hex
94+
}
95+
96+
// 2) Unknown share: reconstructor returns nullopt -> NOTHING is broadcast on
97+
// either path (no fabricated / partial block ever reaches the network).
98+
TEST(DgbWonBlockDispatch, UnknownShareBroadcastsNothing) {
99+
bool did_relay = false;
100+
auto relay = [&](const std::vector<unsigned char>&) { did_relay = true; };
101+
102+
auto recon = std::make_shared<StubReconstructor>();
103+
recon->known = hash_a();
104+
FakeSeam seam; seam.rpc_present = true; seam.submit_ack = true;
105+
106+
auto handler = dgb::coin::make_on_block_found(
107+
[recon](const uint256& h) { return (*recon)(h); }, relay, &seam);
108+
109+
handler(hash_b()); // not the known share
110+
111+
ASSERT_EQ(recon->seen.size(), 1u);
112+
EXPECT_EQ(recon->seen[0], hash_b());
113+
EXPECT_FALSE(did_relay); // P2P primary not touched
114+
EXPECT_EQ(seam.submit_calls, 0); // RPC fallback not touched
115+
}
116+
117+
// 3) RPC-only deployment (no embedded P2P sink yet): the handler still carries
118+
// a known won block to the network via the submitblock fallback alone.
119+
TEST(DgbWonBlockDispatch, RpcOnlyStillReachesNetwork) {
120+
auto recon = std::make_shared<StubReconstructor>();
121+
recon->known = hash_a();
122+
FakeSeam seam; seam.rpc_present = true; seam.submit_ack = true;
123+
124+
auto handler = dgb::coin::make_on_block_found(
125+
[recon](const uint256& h) { return (*recon)(h); },
126+
/*p2p_relay=*/{}, &seam);
127+
128+
handler(hash_a());
129+
130+
EXPECT_EQ(seam.submit_calls, 1); // block reached the network via RPC fallback
131+
EXPECT_EQ(seam.last_hex, kHex);
132+
}
133+
134+
// 4) No reconstructor wired at all: handler is a safe no-op, never throws,
135+
// never dereferences the (here null) seam.
136+
TEST(DgbWonBlockDispatch, MissingReconstructorIsSafeNoOp) {
137+
auto handler = dgb::coin::make_on_block_found(
138+
/*reconstruct=*/{}, /*p2p_relay=*/{}, /*seam=*/nullptr);
139+
EXPECT_NO_THROW(handler(hash_a()));
140+
}

0 commit comments

Comments
 (0)