diff --git a/sweep/fee_bumper.go b/sweep/fee_bumper.go index b081fe479..99360d6fb 100644 --- a/sweep/fee_bumper.go +++ b/sweep/fee_bumper.go @@ -1114,6 +1114,14 @@ func (t *TxPublisher) handleInitialTxError(r *monitorRecord, err error) { case errors.Is(err, ErrZeroFeeRateDelta): result.Event = TxFailed + // When the error is due to not enough inputs to cover the budget, we'll + // send a TxFailed event so these inputs can be retried when the wallet + // has more UTXOs. + case errors.Is(err, ErrNotEnoughInputs), + errors.Is(err, ErrNotEnoughBudget): + + result.Event = TxFailed + // When there are missing inputs, we'll create a TxUnknownSpend bump // result here so the rest of the inputs can be retried. case errors.Is(err, ErrInputMissing): @@ -1757,9 +1765,11 @@ func prepareSweepTx(inputs []input.Input, changePkScript lnwallet.AddrWithKey, // Make sure total output amount is less than total input amount. if requiredOutput+txFee > totalInput { - return 0, noChange, noLocktime, fmt.Errorf("insufficient "+ - "input to create sweep tx: input_sum=%v, "+ - "output_sum=%v", totalInput, requiredOutput+txFee) + log.Errorf("Insufficient input to create sweep tx: "+ + "input_sum=%v, output_sum=%v", totalInput, + requiredOutput+txFee) + + return 0, noChange, noLocktime, ErrNotEnoughInputs } // The value remaining after the required output and fees is the diff --git a/sweep/tx_input_set.go b/sweep/tx_input_set.go index ff3b7f309..db4b893ed 100644 --- a/sweep/tx_input_set.go +++ b/sweep/tx_input_set.go @@ -296,7 +296,9 @@ func (b *BudgetInputSet) NeedWalletInput() bool { } // Get the amount left after covering the input's own budget. - // This amount can then be lent to the above input. + // This amount can then be lent to the above input. For a wallet + // input, its `Budget` is set to zero, which means the whole + // input can be borrowed to cover the budget. budget := inp.params.Budget output := btcutil.Amount(inp.SignDesc().Output.Value) budgetBorrowable += output - budget @@ -350,7 +352,7 @@ func (b *BudgetInputSet) AddWalletInputs(wallet Wallet) error { // Exit early if there are no wallet UTXOs. if len(utxos) == 0 { - return ErrNotEnoughInputs + return fmt.Errorf("%w: empty wallet", ErrNotEnoughInputs) } // Sort the UTXOs by putting smaller values at the start of the slice @@ -362,11 +364,6 @@ func (b *BudgetInputSet) AddWalletInputs(wallet Wallet) error { return utxos[i].Value < utxos[j].Value }) - // Make a copy of the current inputs. If the wallet doesn't have enough - // utxos to cover the budget, we will revert the current set to its - // original state by removing the added wallet inputs. - originalInputs := b.copyInputs() - // Add wallet inputs to the set until the specified budget is covered. for _, utxo := range utxos { err := b.addWalletInput(utxo) @@ -380,11 +377,10 @@ func (b *BudgetInputSet) AddWalletInputs(wallet Wallet) error { } } - // The wallet doesn't have enough utxos to cover the budget. Revert the - // input set to its original state. - b.inputs = originalInputs + log.Warn("Not enough wallet UTXOs to cover the budget, sweeping " + + "anyway...") - return ErrNotEnoughInputs + return nil } // Budget returns the total budget of the set. diff --git a/sweep/tx_input_set_test.go b/sweep/tx_input_set_test.go index 0ea056e49..6e91a3fb1 100644 --- a/sweep/tx_input_set_test.go +++ b/sweep/tx_input_set_test.go @@ -386,12 +386,12 @@ func TestNeedWalletInput(t *testing.T) { } } -// TestAddWalletInputReturnErr tests the three possible errors returned from +// TestAddWalletInputsReturnErr tests the three possible errors returned from // AddWalletInputs: // - error from ListUnspentWitnessFromDefaultAccount. // - error from createWalletTxInput. // - error when wallet doesn't have utxos. -func TestAddWalletInputReturnErr(t *testing.T) { +func TestAddWalletInputsReturnErr(t *testing.T) { t.Parallel() wallet := &MockWallet{} @@ -436,10 +436,9 @@ func TestAddWalletInputReturnErr(t *testing.T) { require.ErrorIs(t, err, ErrNotEnoughInputs) } -// TestAddWalletInputNotEnoughInputs checks that when there are not enough -// wallet utxos, an error is returned and the budget set is reset to its -// initial state. -func TestAddWalletInputNotEnoughInputs(t *testing.T) { +// TestAddWalletInputsNotEnoughInputs checks that when there are not enough +// wallet utxos, no error is returned as long as the wallet is not empty. +func TestAddWalletInputsNotEnoughInputs(t *testing.T) { t.Parallel() wallet := &MockWallet{} @@ -476,19 +475,18 @@ func TestAddWalletInputNotEnoughInputs(t *testing.T) { // Initialize an input set with the pending input. set := BudgetInputSet{inputs: []*SweeperInput{pi}} - // Add wallet inputs to the input set, which should give us an error as - // the wallet cannot cover the budget. + // Add wallet inputs to the input set, which should return no error + // although the wallet cannot cover the budget. err := set.AddWalletInputs(wallet) - require.ErrorIs(t, err, ErrNotEnoughInputs) + require.NoError(t, err) - // Check that the budget set is reverted to its initial state. - require.Len(t, set.inputs, 1) - require.Equal(t, pi, set.inputs[0]) + // Check that the budget set is updated. + require.Len(t, set.inputs, 2) } -// TestAddWalletInputSuccess checks that when there are enough wallet utxos, +// TestAddWalletInputsSuccess checks that when there are enough wallet utxos, // they are added to the input set. -func TestAddWalletInputSuccess(t *testing.T) { +func TestAddWalletInputsSuccess(t *testing.T) { t.Parallel() wallet := &MockWallet{}