From d6972a0ca2269441c7a1125d528fc577c242be7d Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Sun, 9 Aug 2026 03:18:25 +0000 Subject: payout: say what actually failed instead of blaming transaction size SendMoney's "oversized transaction that requires a transaction fee of 0.00" is not an oversized transaction and there is no fee. That branch is taken when nValue + nFeeRequired > GetBalance() -- an empty spendable balance -- and the fee reads 0.00 because CreateTransaction failed before setting one. 3,267 of these in debug.log sent me looking for a transaction-size problem that does not exist. Report what is short, by how much, and against what balance. Name COINBASE_SPENDABLE_DEPTH in main.h and derive GetBlocksToMaturity from it, because the distinction it captures is genuinely surprising: CMerkleTx::GetCredit values a mined coinbase at zero until COINBASE_MATURITY+20 deep, not COINBASE_MATURITY. I first changed the payout gate to that deeper value, on the theory that each payout was firing 20 blocks before its own coinbase could be spent. Measuring it refuted that: across 934 payments, 96% succeeded at depth 100, the moment they were queued, which is only possible because a payout is funded from whatever coin is already mature rather than from its own block. The gate stays at COINBASE_MATURITY, with a comment recording the measurement so the next person does not re-make the same change. The real shortfall is structural and not addressed here: the pool pays out ~99% of each block's subsidy while only matured coinbases are spendable, so it runs with almost no buffer, and any orphaned block it queued a payout for drains that buffer permanently. The 24h retry loop absorbs this -- 0 payouts forfeited -- but the buffer is what needs fixing. (cherry picked from commit e28df01f90d621b0cd818f28d70a20b8ab5d1a8d) --- src/main.cpp | 15 +++++++++++++-- src/main.h | 6 ++++++ src/rpc.cpp | 5 +++++ 3 files changed, 24 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index f84f209..382a008 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1513,7 +1513,7 @@ int CMerkleTx::GetBlocksToMaturity() const { if (!IsCoinBase()) return 0; - return max(0, (COINBASE_MATURITY+20) - GetDepthInMainChain()); + return max(0, COINBASE_SPENDABLE_DEPTH - GetDepthInMainChain()); } @@ -4812,7 +4812,18 @@ bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew) { string strError; if (nValue + nFeeRequired > GetBalance()) - strError = strprintf("Error: This is an oversized transaction that requires a transaction fee of %s ", FormatMoney(nFeeRequired).c_str()); + // Not an oversized transaction -- this branch is simply "the + // spendable balance is too small". Saying otherwise (with a fee + // of 0.00, because CreateTransaction failed before setting one) + // sends anyone reading the log after a failed payout hunting a + // transaction-size problem that does not exist. + strError = strprintf("Error: insufficient spendable balance:" + " need %s (plus fee %s) but only %s is spendable" + " -- mined coin counts as zero until it is %d deep", + FormatMoney(nValue).c_str(), + FormatMoney(nFeeRequired).c_str(), + FormatMoney(GetBalance()).c_str(), + COINBASE_SPENDABLE_DEPTH); else strError = "Error: Transaction creation failed "; return error("SendMoney() : %s\n", strError.c_str()); diff --git a/src/main.h b/src/main.h index 22be3dc..6b1ff0a 100644 --- a/src/main.h +++ b/src/main.h @@ -41,6 +41,12 @@ static const int64 MAX_MONEY = 21000000 * COIN; inline bool MoneyRange(int64 nValue) { return (nValue >= 0 && nValue <= MAX_MONEY); } static const int64 CENT = 1000000; static const int COINBASE_MATURITY = 100; +// Depth at which a coinbase we mined is actually usable. CMerkleTx::GetCredit +// values a coinbase at zero until GetBlocksToMaturity() reaches 0, which is +// this depth -- not COINBASE_MATURITY. Anything that plans to *spend* mined +// coin must gate on this, or it will try to pay out of a balance the wallet +// still reports as zero. Both rules live here so they cannot drift apart. +static const int COINBASE_SPENDABLE_DEPTH = COINBASE_MATURITY + 20; // Bitflash difficulty floor. The PoW is RandomX (memory-hard), ~1000x slower // per hash than SHA-256d, so the target is much easier than Bitcoin's to let a diff --git a/src/rpc.cpp b/src/rpc.cpp index 21a8d77..24b1c88 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -322,6 +322,11 @@ static void QueuePayouts(int blockHeight, int64 minerReward = (int64)((double)nSubsidy * (1.0 - feeFrac)); PendingPayout pp; + // Deliberately COINBASE_MATURITY, not COINBASE_SPENDABLE_DEPTH: a payout is + // funded from whatever coin is already mature, not from its own block's + // coinbase, so waiting the extra 20 blocks delays every payment without + // making one more likely to succeed. Measured over 934 payments: 96% went + // through at depth 100, the moment they were queued. pp.matureAtHeight = blockHeight + COINBASE_MATURITY; int skipped = 0; -- cgit v1.2.3