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 @@ -82,7 +82,7 @@ jobs:
test_address_resolution test_compute_share_target \
test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_redistribute_test dgb_block_assembly_test dgb_witness_commitment_test dgb_header_sample_build_test dgb_header_ingest_test dgb_mempool_ingest_test \
dgb_gentx_coinbase_test dgb_connection_coinbase_test dgb_pplns_payout_split_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test nmc_reconstruct_won_block_test nmc_mempool_name_test nmc_block_broadcast_test nmc_host_dualpath_test nmc_fallback_path_conformance_test dgb_gentx_share_path_test dgb_conn_pplns_producer_test dgb_other_tx_resolver_test \
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 dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_mm_commitment_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test dgb_compact_blocks_bip152_parity_test dgb_aux_dual_target_select_test \
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 dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_mm_commitment_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test dgb_compact_blocks_bip152_parity_test dgb_aux_dual_target_select_test dgb_aux_broadcast_path_election_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_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test dgb_tail_score_endpoints_test dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test dgb_binomial_conf_interval_test dgb_desired_version_tally_test dgb_get_height_and_last_endpoints_test dgb_chain_walk_window_test dgb_redistribute_delegate_ghal_test dgb_share_weight_decay_test dgb_naughty_propagation_test dgb_hash_format_parity_test v37_test \
-j$(nproc)
Expand Down Expand Up @@ -214,7 +214,7 @@ jobs:
test_address_resolution test_compute_share_target \
test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_redistribute_test dgb_block_assembly_test dgb_witness_commitment_test dgb_header_sample_build_test dgb_header_ingest_test dgb_mempool_ingest_test \
dgb_gentx_coinbase_test dgb_connection_coinbase_test dgb_pplns_payout_split_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test nmc_reconstruct_won_block_test nmc_mempool_name_test nmc_block_broadcast_test nmc_host_dualpath_test nmc_fallback_path_conformance_test dgb_gentx_share_path_test dgb_conn_pplns_producer_test dgb_other_tx_resolver_test \
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 dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_mm_commitment_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test dgb_compact_blocks_bip152_parity_test dgb_aux_dual_target_select_test \
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 dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_mm_commitment_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test dgb_compact_blocks_bip152_parity_test dgb_aux_dual_target_select_test dgb_aux_broadcast_path_election_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_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test dgb_tail_score_endpoints_test dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test dgb_binomial_conf_interval_test dgb_desired_version_tally_test dgb_get_height_and_last_endpoints_test dgb_chain_walk_window_test dgb_redistribute_delegate_ghal_test dgb_share_weight_decay_test dgb_naughty_propagation_test dgb_hash_format_parity_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
v37_test \
Expand Down
112 changes: 112 additions & 0 deletions src/impl/dgb/coin/aux_broadcast_path_election.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// ---------------------------------------------------------------------------
// DGB+DOGE (phase DC) — broadcast-path ELECTION + single-fire ledger.
//
// Fenced / test-only contract. Pins the HARD precondition the integrator set
// for any DC live-wire (UID2478): the submitauxblock RPC fallback MUST NOT
// double-fire the same DOGE block hash alongside the embedded submit path.
// Both paths must be mutually exclusive PER WON BLOCK.
//
// This complements coin/aux_dual_target_select.hpp (#490), which decides WHICH
// chain a scrypt pow_hash hits. This header decides, once a DOGE-aux win
// exists, WHICH broadcast path carries it to the network — and proves exactly
// one ever does.
//
// Two orthogonal guards compose into single-fire:
//
// ELECTION (mutual exclusion AT one decision point): the embedded
// submit_block path (frozen fully-assembled block -> P2P relay) is PRIMARY,
// mirroring aux_chain_embedded.hpp where submit_block is the primary method
// and submit_aux_block is the "no daemon to submit to" fallback. RPC
// submitauxblock is elected ONLY when no embedded relay is available. A win
// must be present for any path to fire. The function is total and returns
// exactly one AuxBroadcastPath — never "both".
//
// LEDGER (mutual exclusion ACROSS TIME / retries): even with a correct
// election, a retry or a both-paths-attempted race could re-broadcast the
// same DOGE block hash. AuxBroadcastLedger::try_fire records a hash on its
// first authorization and suppresses every later attempt for that hash,
// regardless of which path attempts it. This is the idempotent guard the
// integrator named ("idempotent guard or path-election before broadcast").
//
// Pure helpers; links only core (uint256) + <set>. Consumes nothing in
// src/impl/doge and touches no node seam (the seam stays parked on the live
// DC slice). Non-circular: the primary-embedded / fallback-RPC posture is
// restated by value here, mirroring aux_chain_embedded.hpp's documented method
// roles without including it.
// ---------------------------------------------------------------------------
#pragma once

#include <set>
#include <core/uint256.hpp>

namespace dgb
{
namespace coin
{

// The broadcast carrier for a won DOGE-aux block. Exactly one is ever elected.
enum class AuxBroadcastPath
{
None, // no win, or no carrier available
EmbeddedSubmitBlock, // PRIMARY: frozen block via embedded P2P relay
RpcSubmitAuxBlock, // FALLBACK: external daemon submitauxblock RPC
};

// Path-election. Total function: returns exactly one path. Embedded is primary;
// RPC fallback only when embedded relay is unavailable; None when there is no
// win or no carrier at all. By construction the two fire-paths are mutually
// exclusive — there is no input for which both EmbeddedSubmitBlock and
// RpcSubmitAuxBlock are "selected", because the result is a single enum value.
inline AuxBroadcastPath elect_aux_broadcast_path(bool have_doge_aux_win,
bool embedded_relay_available,
bool rpc_submit_available)
{
if (!have_doge_aux_win) return AuxBroadcastPath::None;
if (embedded_relay_available) return AuxBroadcastPath::EmbeddedSubmitBlock;
if (rpc_submit_available) return AuxBroadcastPath::RpcSubmitAuxBlock;
return AuxBroadcastPath::None;
}

// Idempotent single-fire ledger keyed on the DOGE block hash. try_fire returns
// true EXACTLY ONCE per distinct hash (first call records + authorizes); every
// later call for the same hash returns false (double-fire suppressed),
// independent of which path attempts it. Distinct hashes are independent.
class AuxBroadcastLedger
{
public:
// Authorize-and-record. true => fresh, caller should broadcast.
// false => this hash was already broadcast; suppress.
bool try_fire(const uint256& doge_block_hash)
{
return m_fired.insert(doge_block_hash).second;
}

bool already_fired(const uint256& doge_block_hash) const
{
return m_fired.find(doge_block_hash) != m_fired.end();
}

private:
std::set<uint256> m_fired;
};

// Convenience composition: elect a path AND consult the ledger in one call.
// Returns the path that should actually broadcast — None if no win, no carrier,
// OR the hash was already fired. On a non-None return the hash is recorded, so
// a subsequent call with the same hash yields None (single-fire guaranteed even
// if BOTH carrier conditions are true on the retry).
inline AuxBroadcastPath elect_and_claim(AuxBroadcastLedger& ledger,
const uint256& doge_block_hash,
bool have_doge_aux_win,
bool embedded_relay_available,
bool rpc_submit_available)
{
const AuxBroadcastPath path = elect_aux_broadcast_path(
have_doge_aux_win, embedded_relay_available, rpc_submit_available);
if (path == AuxBroadcastPath::None) return AuxBroadcastPath::None;
if (!ledger.try_fire(doge_block_hash)) return AuxBroadcastPath::None;
return path;
}

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

# --- DGB+DOGE (phase DC) -- broadcast-path single-fire PROOF KAT ----------
# Proves the integrator HARD precondition (UID2478): the submitauxblock RPC
# fallback cannot double-fire the same DOGE block hash alongside the embedded
# submit path. Pins coin/aux_broadcast_path_election.hpp -- election returns
# exactly one carrier per won block (embedded primary / RPC fallback) and the
# idempotent ledger suppresses any second broadcast of an already-fired hash.
# Pure helpers, consumes nothing in src/impl/doge, touches no node seam. MUST
# appear in BOTH this registration AND the build.yml --target allowlist
# (#143 NOT_BUILT trap).
add_executable(dgb_aux_broadcast_path_election_test aux_broadcast_path_election_test.cpp)
target_link_libraries(dgb_aux_broadcast_path_election_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_aux_broadcast_path_election_test "" AUTO)

# dgb_share_target_genesis_test: FENCED KAT pinning ShareTracker::compute_
# share_target genesis/unknown-prev clamp to [MAX_TARGET/30, MAX_TARGET] vs
# the DGB oracle data.py generate_transaction. Links the same proven set as
Expand Down
138 changes: 138 additions & 0 deletions src/impl/dgb/test/aux_broadcast_path_election_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// ---------------------------------------------------------------------------
// DGB+DOGE (phase DC) — broadcast-path single-fire proof KAT.
//
// Fenced / test-only. Positively PROVES the integrator's HARD precondition for
// any DC live-wire (UID2478): the submitauxblock RPC fallback cannot double-
// fire the same DOGE block hash alongside the embedded submit path. Proven two
// ways that together cover the race:
// (A) ELECTION returns exactly one carrier per won block (never both), with
// embedded primary / RPC fallback ordering (mirrors aux_chain_embedded
// .hpp submit_block-primary / submit_aux_block-fallback roles).
// (B) the idempotent LEDGER suppresses any second broadcast of an already-
// fired hash, regardless of path — so a retry or both-paths-attempted
// race yields exactly ONE network broadcast.
//
// Non-circular: every golden is restated by value here. Pure helpers; links
// only core (uint256). Consumes nothing in src/impl/doge, touches no node seam.
// MUST appear in BOTH test/CMakeLists.txt AND the build.yml --target allowlist
// (#143 NOT_BUILT sentinel trap).
// ---------------------------------------------------------------------------

#include <gtest/gtest.h>

#include <impl/dgb/coin/aux_broadcast_path_election.hpp>
#include <core/uint256.hpp>

using dgb::coin::AuxBroadcastPath;
using dgb::coin::AuxBroadcastLedger;
using dgb::coin::elect_aux_broadcast_path;
using dgb::coin::elect_and_claim;

namespace {
uint256 H(uint64_t n) { uint256 h; h.SetNull(); *h.begin() = static_cast<uint8_t>(n & 0xff); *(h.begin() + 1) = static_cast<uint8_t>((n >> 8) & 0xff); return h; }
}

// ---- (A) ELECTION truth table: exactly one carrier per won block -----------

// No win -> no carrier on EITHER path, whatever the carrier availability.
TEST(DGB_AuxBroadcastElection, NoWinNeverFires) {
for (int e = 0; e < 2; ++e)
for (int r = 0; r < 2; ++r)
EXPECT_EQ(elect_aux_broadcast_path(/*win*/false, e, r),
AuxBroadcastPath::None);
}

// Win + embedded relay available -> ALWAYS embedded (primary), even if RPC is
// also available. This is the core mutual-exclusion: RPC is never co-selected.
TEST(DGB_AuxBroadcastElection, EmbeddedIsPrimaryWhenAvailable) {
EXPECT_EQ(elect_aux_broadcast_path(true, /*emb*/true, /*rpc*/true),
AuxBroadcastPath::EmbeddedSubmitBlock);
EXPECT_EQ(elect_aux_broadcast_path(true, /*emb*/true, /*rpc*/false),
AuxBroadcastPath::EmbeddedSubmitBlock);
}

// Win + no embedded relay + RPC available -> RPC fallback (and ONLY then).
TEST(DGB_AuxBroadcastElection, RpcOnlyAsFallback) {
EXPECT_EQ(elect_aux_broadcast_path(true, /*emb*/false, /*rpc*/true),
AuxBroadcastPath::RpcSubmitAuxBlock);
}

// Win + no carrier at all -> None (cannot fabricate a broadcast).
TEST(DGB_AuxBroadcastElection, NoCarrierNoFire) {
EXPECT_EQ(elect_aux_broadcast_path(true, /*emb*/false, /*rpc*/false),
AuxBroadcastPath::None);
}

// Exhaustive: across the full 2x2x2 input cube the result is a single enum
// value — there is structurally no input that yields "both paths".
TEST(DGB_AuxBroadcastElection, ResultIsAlwaysSinglePath) {
for (int w = 0; w < 2; ++w)
for (int e = 0; e < 2; ++e)
for (int r = 0; r < 2; ++r) {
AuxBroadcastPath p = elect_aux_broadcast_path(w, e, r);
const bool is_embedded = (p == AuxBroadcastPath::EmbeddedSubmitBlock);
const bool is_rpc = (p == AuxBroadcastPath::RpcSubmitAuxBlock);
EXPECT_FALSE(is_embedded && is_rpc); // never both
if (!w) EXPECT_FALSE(is_embedded || is_rpc);
}
}

// ---- (B) idempotent LEDGER: one broadcast per distinct hash ----------------

TEST(DGB_AuxBroadcastLedger, FirstFireFreshSecondSuppressed) {
AuxBroadcastLedger ledger;
const uint256 h = H(0xABCD);
EXPECT_FALSE(ledger.already_fired(h));
EXPECT_TRUE (ledger.try_fire(h)); // first authorization
EXPECT_TRUE (ledger.already_fired(h));
EXPECT_FALSE(ledger.try_fire(h)); // double-fire suppressed
EXPECT_FALSE(ledger.try_fire(h)); // and stays suppressed
}

TEST(DGB_AuxBroadcastLedger, DistinctHashesAreIndependent) {
AuxBroadcastLedger ledger;
EXPECT_TRUE(ledger.try_fire(H(1)));
EXPECT_TRUE(ledger.try_fire(H(2)));
EXPECT_FALSE(ledger.try_fire(H(1))); // h1 already fired
EXPECT_TRUE(ledger.try_fire(H(3)));
}

// ---- (A)+(B) end-to-end single-fire: both paths race the SAME hash ---------

// The integrator's exact scenario: the same won DOGE block is attempted via the
// embedded path AND the RPC fallback. elect_and_claim authorizes exactly ONE;
// the second attempt (different carrier availability, same hash) yields None.
TEST(DGB_AuxBroadcastSingleFire, EmbeddedThenRpcSameHashFiresOnce) {
AuxBroadcastLedger ledger;
const uint256 won = H(0x5151);

// Embedded path wins the election and claims the hash.
EXPECT_EQ(elect_and_claim(ledger, won, /*win*/true, /*emb*/true, /*rpc*/true),
AuxBroadcastPath::EmbeddedSubmitBlock);

// RPC fallback later attempts the SAME hash (e.g. daemon retry) — suppressed.
EXPECT_EQ(elect_and_claim(ledger, won, /*win*/true, /*emb*/false, /*rpc*/true),
AuxBroadcastPath::None);
}

// Symmetric: RPC fires first, embedded retry suppressed — order-independent.
TEST(DGB_AuxBroadcastSingleFire, RpcThenEmbeddedSameHashFiresOnce) {
AuxBroadcastLedger ledger;
const uint256 won = H(0x6262);

EXPECT_EQ(elect_and_claim(ledger, won, /*win*/true, /*emb*/false, /*rpc*/true),
AuxBroadcastPath::RpcSubmitAuxBlock);
EXPECT_EQ(elect_and_claim(ledger, won, /*win*/true, /*emb*/true, /*rpc*/true),
AuxBroadcastPath::None);
}

// A no-win claim never consumes the ledger, so a later genuine win still fires.
TEST(DGB_AuxBroadcastSingleFire, NoWinDoesNotBurnTheHash) {
AuxBroadcastLedger ledger;
const uint256 h = H(0x7777);
EXPECT_EQ(elect_and_claim(ledger, h, /*win*/false, true, true),
AuxBroadcastPath::None);
EXPECT_FALSE(ledger.already_fired(h)); // not recorded
EXPECT_EQ(elect_and_claim(ledger, h, /*win*/true, true, true),
AuxBroadcastPath::EmbeddedSubmitBlock); // genuine win still fires
}
Loading