sweep: create sweep tx even the budget cannot be met

We now always create the sweeping tx even though the budget cannot be
covered so we don't miss the deadline. Note that the fee bump will fail
once the provided wallet input cannot cover the increase fees, which is
fine as these inputs will be marked as failed and be retried again in
the next block. When that happens, if there are new wallet UTXOs, a new
batch will be created to perform the fee bump.
This commit is contained in:
yyforyongyu
2025-03-19 17:32:42 +08:00
parent 3c4fd1b484
commit 861dc145bf
3 changed files with 32 additions and 28 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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{}