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 @@ -67,7 +67,7 @@ jobs:
test_phase4_embedded \
test_mweb_builder \
test_address_resolution test_compute_share_target \
test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_block_assembly_test dgb_header_sample_build_test dgb_header_ingest_test \
test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_block_assembly_test dgb_header_sample_build_test dgb_header_ingest_test dgb_mempool_ingest_test \
dgb_gentx_coinbase_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test dgb_gentx_share_path_test dgb_other_tx_resolver_test \
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test \
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
Expand Down Expand Up @@ -200,7 +200,7 @@ jobs:
test_phase4_embedded \
test_mweb_builder \
test_address_resolution test_compute_share_target \
test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_block_assembly_test dgb_header_sample_build_test dgb_header_ingest_test \
test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_block_assembly_test dgb_header_sample_build_test dgb_header_ingest_test dgb_mempool_ingest_test \
dgb_gentx_coinbase_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test dgb_gentx_share_path_test dgb_other_tx_resolver_test \
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test \
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
Expand Down
64 changes: 64 additions & 0 deletions src/impl/dgb/coin/mempool_ingest.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#pragma once
// ===========================================================================
// c2pool::dgb::wire_mempool_ingest -- connect the embedded P2P transaction
// relay to the in-process Mempool so live `tx` messages populate the pool the
// embedded work template selects from.
//
// The embedded coin P2P layer (coin/p2p_node.hpp, ADD_P2P_HANDLER(tx)) parses
// each received `tx` message into a coin::Transaction and fires
// dgb::interfaces::Node::new_tx (coin/node_interface.hpp). Until this slice
// nothing consumed that event for the Mempool: relayed transactions were
// dropped on the floor, so the embedded mempool stayed empty regardless of P2P
// traffic and EmbeddedCoinNode's injected EmbeddedTxSource
// (make_mempool_tx_source) had nothing to select -- transactions[] in the
// served template was always empty and coinbasevalue carried subsidy only.
//
// wire_mempool_ingest subscribes the pool to that feed. Each announced
// transaction is converted to the Mempool's MutableTransaction form and handed
// to Mempool::add_tx, which is the single insertion SSOT: it computes the txid,
// rejects duplicates, weighs the tx (BIP141) and enforces the byte cap. This
// connector adds NO policy of its own -- it is the tx analog of
// wire_header_ingest (coin/header_ingest.hpp) for the new_headers feed.
//
// FEE POSTURE: add_tx is called WITHOUT a UTXOViewCache here, so fees stay
// fee_known=false until a UTXO view feeds them. That is deliberate and matches
// the embedded shaper's conservative default (make_mempool_tx_source emits
// fee=null and excludes unknown-fee value from the coinbasevalue fold), so a
// P2P-fed mempool cannot desync coinbasevalue versus a daemon's GBT. Wiring a
// real UTXO view is a later slice; this one only opens the feed.
//
// LIFETIME: the handler captures `pool` by reference, so `pool` MUST outlive
// `node`. The returned EventDisposable lets a caller tear the subscription down
// explicitly; while it (and the node) live, every new_tx relay is ingested.
// ===========================================================================

#include <memory>

#include <core/events.hpp>

#include "node_interface.hpp" // dgb::interfaces::Node (new_tx feed)
#include "mempool.hpp" // dgb::coin::Mempool / MutableTransaction
#include "transaction.hpp" // dgb::coin::Transaction -> MutableTransaction

namespace c2pool::dgb
{

// Subscribe `pool` to `node.new_tx`. Returns the subscription handle so the
// caller controls teardown; the subscription persists for the node's life if
// the handle is dropped (EventDisposable does not auto-dispose on destruction).
//
// NOTE: pulls mempool.hpp -> transaction.hpp (the tx serialization codec), so
// include this header ONLY from a TU that already links the full dgb_coin
// codec (main_dgb.cpp + the codec conformance test), never a guard-weight TU
// -- the #143 btclibs SCC trap, identical to embedded_tx_select.hpp.
inline std::shared_ptr<EventDisposable>
wire_mempool_ingest(::dgb::interfaces::Node& node, ::dgb::coin::Mempool& pool)
{
return node.new_tx.subscribe(
[&pool](const ::dgb::coin::Transaction& tx)
{
pool.add_tx(::dgb::coin::MutableTransaction(tx));
});
}

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

# dgb_mempool_ingest_test: guards c2pool::dgb::wire_mempool_ingest, the
# connector routing the embedded P2P `tx` relay (new_tx) into the Mempool
# (tx analog of wire_header_ingest). Compiles the tx serialization codec, so
# it links the full dgb_coin SCC set rather than the scrypt-only guard set.
# MUST also be in BOTH build.yml --target allowlists (#143 NOT_BUILT trap).
add_executable(dgb_mempool_ingest_test mempool_ingest_test.cpp)
target_link_libraries(dgb_mempool_ingest_test PRIVATE
GTest::gtest_main GTest::gtest
core dgb dgb_stratum
c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage
dgb_coin pool sharechain
nlohmann_json::nlohmann_json)
gtest_add_tests(dgb_mempool_ingest_test "" AUTO)

# --- #82: won-block reconstructor BODY (as_block composition) ----------
# Pins coin/reconstruct_won_block.hpp: the single composition the dispatch
# handler injects as its WonBlockReconstructor (resolve_other_tx_hashes ->
Expand Down
125 changes: 125 additions & 0 deletions src/impl/dgb/test/mempool_ingest_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// ---------------------------------------------------------------------------
// dgb_mempool_ingest_test -- pins c2pool::dgb::wire_mempool_ingest, the
// connector that routes the embedded P2P `tx` relay (dgb::interfaces::Node::
// new_tx) into the in-process Mempool so the embedded work template selects
// from a live pool instead of an always-empty one.
//
// This is the tx analog of dgb_header_ingest_test (new_headers -> HeaderChain).
// Disposition (txid compute, duplicate rejection, weight + byte cap) is
// delegated to Mempool::add_tx, the insertion SSOT; the connector adds no
// policy of its own, so the assertions here pin the WIRING (the feed reaches
// the pool, an unwired node feeds nothing, the handle drives it) rather than
// re-testing the mempool's internals.
//
// Links the full dgb_coin codec like dgb_embedded_tx_select_test (it compiles
// the tx serialization). MUST be in BOTH build.yml --target allowlists (#143
// NOT_BUILT trap).
// ---------------------------------------------------------------------------

#include <cstdint>

#include <gtest/gtest.h>

#include <impl/dgb/coin/mempool_ingest.hpp>
#include <impl/dgb/coin/node_interface.hpp>
#include <impl/dgb/coin/mempool.hpp>
#include <impl/dgb/coin/transaction.hpp>

using c2pool::dgb::wire_mempool_ingest;
using dgb::coin::Mempool;
using dgb::coin::MutableTransaction;
using dgb::coin::Transaction;
using dgb::coin::TxIn;
using dgb::coin::TxOut;
using dgb::coin::compute_txid;

namespace {

// A minimal, distinct tx tagged by its prevout index so each has a distinct
// txid (mirrors the builder in dgb_embedded_tx_select_test).
MutableTransaction tagged_tx(uint32_t index)
{
MutableTransaction tx;
tx.version = 1;
tx.locktime = 0;
TxIn in;
in.prevout.hash.SetNull();
in.prevout.index = index;
in.sequence = 0xffffffff;
tx.vin.push_back(in);
TxOut out;
out.value = 50'000;
tx.vout.push_back(out);
return tx;
}

} // namespace

// 1. A fresh, wired-but-quiet node leaves the pool empty.
TEST(MempoolIngest, EmptyPoolBeforeRelay)
{
Mempool pool;
dgb::interfaces::Node node;
auto sub = wire_mempool_ingest(node, pool);

EXPECT_EQ(pool.size(), 0u);
}

// 2. A tx announced on new_tx is ingested through add_tx: the pool grows and
// the tx is queryable by its txid.
TEST(MempoolIngest, AnnouncedTxIsIngested)
{
Mempool pool;
dgb::interfaces::Node node;
auto sub = wire_mempool_ingest(node, pool);

auto mt = tagged_tx(0);
node.new_tx.happened(Transaction(mt));

EXPECT_EQ(pool.size(), 1u);
EXPECT_TRUE(pool.contains(compute_txid(mt)));
}

// 3. Multiple distinct relays accumulate in the pool.
TEST(MempoolIngest, DistinctRelaysAccumulate)
{
Mempool pool;
dgb::interfaces::Node node;
auto sub = wire_mempool_ingest(node, pool);

auto a = tagged_tx(0);
auto b = tagged_tx(1);
node.new_tx.happened(Transaction(a));
node.new_tx.happened(Transaction(b));

EXPECT_EQ(pool.size(), 2u);
EXPECT_TRUE(pool.contains(compute_txid(a)));
EXPECT_TRUE(pool.contains(compute_txid(b)));
}

// 4. Disposition is delegated to add_tx, not the connector: a duplicate relay
// of the same tx is rejected by the pool's SSOT, leaving size unchanged.
TEST(MempoolIngest, DuplicateRelayIsRejected)
{
Mempool pool;
dgb::interfaces::Node node;
auto sub = wire_mempool_ingest(node, pool);

auto mt = tagged_tx(0);
node.new_tx.happened(Transaction(mt));
node.new_tx.happened(Transaction(mt)); // same txid

EXPECT_EQ(pool.size(), 1u);
}

// 5. The connector is the driver: a node with NO ingest subscription drops the
// relay -- the pool stays empty.
TEST(MempoolIngest, UnwiredNodeIngestsNothing)
{
Mempool pool;
dgb::interfaces::Node node; // deliberately NOT wired

node.new_tx.happened(Transaction(tagged_tx(0)));

EXPECT_EQ(pool.size(), 0u);
}
Loading