From 8102c90a4bce0effa39020c89722f379a73b292b Mon Sep 17 00:00:00 2001 From: JohnnyLawDGB Date: Fri, 6 Mar 2026 06:07:02 -0600 Subject: [PATCH] fix: add fee retry loop to redemption flow (Bug #9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Redemption transactions fail with "Insufficient fee inputs for calculated fee" because the initial 0.1 DGB fee estimate is too low for redemptions with multiple inputs (collateral + DD UTXOs + fee UTXOs). The txbuilder calculates the actual fee after constructing the transaction, but if the pre-selected fee UTXOs don't cover it, the build fails with no recovery. Add a retry loop (max 3 attempts) to both redemption code paths: - src/rpc/digidollar.cpp (wallet RPC handler, position_id-based) - src/wallet/digidollarwallet.cpp (direct wallet call, timelock-based) On "Insufficient fee inputs" failure, re-select fee UTXOs using result.totalFees + 20% margin, then retry. Non-fee errors return immediately without retry. Also fix the timelock-based RedeemDigiDollar to populate params.feeAmounts (was passing nullptr to SelectFeeCoins), matching the transfer flow pattern. Tested on testnet19 — 3 consecutive redemptions all succeed on attempt 2 after automatic fee retry: Redemption 1: 10,000 cents, 240,731 tDGB unlocked (fee: 0.280 tDGB) Redemption 2: 10,000 cents, 240,731 tDGB unlocked (fee: 0.215 tDGB) Redemption 3: 10,001 cents, 240,755 tDGB unlocked (fee: 0.248 tDGB) Debug log proof (attempt 1 fails, retry succeeds): BuildRedemptionTransaction attempt 1: success=0, error='Insufficient fee inputs' Fee shortfall on attempt 1, retrying with 25872000 sats (actual 21560000 + 20%) BuildRedemptionTransaction attempt 2: success=1 Co-Authored-By: Claude Opus 4.6 --- src/rpc/digidollar.cpp | 26 ++++++++++++++++++++++-- src/wallet/digidollarwallet.cpp | 35 ++++++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/rpc/digidollar.cpp b/src/rpc/digidollar.cpp index ceecaf82cc..dc4b3bf0ee 100644 --- a/src/rpc/digidollar.cpp +++ b/src/rpc/digidollar.cpp @@ -1397,10 +1397,32 @@ RPCHelpMan redeemdigidollar() LogPrintf("DigiDollar: Selected %d sats in fees from %d UTXOs for redemption\n", selectedFeeTotal, redeemParams.feeUtxos.size()); - DigiDollar::TxBuilderResult redeemResult = redeemBuilder.BuildRedemptionTransaction(redeemParams); + DigiDollar::TxBuilderResult redeemResult; + for (int attempt = 0; attempt < 3; ++attempt) { + redeemResult = redeemBuilder.BuildRedemptionTransaction(redeemParams); + LogPrintf("DigiDollar: BuildRedemptionTransaction attempt %d returned success=%d, error='%s'\n", + attempt + 1, redeemResult.success, redeemResult.error.c_str()); + if (redeemResult.success) break; + + // Only retry on fee shortfall errors + if (redeemResult.error.find("Insufficient fee inputs") == std::string::npos) { + throw JSONRPCError(RPC_WALLET_ERROR, "Failed to build redemption transaction: " + redeemResult.error); + } + // Retry with actual fee + 20% margin + CAmount retryFee = redeemResult.totalFees + (redeemResult.totalFees * 20 / 100); + LogPrintf("DigiDollar: Fee shortfall on attempt %d, retrying with %lld sats (actual %lld + 20%%)\n", + attempt + 1, static_cast(retryFee), static_cast(redeemResult.totalFees)); + redeemParams.feeUtxos.clear(); + feeAmounts.clear(); + selectedFeeTotal = 0; + if (!dd_wallet->SelectFeeCoins(retryFee, redeemParams.feeUtxos, selectedFeeTotal, &feeAmounts, &exclude_utxos)) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient DGB balance for retry fee"); + } + redeemParams.feeAmounts = feeAmounts; + } if (!redeemResult.success) { - throw JSONRPCError(RPC_WALLET_ERROR, "Failed to build redemption transaction: " + redeemResult.error); + throw JSONRPCError(RPC_WALLET_ERROR, "Failed to build redemption transaction after 3 attempts: " + redeemResult.error); } LogPrintf("DigiDollar: Redemption transaction built with %d inputs:\n", redeemResult.tx.vin.size()); diff --git a/src/wallet/digidollarwallet.cpp b/src/wallet/digidollarwallet.cpp index 7ec27923fd..51d39dc24f 100644 --- a/src/wallet/digidollarwallet.cpp +++ b/src/wallet/digidollarwallet.cpp @@ -4482,17 +4482,42 @@ bool DigiDollarWallet::RedeemDigiDollar(const uint256& dd_timelock_id, const CAm exclude_utxos.size(), params.ddUtxos.size()); CAmount selectedFeeTotal = 0; - if (!SelectFeeCoins(estimatedFee, params.feeUtxos, selectedFeeTotal, nullptr, &exclude_utxos)) { + std::vector fee_amounts; + if (!SelectFeeCoins(estimatedFee, params.feeUtxos, selectedFeeTotal, &fee_amounts, &exclude_utxos)) { LogPrintf("DigiDollar: Insufficient DGB balance for fees\n"); return false; } + params.feeAmounts = fee_amounts; LogPrintf("DigiDollar: CALLING BuildRedemptionTransaction now...\n"); - auto result = builder.BuildRedemptionTransaction(params); - LogPrintf("DigiDollar: BuildRedemptionTransaction returned success=%d, error='%s'\n", - result.success, result.error.c_str()); + DigiDollar::TxBuilderResult result; + for (int attempt = 0; attempt < 3; ++attempt) { + result = builder.BuildRedemptionTransaction(params); + LogPrintf("DigiDollar: BuildRedemptionTransaction attempt %d returned success=%d, error='%s'\n", + attempt + 1, result.success, result.error.c_str()); + if (result.success) break; + + // Only retry on fee shortfall errors + if (result.error.find("Insufficient fee inputs") == std::string::npos) { + LogPrintf("DigiDollar: Redemption transaction build failed (non-fee error) - %s\n", result.error); + return false; + } + + // Retry with actual fee + 20% margin + CAmount retryFee = result.totalFees + (result.totalFees * 20 / 100); + LogPrintf("DigiDollar: Fee shortfall on attempt %d, retrying with %lld sats (actual %lld + 20%%)\n", + attempt + 1, static_cast(retryFee), static_cast(result.totalFees)); + params.feeUtxos.clear(); + fee_amounts.clear(); + selectedFeeTotal = 0; + if (!SelectFeeCoins(retryFee, params.feeUtxos, selectedFeeTotal, &fee_amounts, &exclude_utxos)) { + LogPrintf("DigiDollar: Insufficient DGB balance for retry fee of %lld sats\n", static_cast(retryFee)); + return false; + } + params.feeAmounts = fee_amounts; + } if (!result.success) { - LogPrintf("DigiDollar: Redemption transaction build failed - %s\n", result.error); + LogPrintf("DigiDollar: Redemption transaction build failed after 3 attempts - %s\n", result.error); return false; } LogPrintf("DigiDollar: BuildRedemptionTransaction SUCCESS, transaction has %d inputs and %d outputs\n",