|
| 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