|
| 1 | +// --------------------------------------------------------------------------- |
| 2 | +// btc::coin::broadcast_block_with_fallback dual-path guard KATs. |
| 3 | +// |
| 4 | +// Offline contract slice locking the won-block fallback orchestration the BTC |
| 5 | +// run-loop depends on: P2P relay is PRIMARY, submitblock RPC is the FALLBACK |
| 6 | +// (fired ONLY when P2P is unavailable / relay-failed -- deliberately NOT |
| 7 | +// always-both). The function's doc-comment promises a false return is the |
| 8 | +// ONLY "reached neither sink" signal and that a won block is NEVER |
| 9 | +// silent-dropped. A THROWING relay_p2p() sink violated that promise: the |
| 10 | +// exception unwound PAST the function, bypassing the submitblock fallback |
| 11 | +// entirely and silently dropping the won block -- the exact hole the fallback |
| 12 | +// exists to close. These KATs lock BOTH legs guarded (mirror of the NMC #468 |
| 13 | +// leg-guard fix in nmc_block_broadcast_test.cpp). |
| 14 | +// |
| 15 | +// Rides the already-allowlisted btc_share_test executable, so no build.yml |
| 16 | +// --target allowlist change is needed and there is no #137-style NOT_BUILT |
| 17 | +// sentinel risk. p2pool-merged-v36 surface: NONE. |
| 18 | +// --------------------------------------------------------------------------- |
| 19 | + |
| 20 | +#include <gtest/gtest.h> |
| 21 | + |
| 22 | +#include <stdexcept> |
| 23 | + |
| 24 | +#include "../coin/block_broadcast.hpp" |
| 25 | + |
| 26 | +// 1) Primary succeeds -> true, and the submitblock fallback is NOT invoked |
| 27 | +// (the deliberate no-double-broadcast rule). |
| 28 | +TEST(BtcBlockBroadcastGuard, P2pSuccessSkipsFallback) { |
| 29 | + bool rpc_called = false; |
| 30 | + auto relay = [] { return true; }; |
| 31 | + auto submit = [&] { rpc_called = true; return true; }; |
| 32 | + |
| 33 | + EXPECT_TRUE(btc::coin::broadcast_block_with_fallback(relay, submit)); |
| 34 | + EXPECT_FALSE(rpc_called); // primary won -> no double submit |
| 35 | +} |
| 36 | + |
| 37 | +// 2) Primary relay-fails (returns false) -> submitblock fallback fires. |
| 38 | +TEST(BtcBlockBroadcastGuard, P2pFalseFiresFallback) { |
| 39 | + int rpc_calls = 0; |
| 40 | + auto relay = [] { return false; }; |
| 41 | + auto submit = [&] { ++rpc_calls; return true; }; |
| 42 | + |
| 43 | + EXPECT_TRUE(btc::coin::broadcast_block_with_fallback(relay, submit)); |
| 44 | + EXPECT_EQ(rpc_calls, 1); |
| 45 | +} |
| 46 | + |
| 47 | +// 3) PRIMARY-LEG GUARD (the fix): a THROWING relay sink is a relay-failed mode, |
| 48 | +// not a reason to skip the fallback. The submitblock RPC STILL fires, the |
| 49 | +// won block reaches the node, and the dispatcher does NOT propagate the |
| 50 | +// throw. Before the guard this exception unwound past the function and the |
| 51 | +// block was silently dropped. |
| 52 | +TEST(BtcBlockBroadcastGuard, ThrowingP2pStillFiresFallback) { |
| 53 | + int rpc_calls = 0; |
| 54 | + auto relay = []() -> bool { throw std::runtime_error("p2p down mid-relay"); }; |
| 55 | + auto submit = [&] { ++rpc_calls; return true; }; |
| 56 | + |
| 57 | + bool ok = false; |
| 58 | + EXPECT_NO_THROW(ok = btc::coin::broadcast_block_with_fallback(relay, submit)); |
| 59 | + EXPECT_TRUE(ok); // fallback carried the won block |
| 60 | + EXPECT_EQ(rpc_calls, 1); // fallback fired despite the throw |
| 61 | +} |
| 62 | + |
| 63 | +// 4) Throwing primary with NO fallback sink -> definite false (reached neither |
| 64 | +// sink, the caller's documented "scream / lost-subsidy" path), never a throw. |
| 65 | +TEST(BtcBlockBroadcastGuard, ThrowingP2pNoFallbackReturnsFalse) { |
| 66 | + auto relay = []() -> bool { throw std::runtime_error("p2p down"); }; |
| 67 | + |
| 68 | + bool ok = true; |
| 69 | + EXPECT_NO_THROW(ok = btc::coin::broadcast_block_with_fallback(relay, /*submit_rpc=*/{})); |
| 70 | + EXPECT_FALSE(ok); |
| 71 | +} |
| 72 | + |
| 73 | +// 5) FALLBACK-LEG GUARD: when submitblock itself throws there is no further |
| 74 | +// sink, so the throw collapses to a definite false return -> the caller's |
| 75 | +// "reached neither sink, scream" contract fires instead of an exception |
| 76 | +// escaping the won-block handler. |
| 77 | +TEST(BtcBlockBroadcastGuard, ThrowingFallbackReturnsFalse) { |
| 78 | + auto relay = [] { return false; }; |
| 79 | + auto submit = []() -> bool { throw std::runtime_error("submitblock rpc threw"); }; |
| 80 | + |
| 81 | + bool ok = true; |
| 82 | + EXPECT_NO_THROW(ok = btc::coin::broadcast_block_with_fallback(relay, submit)); |
| 83 | + EXPECT_FALSE(ok); |
| 84 | +} |
| 85 | + |
| 86 | +// 6) Neither sink live -> safe false no-op (no throw, no silent-drop). |
| 87 | +TEST(BtcBlockBroadcastGuard, NoSinkIsSafeFalse) { |
| 88 | + bool ok = true; |
| 89 | + EXPECT_NO_THROW(ok = btc::coin::broadcast_block_with_fallback({}, {})); |
| 90 | + EXPECT_FALSE(ok); |
| 91 | +} |
0 commit comments