Skip to content

fix: add fee retry loop to redemption flow (Bug #9)#389

Closed
JohnnyLawDGB wants to merge 1 commit intoDigiByte-Core:feature/digidollar-v1from
JohnnyLawDGB:fix/bug9-redemption-fee-retry
Closed

fix: add fee retry loop to redemption flow (Bug #9)#389
JohnnyLawDGB wants to merge 1 commit intoDigiByte-Core:feature/digidollar-v1from
JohnnyLawDGB:fix/bug9-redemption-fee-retry

Conversation

@JohnnyLawDGB
Copy link

Summary

  • Fixes Bug Propose new decay rate #9: "Insufficient fee inputs for calculated fee" on redemption, which made collateral permanently locked
  • Adds a retry loop (max 3 attempts) to both redemption code paths when the initial 0.1 DGB fee estimate is too low
  • On fee shortfall, re-selects fee UTXOs using result.totalFees + 20% margin, then retries the build
  • Also fixes the timelock-based RedeemDigiDollar to populate params.feeAmounts (was passing nullptr to SelectFeeCoins)

Root Cause

Redemption transactions with multiple inputs (1 collateral + 2+ DD UTXOs + fee UTXOs) require more than the 0.1 DGB fee estimate. The txbuilder calculates the actual fee after constructing the transaction, but if pre-selected fee UTXOs don't cover it, the build fails with no recovery path.

Files Changed

File Change
src/rpc/digidollar.cpp Add retry loop to wallet RPC redemption handler
src/wallet/digidollarwallet.cpp Fix feeAmounts population + add retry loop to direct wallet call

Testnet Verification (testnet19, block ~98,400)

Three consecutive redemptions all succeed after automatic fee retry:

# Position DD Redeemed DGB Unlocked Fee TXID
1 1b1a69dd... 10,000 cents 240,731.82 tDGB 0.280 tDGB 6c97a7c3...
2 f66e9e76... 10,000 cents 240,731.82 tDGB 0.215 tDGB e00a5145...
3 75b3b386... 10,001 cents 240,755.90 tDGB 0.248 tDGB a69f5a59...

Debug Log Proof

# Redemption 1
DigiDollar: ====== REDEMPTION REQUEST ======
DigiDollar: Position ID: 1b1a69dd...
DigiDollar: Selected 17177782 sats in fees from 2 UTXOs for redemption
DigiDollar: BuildRedemptionTransaction FAILED - Insufficient fee inputs for calculated fee
DigiDollar: attempt 1 returned success=0, error='Insufficient fee inputs for calculated fee'
DigiDollar: Fee shortfall on attempt 1, retrying with 25872000 sats (actual 21560000 + 20%)
DigiDollar: BuildRedemptionTransaction SUCCESS - 7 inputs, 4 outputs, fee: 28035000 sats
DigiDollar: attempt 2 returned success=1, error=''

# Redemption 2
DigiDollar: Position ID: f66e9e76...
DigiDollar: Selected 14909455 sats in fees from 2 UTXOs
DigiDollar: attempt 1 returned success=0 (Insufficient fee inputs)
DigiDollar: Fee shortfall, retrying with 22050000 sats (actual 18375000 + 20%)
DigiDollar: attempt 2 returned success=1

# Redemption 3
DigiDollar: Position ID: 75b3b386...
DigiDollar: Selected 10527237 sats in fees from 2 UTXOs
DigiDollar: attempt 1 returned success=0 (Insufficient fee inputs)
DigiDollar: Fee shortfall, retrying with 22050000 sats (actual 18375000 + 20%)
DigiDollar: attempt 2 returned success=1

Test plan

  • Unit tests pass: digidollar_wallet_tests (131 cases), digidollar_redeem_tests (23 cases)
  • Live testnet redemption: 3/3 positions redeemed successfully
  • Retry logic verified in debug logs: all 3 needed exactly 1 retry
  • Verify no regression on mint and transfer flows

🤖 Generated with Claude Code

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 <noreply@anthropic.com>
@DigiSwarm
Copy link

Thanks for digging into this and for the solid testing on testnet19 — the debug log proof is great work.

We've been working on the same Bug #9 issue on our end, and we've actually addressed the root cause directly rather than adding a retry loop. Here's what happened:

The core problem was that the fee estimation was hardcoded at estimatedFee = 10000000 (0.1 DGB) in four different places across digidollar.cpp and digidollarwallet.cpp. At the DD minimum fee rate of 35M sat/kB, a typical redemption tx (~400 vbytes) actually needs ~14M sats minimum. The 0.1 DGB estimate was simply too low.

Our fix replaces the hardcoded value with a proper calculation everywhere:

estimatedFee = (vsize * feeRate) / 1000  // ~14M sats at 35M/kB
estimatedFee += estimatedFee / 2          // 50% safety margin = ~21M sats
if (estimatedFee < 10000000) floor at 0.1 DGB

This means the first attempt should already have enough fee selected, so the retry loop shouldn't be needed. The retry approach works as a bandaid, but it still starts with the wrong estimate and always fails on attempt 1 before recovering — your own logs show this ("attempt 1 returned success=0" on all 3 redemptions).

However — you caught a real bug that we missed. The nullptr being passed to SelectFeeCoins in RedeemDigiDollar() at line ~4503 of digidollarwallet.cpp is a legitimate issue. Without populating params.feeAmounts, the TxBuilder doesn't know the value of each fee UTXO. The RPC path did this correctly, but the wallet's direct call path didn't. Good catch.

We've incorporated that fix into our RC24 branch with credit to you in the commit message (50a62bc — "Credit: JohnnyLawDGB (identified in PR #389)").

Going to close this PR since the underlying fee estimation has been properly fixed, but thank you for the contribution — the nullptr fix is real and is now part of RC24.

@DigiSwarm DigiSwarm closed this Mar 6, 2026
JaredTate added a commit that referenced this pull request Mar 6, 2026
SelectFeeCoins was called with nullptr for the amounts output in the
wallet's direct RedeemDigiDollar() method, leaving params.feeAmounts
empty. BuildRedemptionTransaction needs per-UTXO amounts to correctly
construct fee inputs. The RPC path already did this correctly.

Credit: JohnnyLawDGB (identified in PR #389)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants