Skip to content

Commit ef97094

Browse files
authored
Merge pull request #729 from frstrtr/fix/nmc-underfill-guard
nmc(underfill): port near-empty-template guard to NMC template_builder (+ KAT)
2 parents a99089f + 1cdf451 commit ef97094

4 files changed

Lines changed: 403 additions & 3 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ jobs:
123123
test_mweb_builder \
124124
test_address_resolution test_compute_share_target test_web_honesty_regression \
125125
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 \
126-
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 \
126+
dgb_gentx_coinbase_test dgb_connection_coinbase_test dgb_pplns_payout_split_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_underfill_guard_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 \
127127
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 dgb_aux_doge_submit_test dgb_aux_doge_embed_livewire_test dgb_aux_doge_dc_layout_verifier_test \
128128
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
129129
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_share_target_retarget_test dgb_share_bits_oracle_pin_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_auto_ratchet_sim_test dgb_binomial_conf_interval_test dgb_desired_version_tally_test dgb_min_protocol_ratchet_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 dgb_emergency_decay_saturation_test dgb_arith256_muldiv_kat_test v37_test \
@@ -280,7 +280,7 @@ jobs:
280280
test_mweb_builder \
281281
test_address_resolution test_compute_share_target test_web_honesty_regression \
282282
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 \
283-
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 \
283+
dgb_gentx_coinbase_test dgb_connection_coinbase_test dgb_pplns_payout_split_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_underfill_guard_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 \
284284
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 dgb_aux_doge_submit_test dgb_aux_doge_embed_livewire_test dgb_aux_doge_dc_layout_verifier_test \
285285
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
286286
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_share_target_retarget_test dgb_share_bits_oracle_pin_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_auto_ratchet_sim_test dgb_binomial_conf_interval_test dgb_desired_version_tally_test dgb_min_protocol_ratchet_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 dgb_emergency_decay_saturation_test dgb_arith256_muldiv_kat_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \

src/impl/nmc/coin/template_builder.hpp

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,40 @@ inline std::string bits_to_hex(uint32_t bits) {
131131
return std::string(buf);
132132
}
133133

134+
// Underfill guard (v36 cutover deploy path)
135+
//
136+
// Port of the LTC/DOGE template-builder guard (src/impl/ltc/coin/
137+
// template_builder.hpp, src/impl/doge/coin/template_builder.hpp) to the NMC
138+
// embedded template path, in the DASH free-predicate form
139+
// (src/impl/dash/coin/embedded_gbt.hpp) — same shape as the BTC port
140+
// (src/impl/btc/coin/template_builder.hpp). Detects the "near-empty template
141+
// on a non-empty mempool" regression: the tx selector returns almost no
142+
// transactions even though the local mempool holds a substantial fee-paying
143+
// backlog. c2pool-side template-fill safety net — NOT the byte-parity KAT
144+
// axis; thresholds are the v36-native shared structure (standardize
145+
// cross-coin) pinned to the legacy p2pool near-empty floor (~50 kB),
146+
// identical to LTC/DOGE/DASH/BTC. NMC is BTC's merge-mined aux child, so a
147+
// near-empty aux block does not waste parent PoW (severity LOW) — this port
148+
// completes the all-coin underfill matrix.
149+
inline constexpr uint64_t UNDERFILL_MIN_FILL_BYTES = 50'000ull; // < this = near-empty block
150+
inline constexpr uint64_t UNDERFILL_BACKLOG_SLACK = 50'000ull; // unselected fee-paying material that should have filled it
151+
152+
/// Pure trip predicate — the exact boolean the LTC/DOGE guards evaluate.
153+
/// Factored out so the KAT can pin it without a log scraper:
154+
/// near_empty : template packed fewer bytes than the near-empty floor
155+
/// has_backlog : the mempool holds fee-paying material (known fees > 0)
156+
/// well beyond what was selected (> selected + slack)
157+
/// Genuinely empty (or fee-unknown-only) mempools never trip.
158+
inline bool underfill_guard_trips(uint64_t selected_bytes,
159+
uint64_t mempool_bytes,
160+
uint64_t mempool_known_fees)
161+
{
162+
const bool near_empty = selected_bytes < UNDERFILL_MIN_FILL_BYTES;
163+
const bool has_backlog = mempool_known_fees > 0
164+
&& mempool_bytes > selected_bytes + UNDERFILL_BACKLOG_SLACK;
165+
return near_empty && has_backlog;
166+
}
167+
134168
// TemplateBuilder
135169

136170
/// Builds an NMC block template from a validated HeaderChain and Mempool.
@@ -165,10 +199,16 @@ class TemplateBuilder {
165199

166200
/// Build a WorkData template from the current chain tip + mempool.
167201
/// Returns std::nullopt if the chain has no tip yet (not synced to genesis).
202+
/// underfill_tripped: optional underfill-guard observation seam. Defaults
203+
/// to nullptr so every existing caller is byte-for-byte unchanged
204+
/// (SAFE-ADDITIVE); the guard KAT passes a bool to pin the wiring without
205+
/// a log scraper. The guard itself is log-only (WARNING), exactly like
206+
/// LTC/DOGE/BTC — it never alters the template.
168207
static std::optional<rpc::WorkData> build_template(
169208
const HeaderChain& chain,
170209
const Mempool& pool,
171-
bool is_testnet = false)
210+
bool is_testnet = false,
211+
bool* underfill_tripped = nullptr)
172212
{
173213
(void)is_testnet; // reserved for future per-network rules
174214
auto t0 = std::chrono::steady_clock::now();
@@ -224,9 +264,11 @@ class TemplateBuilder {
224264
std::vector<Transaction> tx_objects;
225265
std::vector<uint256> tx_hashes;
226266

267+
uint64_t selected_bytes = 0; // wire bytes packed into this template (underfill guard)
227268
for (const auto& stx : selected_txs) {
228269
uint256 txid = compute_txid(stx.tx);
229270
auto packed = pack(TX_WITH_WITNESS(stx.tx));
271+
selected_bytes += packed.get_span().size();
230272
std::string hex_data = HexStr(packed.get_span());
231273
// wtxid = SHA256d of witness serialization (for witness merkle tree)
232274
uint256 wtxid = Hash(packed.get_span());
@@ -245,6 +287,32 @@ class TemplateBuilder {
245287
tx_hashes.push_back(txid);
246288
}
247289

290+
// Underfill guard
291+
//
292+
// Do not silently treat a near-empty template as healthy when the
293+
// mempool held fee-paying backlog that should have filled it. We cannot
294+
// fabricate transactions, so we surface loudly (WARNING) for
295+
// contabo-prod-watch / the operator rather than shipping a false-empty
296+
// block as normal. Genuinely empty mempools never trip this. Mirrors
297+
// the LTC/DOGE/BTC TemplateBuilder guard; additive only — the GBT JSON
298+
// below is untouched either way.
299+
{
300+
const uint64_t mempool_bytes = static_cast<uint64_t>(pool.byte_size());
301+
const uint64_t mempool_fees = pool.total_fees();
302+
const bool tripped = underfill_guard_trips(selected_bytes,
303+
mempool_bytes,
304+
mempool_fees);
305+
if (underfill_tripped) *underfill_tripped = tripped;
306+
if (tripped) {
307+
LOG_WARNING << "[EMB-NMC] TemplateBuilder UNDERFILL: selected "
308+
<< selected_txs.size() << " tx / " << selected_bytes
309+
<< "B into template while mempool holds " << pool.size()
310+
<< " tx / " << mempool_bytes << "B (" << mempool_fees
311+
<< " sat fees) — near-empty aux block on a non-empty "
312+
<< "mempool; template-fill regression, gates cutover.";
313+
}
314+
}
315+
248316
// Build GBT-compatible JSON
249317
nlohmann::json data;
250318
data["version"] = static_cast<int>(block_version);

src/impl/nmc/test/CMakeLists.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ if (BUILD_TESTING AND GTest_FOUND)
2727
# P1 PC: embedded template builder KATs. Header-only builder
2828
# (template_builder.hpp / rpc_data.hpp); links the same SCC the sibling
2929
# merkle test does (core + nmc_coin for the out-of-line transaction ctor).
30+
# The underfill guard KAT that used to be compiled into this target now
31+
# lives in its own nmc_underfill_guard_test target below (btc/dash/dgb symmetry).
3032
add_executable(nmc_template_builder_test nmc_template_builder_test.cpp)
3133
target_link_libraries(nmc_template_builder_test PRIVATE
3234
GTest::gtest_main GTest::gtest
@@ -35,6 +37,24 @@ if (BUILD_TESTING AND GTest_FOUND)
3537
c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage)
3638
target_link_libraries(nmc_template_builder_test PRIVATE nmc_coin)
3739

40+
# nmc_underfill_guard_test: port of the LTC/DOGE "near-empty template on a
41+
# non-empty mempool" template-builder guard to the NMC embedded template
42+
# path (coin/template_builder.hpp), matching the BTC/DGB/DASH ports. Predicate
43+
# KATs at the pinned 50 kB floor/slack + build_template wiring via the
44+
# SAFE-ADDITIVE underfill_tripped seam (log-only guard; GBT projection asserted
45+
# unchanged). Standalone target mirroring btc/dash/dgb *_underfill_guard_test
46+
# for symmetry; same SCC as the sibling template_builder test (core + nmc_coin
47+
# for the out-of-line transaction ctor). MUST be listed in the build.yml
48+
# --target allowlist below (a registered target absent from that allowlist
49+
# reds master as a NOT_BUILT sentinel: the #724/#728 lesson).
50+
add_executable(nmc_underfill_guard_test nmc_underfill_guard_test.cpp)
51+
target_link_libraries(nmc_underfill_guard_test PRIVATE
52+
GTest::gtest_main GTest::gtest
53+
core nlohmann_json::nlohmann_json)
54+
target_link_libraries(nmc_underfill_guard_test PRIVATE
55+
c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage)
56+
target_link_libraries(nmc_underfill_guard_test PRIVATE nmc_coin)
57+
3858
# P1 card 237: wire-vector KAT. Round-trips the canonical CAuxPow layout
3959
# against a REAL Namecoin mainnet block (757000). Same SCC as the sibling
4060
# tests (core + nmc_coin for the out-of-line MutableTransaction ctor).
@@ -110,6 +130,7 @@ if (BUILD_TESTING AND GTest_FOUND)
110130
include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR})
111131
gtest_add_tests(nmc_auxpow_merkle_test "" AUTO)
112132
gtest_add_tests(nmc_template_builder_test "" AUTO)
133+
gtest_add_tests(nmc_underfill_guard_test "" AUTO)
113134
gtest_add_tests(nmc_auxpow_wire_test "" AUTO)
114135
gtest_add_tests(nmc_reconstruct_won_block_test "" AUTO)
115136
gtest_add_tests(nmc_mempool_name_test "" AUTO)

0 commit comments

Comments
 (0)