Skip to content

Commit 4ebd5f8

Browse files
authored
Merge pull request #245 from frstrtr/dgb/embedded-mempool-ingest-feed
dgb: wire embedded P2P tx relay into the in-process Mempool
2 parents 3ddbca3 + 008ba08 commit 4ebd5f8

4 files changed

Lines changed: 205 additions & 2 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ jobs:
6767
test_phase4_embedded \
6868
test_mweb_builder \
6969
test_address_resolution test_compute_share_target \
70-
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 \
70+
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 \
7171
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 \
7272
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 \
7373
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
@@ -200,7 +200,7 @@ jobs:
200200
test_phase4_embedded \
201201
test_mweb_builder \
202202
test_address_resolution test_compute_share_target \
203-
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 \
203+
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 \
204204
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 \
205205
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 \
206206
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#pragma once
2+
// ===========================================================================
3+
// c2pool::dgb::wire_mempool_ingest -- connect the embedded P2P transaction
4+
// relay to the in-process Mempool so live `tx` messages populate the pool the
5+
// embedded work template selects from.
6+
//
7+
// The embedded coin P2P layer (coin/p2p_node.hpp, ADD_P2P_HANDLER(tx)) parses
8+
// each received `tx` message into a coin::Transaction and fires
9+
// dgb::interfaces::Node::new_tx (coin/node_interface.hpp). Until this slice
10+
// nothing consumed that event for the Mempool: relayed transactions were
11+
// dropped on the floor, so the embedded mempool stayed empty regardless of P2P
12+
// traffic and EmbeddedCoinNode's injected EmbeddedTxSource
13+
// (make_mempool_tx_source) had nothing to select -- transactions[] in the
14+
// served template was always empty and coinbasevalue carried subsidy only.
15+
//
16+
// wire_mempool_ingest subscribes the pool to that feed. Each announced
17+
// transaction is converted to the Mempool's MutableTransaction form and handed
18+
// to Mempool::add_tx, which is the single insertion SSOT: it computes the txid,
19+
// rejects duplicates, weighs the tx (BIP141) and enforces the byte cap. This
20+
// connector adds NO policy of its own -- it is the tx analog of
21+
// wire_header_ingest (coin/header_ingest.hpp) for the new_headers feed.
22+
//
23+
// FEE POSTURE: add_tx is called WITHOUT a UTXOViewCache here, so fees stay
24+
// fee_known=false until a UTXO view feeds them. That is deliberate and matches
25+
// the embedded shaper's conservative default (make_mempool_tx_source emits
26+
// fee=null and excludes unknown-fee value from the coinbasevalue fold), so a
27+
// P2P-fed mempool cannot desync coinbasevalue versus a daemon's GBT. Wiring a
28+
// real UTXO view is a later slice; this one only opens the feed.
29+
//
30+
// LIFETIME: the handler captures `pool` by reference, so `pool` MUST outlive
31+
// `node`. The returned EventDisposable lets a caller tear the subscription down
32+
// explicitly; while it (and the node) live, every new_tx relay is ingested.
33+
// ===========================================================================
34+
35+
#include <memory>
36+
37+
#include <core/events.hpp>
38+
39+
#include "node_interface.hpp" // dgb::interfaces::Node (new_tx feed)
40+
#include "mempool.hpp" // dgb::coin::Mempool / MutableTransaction
41+
#include "transaction.hpp" // dgb::coin::Transaction -> MutableTransaction
42+
43+
namespace c2pool::dgb
44+
{
45+
46+
// Subscribe `pool` to `node.new_tx`. Returns the subscription handle so the
47+
// caller controls teardown; the subscription persists for the node's life if
48+
// the handle is dropped (EventDisposable does not auto-dispose on destruction).
49+
//
50+
// NOTE: pulls mempool.hpp -> transaction.hpp (the tx serialization codec), so
51+
// include this header ONLY from a TU that already links the full dgb_coin
52+
// codec (main_dgb.cpp + the codec conformance test), never a guard-weight TU
53+
// -- the #143 btclibs SCC trap, identical to embedded_tx_select.hpp.
54+
inline std::shared_ptr<EventDisposable>
55+
wire_mempool_ingest(::dgb::interfaces::Node& node, ::dgb::coin::Mempool& pool)
56+
{
57+
return node.new_tx.subscribe(
58+
[&pool](const ::dgb::coin::Transaction& tx)
59+
{
60+
pool.add_tx(::dgb::coin::MutableTransaction(tx));
61+
});
62+
}
63+
64+
} // namespace c2pool::dgb

src/impl/dgb/test/CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,20 @@ if (BUILD_TESTING AND GTest_FOUND)
6565
dgb_coin pool sharechain)
6666
gtest_add_tests(dgb_header_ingest_test "" AUTO)
6767

68+
# dgb_mempool_ingest_test: guards c2pool::dgb::wire_mempool_ingest, the
69+
# connector routing the embedded P2P `tx` relay (new_tx) into the Mempool
70+
# (tx analog of wire_header_ingest). Compiles the tx serialization codec, so
71+
# it links the full dgb_coin SCC set rather than the scrypt-only guard set.
72+
# MUST also be in BOTH build.yml --target allowlists (#143 NOT_BUILT trap).
73+
add_executable(dgb_mempool_ingest_test mempool_ingest_test.cpp)
74+
target_link_libraries(dgb_mempool_ingest_test PRIVATE
75+
GTest::gtest_main GTest::gtest
76+
core dgb dgb_stratum
77+
c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage
78+
dgb_coin pool sharechain
79+
nlohmann_json::nlohmann_json)
80+
gtest_add_tests(dgb_mempool_ingest_test "" AUTO)
81+
6882
# --- #82: won-block reconstructor BODY (as_block composition) ----------
6983
# Pins coin/reconstruct_won_block.hpp: the single composition the dispatch
7084
# handler injects as its WonBlockReconstructor (resolve_other_tx_hashes ->
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// ---------------------------------------------------------------------------
2+
// dgb_mempool_ingest_test -- pins c2pool::dgb::wire_mempool_ingest, the
3+
// connector that routes the embedded P2P `tx` relay (dgb::interfaces::Node::
4+
// new_tx) into the in-process Mempool so the embedded work template selects
5+
// from a live pool instead of an always-empty one.
6+
//
7+
// This is the tx analog of dgb_header_ingest_test (new_headers -> HeaderChain).
8+
// Disposition (txid compute, duplicate rejection, weight + byte cap) is
9+
// delegated to Mempool::add_tx, the insertion SSOT; the connector adds no
10+
// policy of its own, so the assertions here pin the WIRING (the feed reaches
11+
// the pool, an unwired node feeds nothing, the handle drives it) rather than
12+
// re-testing the mempool's internals.
13+
//
14+
// Links the full dgb_coin codec like dgb_embedded_tx_select_test (it compiles
15+
// the tx serialization). MUST be in BOTH build.yml --target allowlists (#143
16+
// NOT_BUILT trap).
17+
// ---------------------------------------------------------------------------
18+
19+
#include <cstdint>
20+
21+
#include <gtest/gtest.h>
22+
23+
#include <impl/dgb/coin/mempool_ingest.hpp>
24+
#include <impl/dgb/coin/node_interface.hpp>
25+
#include <impl/dgb/coin/mempool.hpp>
26+
#include <impl/dgb/coin/transaction.hpp>
27+
28+
using c2pool::dgb::wire_mempool_ingest;
29+
using dgb::coin::Mempool;
30+
using dgb::coin::MutableTransaction;
31+
using dgb::coin::Transaction;
32+
using dgb::coin::TxIn;
33+
using dgb::coin::TxOut;
34+
using dgb::coin::compute_txid;
35+
36+
namespace {
37+
38+
// A minimal, distinct tx tagged by its prevout index so each has a distinct
39+
// txid (mirrors the builder in dgb_embedded_tx_select_test).
40+
MutableTransaction tagged_tx(uint32_t index)
41+
{
42+
MutableTransaction tx;
43+
tx.version = 1;
44+
tx.locktime = 0;
45+
TxIn in;
46+
in.prevout.hash.SetNull();
47+
in.prevout.index = index;
48+
in.sequence = 0xffffffff;
49+
tx.vin.push_back(in);
50+
TxOut out;
51+
out.value = 50'000;
52+
tx.vout.push_back(out);
53+
return tx;
54+
}
55+
56+
} // namespace
57+
58+
// 1. A fresh, wired-but-quiet node leaves the pool empty.
59+
TEST(MempoolIngest, EmptyPoolBeforeRelay)
60+
{
61+
Mempool pool;
62+
dgb::interfaces::Node node;
63+
auto sub = wire_mempool_ingest(node, pool);
64+
65+
EXPECT_EQ(pool.size(), 0u);
66+
}
67+
68+
// 2. A tx announced on new_tx is ingested through add_tx: the pool grows and
69+
// the tx is queryable by its txid.
70+
TEST(MempoolIngest, AnnouncedTxIsIngested)
71+
{
72+
Mempool pool;
73+
dgb::interfaces::Node node;
74+
auto sub = wire_mempool_ingest(node, pool);
75+
76+
auto mt = tagged_tx(0);
77+
node.new_tx.happened(Transaction(mt));
78+
79+
EXPECT_EQ(pool.size(), 1u);
80+
EXPECT_TRUE(pool.contains(compute_txid(mt)));
81+
}
82+
83+
// 3. Multiple distinct relays accumulate in the pool.
84+
TEST(MempoolIngest, DistinctRelaysAccumulate)
85+
{
86+
Mempool pool;
87+
dgb::interfaces::Node node;
88+
auto sub = wire_mempool_ingest(node, pool);
89+
90+
auto a = tagged_tx(0);
91+
auto b = tagged_tx(1);
92+
node.new_tx.happened(Transaction(a));
93+
node.new_tx.happened(Transaction(b));
94+
95+
EXPECT_EQ(pool.size(), 2u);
96+
EXPECT_TRUE(pool.contains(compute_txid(a)));
97+
EXPECT_TRUE(pool.contains(compute_txid(b)));
98+
}
99+
100+
// 4. Disposition is delegated to add_tx, not the connector: a duplicate relay
101+
// of the same tx is rejected by the pool's SSOT, leaving size unchanged.
102+
TEST(MempoolIngest, DuplicateRelayIsRejected)
103+
{
104+
Mempool pool;
105+
dgb::interfaces::Node node;
106+
auto sub = wire_mempool_ingest(node, pool);
107+
108+
auto mt = tagged_tx(0);
109+
node.new_tx.happened(Transaction(mt));
110+
node.new_tx.happened(Transaction(mt)); // same txid
111+
112+
EXPECT_EQ(pool.size(), 1u);
113+
}
114+
115+
// 5. The connector is the driver: a node with NO ingest subscription drops the
116+
// relay -- the pool stays empty.
117+
TEST(MempoolIngest, UnwiredNodeIngestsNothing)
118+
{
119+
Mempool pool;
120+
dgb::interfaces::Node node; // deliberately NOT wired
121+
122+
node.new_tx.happened(Transaction(tagged_tx(0)));
123+
124+
EXPECT_EQ(pool.size(), 0u);
125+
}

0 commit comments

Comments
 (0)