Skip to content

Commit d58e38f

Browse files
committed
Fix UTXO maturity gate: block-count-based + undo data pruning
Replace gap-based maturity check (chain_h - utxo_h) with block counter (blocks_connected >= coinbase_maturity). The gap check passed incorrectly when UTXO tip matched chain tip but had holes in block history. Add undo data pruning matching daemon MIN_BLOCKS_TO_KEEP: LTC: 288 blocks (litecoin/src/validation.h) DOGE: 1440 blocks (dogecoin/src/validation.h) Constants reference daemon source: LTC coinbase_maturity=100, pegout_maturity=6 (litecoin/src/consensus/consensus.h) DOGE coinbase_maturity=240 post-DigiShield (dogecoin/src/chainparams.cpp digishieldConsensus)
1 parent ef66417 commit d58e38f

3 files changed

Lines changed: 70 additions & 22 deletions

File tree

src/c2pool/c2pool_refactored.cpp

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1384,21 +1384,20 @@ int main(int argc, char* argv[]) {
13841384
mweb_tracker = std::make_unique<ltc::coin::MWEBTracker>();
13851385
embedded_node = std::make_unique<ltc::coin::EmbeddedCoinNode>(
13861386
*embedded_chain, *embedded_pool, settings->m_testnet, mweb_tracker.get());
1387-
// Gate mining until UTXO has coinbase maturity depth (100 blocks).
1387+
// Gate mining until UTXO has coinbase maturity depth (100 blocks for LTC).
13881388
// Without this, block templates may include TXs spending immature coinbase outputs.
1389-
if (ltc_utxo_db) {
1390-
auto* db_ptr = ltc_utxo_db.get();
1391-
auto* chain_ptr = embedded_chain.get();
1389+
// Reference: litecoin/src/consensus/consensus.h COINBASE_MATURITY = 100
1390+
if (ltc_utxo_cache) {
1391+
auto* cache_ptr = ltc_utxo_cache.get();
13921392
constexpr uint32_t LTC_MATURITY = core::coin::LTC_LIMITS.coinbase_maturity;
1393-
embedded_node->set_utxo_ready_fn([db_ptr, chain_ptr, LTC_MATURITY]() {
1394-
auto chain_h = chain_ptr->height();
1395-
auto utxo_h = db_ptr->get_best_height();
1396-
bool ready = utxo_h > 0 && chain_h > 0 && (chain_h - utxo_h) < LTC_MATURITY;
1393+
embedded_node->set_utxo_ready_fn([cache_ptr, LTC_MATURITY]() {
1394+
auto connected = cache_ptr->blocks_connected();
1395+
bool ready = connected >= LTC_MATURITY;
13971396
if (!ready) {
13981397
static int s_log_ctr = 0;
13991398
if (s_log_ctr++ % 20 == 0)
1400-
LOG_INFO << "[EMB-LTC] UTXO maturity gate: chain=" << chain_h
1401-
<< " utxo=" << utxo_h << " need gap<" << LTC_MATURITY;
1399+
LOG_INFO << "[EMB-LTC] UTXO maturity gate: blocks_connected="
1400+
<< connected << " need>=" << LTC_MATURITY;
14021401
}
14031402
return ready;
14041403
});
@@ -1749,6 +1748,10 @@ int main(int argc, char* argv[]) {
17491748
utxo_db->put_block_undo(height, undo);
17501749
utxo->flush(block_hash, height);
17511750

1751+
// Prune old undo data matching litecoind MIN_BLOCKS_TO_KEEP = 288
1752+
// Reference: litecoin/src/validation.h MIN_BLOCKS_TO_KEEP
1753+
utxo->prune_undo(height, core::coin::LTC_MIN_BLOCKS_TO_KEEP);
1754+
17521755
static int utxo_log = 0;
17531756
if (utxo_log++ % 10 == 0) {
17541757
LOG_INFO << "[EMB-LTC] UTXO: connected block " << height
@@ -3537,21 +3540,20 @@ int main(int argc, char* argv[]) {
35373540
{
35383541
auto backend = std::make_unique<doge::coin::AuxChainEmbedded>(
35393542
*doge_chain, *doge_pool, *doge_params_ptr, cfg);
3540-
// Gate mining until UTXO has coinbase maturity depth (240 blocks).
3543+
// Gate mining until UTXO has coinbase maturity depth (240 blocks for DOGE).
35413544
// Without this, block templates may include TXs spending immature coinbase outputs.
3542-
if (doge_utxo_db) {
3543-
auto* db_ptr = doge_utxo_db.get();
3544-
auto* chain_ptr = doge_chain.get();
3545+
// Reference: dogecoin/src/chainparams.cpp digishieldConsensus.nCoinbaseMaturity = 240
3546+
if (doge_utxo_cache) {
3547+
auto* cache_ptr = doge_utxo_cache.get();
35453548
constexpr uint32_t DOGE_MATURITY = core::coin::DOGE_LIMITS.coinbase_maturity;
3546-
backend->embedded_node().set_utxo_ready_fn([db_ptr, chain_ptr, DOGE_MATURITY]() {
3547-
auto chain_h = chain_ptr->height();
3548-
auto utxo_h = db_ptr->get_best_height();
3549-
bool ready = utxo_h > 0 && chain_h > 0 && (chain_h - utxo_h) < DOGE_MATURITY;
3549+
backend->embedded_node().set_utxo_ready_fn([cache_ptr, DOGE_MATURITY]() {
3550+
auto connected = cache_ptr->blocks_connected();
3551+
bool ready = connected >= DOGE_MATURITY;
35503552
if (!ready) {
35513553
static int s_log_ctr = 0;
35523554
if (s_log_ctr++ % 20 == 0)
3553-
LOG_INFO << "[EMB-DOGE] UTXO maturity gate: chain=" << chain_h
3554-
<< " utxo=" << utxo_h << " need gap<" << DOGE_MATURITY;
3555+
LOG_INFO << "[EMB-DOGE] UTXO maturity gate: blocks_connected="
3556+
<< connected << " need>=" << DOGE_MATURITY;
35553557
}
35563558
return ready;
35573559
});
@@ -3768,6 +3770,11 @@ int main(int argc, char* argv[]) {
37683770
auto undo = utxo->connect_block(block, height, txid_fn);
37693771
utxo_db->put_block_undo(height, undo);
37703772
utxo->flush(block_hash, height);
3773+
3774+
// Prune old undo data matching dogecoind MIN_BLOCKS_TO_KEEP = 1440
3775+
// Reference: dogecoin/src/validation.h MIN_BLOCKS_TO_KEEP
3776+
utxo->prune_undo(height, core::coin::DOGE_MIN_BLOCKS_TO_KEEP);
3777+
37713778
LOG_INFO << "[EMB-DOGE] UTXO: connected block " << height
37723779
<< " hash=" << block_hash.GetHex().substr(0, 16)
37733780
<< " txs=" << block.m_txs.size()

src/core/coin/utxo.hpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,21 @@ struct ChainLimits {
3535
};
3636

3737
/// LTC: 84M * 1e8 sat, 100-block coinbase maturity, 6-block pegout maturity
38+
/// Reference: litecoin/src/consensus/consensus.h COINBASE_MATURITY, PEGOUT_MATURITY
3839
static constexpr ChainLimits LTC_LIMITS = {8'400'000'000'000'000LL, 100, 6};
3940

4041
/// DOGE: ~10B * 1e8 sat (infinite inflation, but max single value bounded),
41-
/// 240-block coinbase maturity, no pegout (no MWEB)
42+
/// 240-block coinbase maturity (post-DigiShield), no pegout (no MWEB)
43+
/// Reference: dogecoin/src/chainparams.cpp digishieldConsensus.nCoinbaseMaturity
4244
static constexpr ChainLimits DOGE_LIMITS = {1'000'000'000'000'000'000LL, 240, 0};
4345

46+
/// Minimum blocks of undo data to keep for reorg safety + pruning.
47+
/// Matches the MIN_BLOCKS_TO_KEEP constant in each daemon's validation.h.
48+
/// Reference: litecoin/src/validation.h MIN_BLOCKS_TO_KEEP = 288
49+
/// Reference: dogecoin/src/validation.h MIN_BLOCKS_TO_KEEP = 1440
50+
static constexpr uint32_t LTC_MIN_BLOCKS_TO_KEEP = 288;
51+
static constexpr uint32_t DOGE_MIN_BLOCKS_TO_KEEP = 1440;
52+
4453
/// Check if a value is within the valid money range for a chain.
4554
/// Reference: LTC/DOGE amount.h MoneyRange()
4655
inline bool money_range(int64_t v, const ChainLimits& lim) {

src/core/coin/utxo_view_cache.hpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ namespace coin {
2828

2929
class UTXOViewCache {
3030
public:
31-
explicit UTXOViewCache(UTXOViewDB* base) : m_base(base) {}
31+
explicit UTXOViewCache(UTXOViewDB* base) : m_base(base) {
32+
// Initialize block counter from DB state (survives restarts)
33+
if (m_base && m_base->get_best_height() > 0)
34+
m_blocks_connected = m_base->get_best_height();
35+
}
3236

3337
// ── Read operations ─────────────────────────────────────────────────
3438

@@ -178,6 +182,7 @@ class UTXOViewCache {
178182
}
179183
}
180184

185+
++m_blocks_connected;
181186
return undo;
182187
}
183188

@@ -272,8 +277,35 @@ class UTXOViewCache {
272277
return m_base ? m_base->get_best_height() : 0;
273278
}
274279

280+
/// Number of blocks connected since startup (or loaded from DB on restart).
281+
/// Used for coinbase maturity gate — mining blocked until this reaches
282+
/// the chain's COINBASE_MATURITY depth.
283+
uint32_t blocks_connected() const { return m_blocks_connected; }
284+
285+
/// Prune undo data older than (tip_height - keep_depth).
286+
/// Reference: litecoind validation.cpp PruneBlockFilesManual()
287+
/// LTC: keep_depth = MIN_BLOCKS_TO_KEEP = 288 (litecoin/src/validation.h)
288+
/// DOGE: keep_depth = MIN_BLOCKS_TO_KEEP = 1440 (dogecoin/src/validation.h)
289+
uint32_t prune_undo(uint32_t tip_height, uint32_t keep_depth) {
290+
if (!m_base || tip_height <= keep_depth) return 0;
291+
uint32_t prune_below = tip_height - keep_depth;
292+
uint32_t pruned = 0;
293+
for (uint32_t h = m_oldest_undo_height; h < prune_below; ++h) {
294+
if (m_base->remove_block_undo(h))
295+
++pruned;
296+
}
297+
if (pruned > 0) {
298+
m_oldest_undo_height = prune_below;
299+
LOG_INFO << "[UTXO] Pruned " << pruned << " undo records below height "
300+
<< prune_below << " (keep_depth=" << keep_depth << ")";
301+
}
302+
return pruned;
303+
}
304+
275305
private:
276306
UTXOViewDB* m_base;
307+
uint32_t m_blocks_connected{0};
308+
uint32_t m_oldest_undo_height{0};
277309
// Cache: outpoint → optional<Coin>
278310
// - has_value() && !is_spent(): coin exists
279311
// - nullopt: coin was spent (erased)

0 commit comments

Comments
 (0)