Skip to content

Commit 9fc913e

Browse files
committed
bch(g3a): populate embedded mempool from BCHN GBT for populated won blocks
The embedded daemon built coinbase-only templates (nTx=1) on regtest and any RPC-primary deploy: loose BCHN-mempool txs never reach the embedded P2P relay, so build_template selected from an empty m_pool. Two fenced fixes: 1. EmbeddedDaemon::sync_mempool_from_rpc() (run()-startup): feed the BCHN GBT tx set -- the same set NodeRPC::getwork() consumes -- into m_pool. No-op when no external RPC is bound; regtest skips the mainnet/testnet anchor (is_regtest stored as a member). One-shot known limitation; per-tip refresh is a tracked non-blocking follow-up. 2. Mempool::add_tx_with_known_fee() + GBT per-tx fee passthrough (fee_known=true) so get_sorted_txs_with_fees() selects GBT txs into the template and coinbasevalue stays accurate. Proven on live regtest BCHN: won block @h326 nTx=7 (6 mempool + coinbase), 1539-byte relayed body, both broadcaster legs fire (p2p sent, rpc ok, tip 325->326). Fence: src/impl/bch only; no p2pool-merged-v36 surface; external-RPC fallback preserved.
1 parent 55590c0 commit 9fc913e

2 files changed

Lines changed: 91 additions & 1 deletion

File tree

src/impl/bch/coin/embedded_daemon.hpp

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ class EmbeddedDaemon {
9191
m_embedded(m_chain, m_pool, config->m_testnet),
9292
m_node(context, config),
9393
m_abla(config->m_testnet, anchor_height, m_chain),
94-
m_connector(m_chain, m_pool) {}
94+
m_connector(m_chain, m_pool) { m_regtest = is_regtest; }
9595

9696
EmbeddedDaemon(const EmbeddedDaemon&) = delete;
9797
EmbeddedDaemon& operator=(const EmbeddedDaemon&) = delete;
@@ -107,12 +107,58 @@ class EmbeddedDaemon {
107107
wire_chain_ingest(); // new_headers --> HeaderChain (advances synced height)
108108
pin_cold_start_anchor(); // operator-APPROVED VM300 anchor (decisions@ 2026-06-18); floor-equivalent
109109
const bool p2p_up = maybe_start_p2p(); // arm won-block P2P relay leg + connector re-request sink (gated on configured peer)
110+
sync_mempool_from_rpc(); // RPC-fallback: BCHN loose-mempool txs never reach embedded P2P -> fill m_pool so populated templates (nTx>1) are built
110111
LOG_INFO << "[EMB-BCH] embedded daemon up: embedded-primary work source,"
111112
<< " external BCHN-RPC fallback retained, ABLA loop closed"
112113
<< " (cold-start anchor pinned @" << BchnAnchorRecord::height << "),"
113114
<< " P2P relay leg " << (p2p_up ? "LIVE" : "OFF (no peer configured -> RPC-only)") << ".";
114115
}
115116

117+
/// Populate the embedded mempool from the external BCHN GBT (RPC-fallback
118+
/// path). On regtest -- and any RPC-primary deploy -- loose transactions
119+
/// live in BCHN's mempool and never reach the embedded P2P relay, so the
120+
/// template builder selects from an empty m_pool and every won block is
121+
/// coinbase-only (nTx=1). BCHN GBT already returns the CTOR-ordered,
122+
/// fee-annotated, consensus-valid tx set -- the SAME set NodeRPC::getwork()
123+
/// consumes for the external-RPC work path -- so we hand those to m_pool and
124+
/// build_template selects them unchanged. No-op when no external RPC is bound
125+
/// (a pure embedded-P2P deploy keeps its own relayed mempool).
126+
/// KNOWN LIMITATION: one-shot at run()-startup; per-tip/periodic refresh is a
127+
/// tracked production-standup follow-up (fenced, non-blocking for G3a).
128+
/// p2pool-merged-v36 surface: NONE -- local mempool population only.
129+
std::size_t sync_mempool_from_rpc() {
130+
auto* r = m_node.rpc();
131+
if (!r) return 0; // pure embedded-P2P deploy -> no RPC fill
132+
std::size_t added = 0;
133+
try {
134+
auto wd = r->getwork(); // GBT-derived; m_txs unpacked, witness-free
135+
const auto& jtx = (wd.m_data.is_object() && wd.m_data.contains("transactions")
136+
&& wd.m_data["transactions"].is_array())
137+
? wd.m_data["transactions"]
138+
: nlohmann::json::array();
139+
for (std::size_t i = 0; i < wd.m_txs.size(); ++i) {
140+
MutableTransaction mtx(wd.m_txs[i]);
141+
bool ok = false;
142+
if (i < jtx.size() && jtx[i].is_object()
143+
&& jtx[i].contains("fee") && jtx[i]["fee"].is_number()) {
144+
// Authoritative per-tx fee from BCHN GBT -> fee_known=true so
145+
// get_sorted_txs_with_fees() selects it into the template.
146+
ok = m_pool.add_tx_with_known_fee(
147+
mtx, static_cast<uint64_t>(jtx[i]["fee"].template get<int64_t>()));
148+
} else {
149+
ok = m_pool.add_tx(mtx); // no fee field -> UTXO/feeless fallback
150+
}
151+
if (ok) ++added;
152+
}
153+
LOG_INFO << "[EMB-BCH] mempool RPC-sync: +" << added
154+
<< " txs from BCHN GBT (m_pool size=" << m_pool.size() << ")";
155+
} catch (const std::exception& e) {
156+
LOG_WARNING << "[EMB-BCH] mempool RPC-sync skipped (RPC fallback): "
157+
<< e.what();
158+
}
159+
return added;
160+
}
161+
116162
/// READ-ONLY IBD evidence harness entry (drives the --ibd run-loop in
117163
/// main_bch.cpp). Brings up ONLY the network-free chain + the P2P front-end
118164
/// pointed at a single BCHN peer (VM300 bchn-bch .110:8333), with the
@@ -369,6 +415,13 @@ class EmbeddedDaemon {
369415
/// p2pool-merged-v36 surface (ABLA is BCH embedded-internal).
370416
void pin_cold_start_anchor() {
371417
using Rec = BchnAnchorRecord;
418+
if (m_regtest) {
419+
// Regtest has no mainnet/testnet BCHN anchor; the @955700 record is
420+
// meaningless here. ABLA stays at its floor anchor; the chain syncs
421+
// from the regtest genesis. Skipping the pin keeps regtest correct.
422+
LOG_INFO << "[EMB-BCH] cold-start anchor SKIPPED (regtest: floor ABLA, genesis-rooted chain).";
423+
return;
424+
}
372425
apply_bchn_anchor(Rec::height, Rec::state(m_config->m_testnet));
373426
const uint64_t pinned = m_abla.tracker().budget_for_tip(Rec::height);
374427
if (Rec::is_floor())
@@ -496,6 +549,7 @@ class EmbeddedDaemon {
496549
}
497550

498551
private:
552+
bool m_regtest = false; // regtest 3rd-net: skip mainnet/testnet anchor + drive RPC mempool sync
499553
config_t* m_config; // not owned (binary entrypoint owns it)
500554
HeaderChain m_chain; // before m_embedded + m_abla: their refs bind here
501555
Mempool m_pool; // before m_embedded

src/impl/bch/coin/mempool.hpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,42 @@ class Mempool {
172172
return true;
173173
}
174174

175+
/// Add a transaction with a KNOWN fee supplied by an authoritative source
176+
/// (BCHN GBT transactions[].fee). Used by the embedded-daemon RPC mempool
177+
/// sync: BCHN already validated the tx and computed its exact fee, so we set
178+
/// fee_known=true and index it for template selection. Without this the tx
179+
/// lands with fee_known=false and get_sorted_txs_with_fees() excludes it
180+
/// (empty template -> coinbase-only won blocks, nTx=1). Fee accuracy matches
181+
/// p2pool, which sources fees from the same daemon GBT.
182+
bool add_tx_with_known_fee(const MutableTransaction& tx, uint64_t fee) {
183+
uint256 txid = compute_txid(tx);
184+
std::lock_guard<std::mutex> lock(m_mutex);
185+
if (m_pool.count(txid))
186+
return false;
187+
MempoolEntry entry;
188+
entry.tx = tx;
189+
entry.txid = txid;
190+
entry.byte_size = compute_tx_byte_size(tx);
191+
entry.time_added = std::time(nullptr);
192+
entry.fee = fee;
193+
entry.fee_known = true;
194+
int evicted = 0;
195+
while (m_total_bytes + entry.byte_size > m_max_bytes && !m_time_index.empty()) {
196+
auto oldest = m_time_index.begin();
197+
evict_one_locked(oldest->second);
198+
++evicted;
199+
}
200+
m_pool[txid] = std::move(entry);
201+
auto& stored = m_pool[txid];
202+
m_time_index.emplace(stored.time_added, txid);
203+
m_total_bytes += stored.byte_size;
204+
for (const auto& vin : stored.tx.vin) {
205+
m_spent_outputs[std::make_pair(vin.prevout.hash, vin.prevout.index)] = txid;
206+
}
207+
m_feerate_index.emplace(stored.feerate(), txid);
208+
return true;
209+
}
210+
175211
/// Remove a single transaction by txid.
176212
void remove_tx(const uint256& txid) {
177213
std::lock_guard<std::mutex> lock(m_mutex);

0 commit comments

Comments
 (0)