Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
test_address_resolution test_compute_share_target \
test_utxo test_dgb_subsidy dgb_share_test \
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
dgb_coin_node_seam_test dgb_block_broadcast_test \
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test \
v37_test \
-j$(nproc)

Expand Down Expand Up @@ -200,7 +200,7 @@ jobs:
test_address_resolution test_compute_share_target \
test_utxo test_dgb_subsidy dgb_share_test \
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
dgb_coin_node_seam_test dgb_block_broadcast_test \
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test \
test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
v37_test \
-j$(nproc)
Expand Down
94 changes: 94 additions & 0 deletions src/impl/dgb/coin/won_block_dispatch.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#pragma once
// ---------------------------------------------------------------------------
// dgb::coin::make_on_block_found -- the won-block DISPATCH handler the run-loop
// installs as dgb::ShareTracker::m_on_block_found.
//
// This is the wiring half between the dispatcher (#166, broadcast_won_block)
// and the live run-loop: on a won block the tracker fires m_on_block_found with
// the winning share hash. This handler turns that share hash into a fully
// serialized parent block (via an injected WonBlockReconstructor -- the
// share->block "as_block" step) and then dispatches it down BOTH broadcast
// paths via broadcast_won_block (P2P primary + external digibyted submitblock
// fallback, always-fire dual-path rule).
//
// The reconstruct step is injected as a std::function rather than called inline
// so this handler is build-verifiable and run-tested NOW, before the faithful
// share->block reassembly (gentx_before_refhash + ref/merkle link + tx lookup,
// mirroring p2pool data.py Share.as_block) is ported. The run-loop binds the
// real reconstructor + the live P2P relay sink + the CoinNode seam when the
// embedded NodeP2P lands; until then a stub reconstructor + the external-RPC
// fallback already carry a won block onto the network.
//
// Contract:
// * reconstruct(share_hash) -> nullopt => UNKNOWN/unassemblable share: log a
// warning and broadcast NOTHING (never fabricate or relay a partial block).
// * reconstruct(share_hash) -> {bytes,hex} => fire broadcast_won_block, whose
// own dual-path guards decide which sink(s) carry it.
//
// DGB-Scrypt is a STANDALONE parent in the V36 default build -- no merged-
// coinbase leg (DOGE aux is -DAUX_DOGE=ON stretch, a separate parent path).
// p2pool-merged-v36 surface: NONE -- block dispatch only; no PoW hash, share
// format, coinbase commitment, or PPLNS math is touched. Per-coin isolation:
// src/impl/dgb/coin/ only.
// ---------------------------------------------------------------------------

#include <functional>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include <core/coin/node_iface.hpp>
#include <core/log.hpp>
#include <core/uint256.hpp>

#include "block_broadcast.hpp"

namespace dgb
{
namespace coin
{

// Reconstructs the full serialized parent block for a won share.
// share_hash -> {block_bytes, block_hex}, or nullopt if the share is unknown
// or cannot be assembled (e.g. a referenced tx is missing).
// block_bytes is the pre-serialized blob the embedded P2P relay sends; block_hex
// is the same block for the external submitblock fallback. Mirrors p2pool
// data.py Share.as_block; the faithful body lands in a follow-up slice.
using WonBlockReconstructor =
std::function<std::optional<std::pair<std::vector<unsigned char>, std::string>>(const uint256&)>;

// Build the m_on_block_found handler. The run-loop assigns the returned closure
// to tracker.m_on_block_found; `p2p_relay` may be empty (no embedded sink yet)
// and `seam` may be null / RPC-less -- broadcast_won_block guards each leg.
inline std::function<void(const uint256&)>
make_on_block_found(WonBlockReconstructor reconstruct,
P2pRelaySink p2p_relay,
core::coin::ICoinNode* seam)
{
return [reconstruct = std::move(reconstruct),
p2p_relay = std::move(p2p_relay),
seam](const uint256& share_hash)
{
if (!reconstruct) {
LOG_ERROR << "[EMB-DGB] won-block " << share_hash.GetHex().substr(0, 16)
<< " -- no reconstructor wired; cannot broadcast.";
return;
}

auto blk = reconstruct(share_hash);
if (!blk) {
LOG_WARNING << "[EMB-DGB] won-block " << share_hash.GetHex().substr(0, 16)
<< " could not be reconstructed -- NOT broadcast.";
return;
}

LOG_INFO << "[EMB-DGB] GOT BLOCK! share=" << share_hash.GetHex().substr(0, 16)
<< " reconstructed " << blk->first.size()
<< " bytes -- dispatching dual-path.";
broadcast_won_block(p2p_relay, seam, blk->first, blk->second);
};
}

} // namespace coin
} // namespace dgb
13 changes: 13 additions & 0 deletions src/impl/dgb/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ if (BUILD_TESTING AND GTest_FOUND)
dgb_coin pool sharechain)
gtest_add_tests(dgb_block_broadcast_test "" AUTO)

# --- #82 broadcaster-gate: won-block DISPATCH handler --------------------
# Header-only contract test for coin/won_block_dispatch.hpp (the handler the
# run-loop wires tracker().m_on_block_found to: reconstruct share->block,
# then fire broadcast_won_block dual-path). Links core for the ICoinNode seam
# + LOG_* symbols; uses a fake ICoinNode + stub reconstructor, no OBJECT lib.
add_executable(dgb_won_block_dispatch_test won_block_dispatch_test.cpp)
target_link_libraries(dgb_won_block_dispatch_test PRIVATE
GTest::gtest_main GTest::gtest
core dgb
c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage
dgb_coin pool sharechain)
gtest_add_tests(dgb_won_block_dispatch_test "" AUTO)

# --- M3 RPC-transport standalone regression guards ---------------------
# Header-only guards over the external-daemon RPC coin-layer SSOTs
# (rpc_request.hpp request-shape + version floor + genesis identity, and
Expand Down
140 changes: 140 additions & 0 deletions src/impl/dgb/test/won_block_dispatch_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// ---------------------------------------------------------------------------
// dgb::coin::make_on_block_found won-block dispatch wiring test (#82
// broadcaster-gate, dispatch half).
//
// Drives the handler the run-loop installs as ShareTracker::m_on_block_found
// END-TO-END: invoke the closure with a winning share hash and assert it (a)
// calls the injected reconstructor for that hash, (b) on a reconstructed block
// fires broadcast_won_block down BOTH paths (P2P primary + RPC fallback), and
// (c) on an unknown/unassemblable share broadcasts NOTHING. This is the seam
// that, once the live NodeP2P relay + faithful as_block reconstructor are
// bound, makes a won block reach the network -- so it locks the dispatch
// contract now while those pieces are still being ported.
//
// Uses a fake ICoinNode (the external-digibyted submitblock fallback is the
// live leg today) + a stub reconstructor, so no embedded daemon / network is
// needed. p2pool-merged-v36 surface: NONE.
//
// MUST appear in BOTH test/CMakeLists.txt AND the build.yml --target allowlist
// or it becomes a #143-style NOT_BUILT sentinel that reds master.
// ---------------------------------------------------------------------------

#include <gtest/gtest.h>

#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "../coin/won_block_dispatch.hpp"

namespace {

// Same controllable seam shape as block_broadcast_test: records the submitblock
// call so the "always-fire fallback" rule is observable through the handler.
class FakeSeam : public core::coin::ICoinNode {
public:
bool rpc_present = true;
bool submit_ack = true;
int submit_calls = 0;
std::string last_hex;

core::coin::WorkView get_work_view() override { return {}; }
bool submit_block_hex(const std::string& hex, bool) override {
++submit_calls; last_hex = hex; return submit_ack;
}
bool is_embedded() const override { return false; }
bool has_rpc() const override { return rpc_present; }
};

const std::vector<unsigned char> kBytes(160, 0x37);
const std::string kHex = "deadbeef";

// Reconstructor that returns a canned block for a single "known" share and
// nullopt for anything else -- and records every hash it was asked about.
struct StubReconstructor {
uint256 known;
std::vector<uint256> seen;
std::optional<std::pair<std::vector<unsigned char>, std::string>>
operator()(const uint256& h) {
seen.push_back(h);
if (h == known)
return std::make_pair(kBytes, kHex);
return std::nullopt;
}
};

uint256 hash_a() { return uint256S("00000000000000000000000000000000000000000000000000000000000000aa"); }
uint256 hash_b() { return uint256S("00000000000000000000000000000000000000000000000000000000000000bb"); }

} // namespace

// 1) Known won share: reconstructor consulted for THAT hash, then both paths
// fire (P2P primary relays the bytes, RPC fallback always fires the hex).
TEST(DgbWonBlockDispatch, KnownShareFiresBothPaths) {
std::vector<unsigned char> relayed;
bool did_relay = false;
auto relay = [&](const std::vector<unsigned char>& b) { did_relay = true; relayed = b; };

auto recon = std::make_shared<StubReconstructor>();
recon->known = hash_a();
FakeSeam seam; seam.rpc_present = true; seam.submit_ack = true;

auto handler = dgb::coin::make_on_block_found(
[recon](const uint256& h) { return (*recon)(h); }, relay, &seam);

handler(hash_a());

ASSERT_EQ(recon->seen.size(), 1u);
EXPECT_EQ(recon->seen[0], hash_a());
EXPECT_TRUE(did_relay);
EXPECT_EQ(relayed, kBytes); // P2P primary carried the reconstructed bytes
EXPECT_EQ(seam.submit_calls, 1); // RPC fallback ALWAYS fired
EXPECT_EQ(seam.last_hex, kHex); // ...with the reconstructed block hex
}

// 2) Unknown share: reconstructor returns nullopt -> NOTHING is broadcast on
// either path (no fabricated / partial block ever reaches the network).
TEST(DgbWonBlockDispatch, UnknownShareBroadcastsNothing) {
bool did_relay = false;
auto relay = [&](const std::vector<unsigned char>&) { did_relay = true; };

auto recon = std::make_shared<StubReconstructor>();
recon->known = hash_a();
FakeSeam seam; seam.rpc_present = true; seam.submit_ack = true;

auto handler = dgb::coin::make_on_block_found(
[recon](const uint256& h) { return (*recon)(h); }, relay, &seam);

handler(hash_b()); // not the known share

ASSERT_EQ(recon->seen.size(), 1u);
EXPECT_EQ(recon->seen[0], hash_b());
EXPECT_FALSE(did_relay); // P2P primary not touched
EXPECT_EQ(seam.submit_calls, 0); // RPC fallback not touched
}

// 3) RPC-only deployment (no embedded P2P sink yet): the handler still carries
// a known won block to the network via the submitblock fallback alone.
TEST(DgbWonBlockDispatch, RpcOnlyStillReachesNetwork) {
auto recon = std::make_shared<StubReconstructor>();
recon->known = hash_a();
FakeSeam seam; seam.rpc_present = true; seam.submit_ack = true;

auto handler = dgb::coin::make_on_block_found(
[recon](const uint256& h) { return (*recon)(h); },
/*p2p_relay=*/{}, &seam);

handler(hash_a());

EXPECT_EQ(seam.submit_calls, 1); // block reached the network via RPC fallback
EXPECT_EQ(seam.last_hex, kHex);
}

// 4) No reconstructor wired at all: handler is a safe no-op, never throws,
// never dereferences the (here null) seam.
TEST(DgbWonBlockDispatch, MissingReconstructorIsSafeNoOp) {
auto handler = dgb::coin::make_on_block_found(
/*reconstruct=*/{}, /*p2p_relay=*/{}, /*seam=*/nullptr);
EXPECT_NO_THROW(handler(hash_a()));
}