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 committed 2025-03-26 15:26:08 +08:00
1 parent 3c4fd1b484
commit 861dc145bf
3 files changed
+32 -28

No files matched your search

+13 -3
View File
@@ -1114,6 +1114,14 @@ func (t *TxPublisher) handleInitialTxError(r *monitorRecord, err error) {
case errors.Is(err, ErrZeroFeeRateDelta): case errors.Is(err, ErrZeroFeeRateDelta):
result.Event = TxFailed 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 // When there are missing inputs, we'll create a TxUnknownSpend bump
// result here so the rest of the inputs can be retried. // result here so the rest of the inputs can be retried.
case errors.Is(err, ErrInputMissing): 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. // Make sure total output amount is less than total input amount.
if requiredOutput+txFee > totalInput { if requiredOutput+txFee > totalInput {
return 0, noChange, noLocktime, fmt.Errorf("insufficient "+ log.Errorf("Insufficient input to create sweep tx: "+
"input to create sweep tx: input_sum=%v, "+ "input_sum=%v, output_sum=%v", totalInput,
"output_sum=%v", totalInput, requiredOutput+txFee) requiredOutput+txFee)
return 0, noChange, noLocktime, ErrNotEnoughInputs
} }
// The value remaining after the required output and fees is the // The value remaining after the required output and fees is the
+7 -11
View File
@@ -296,7 +296,9 @@ func (b *BudgetInputSet) NeedWalletInput() bool {
} }
// Get the amount left after covering the input's own budget. // 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 budget := inp.params.Budget
output := btcutil.Amount(inp.SignDesc().Output.Value) output := btcutil.Amount(inp.SignDesc().Output.Value)
budgetBorrowable += output - budget budgetBorrowable += output - budget
@@ -350,7 +352,7 @@ func (b *BudgetInputSet) AddWalletInputs(wallet Wallet) error {
// Exit early if there are no wallet UTXOs. // Exit early if there are no wallet UTXOs.
if len(utxos) == 0 { 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 // 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 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. // Add wallet inputs to the set until the specified budget is covered.
for _, utxo := range utxos { for _, utxo := range utxos {
err := b.addWalletInput(utxo) 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 log.Warn("Not enough wallet UTXOs to cover the budget, sweeping " +
// input set to its original state. "anyway...")
b.inputs = originalInputs
return ErrNotEnoughInputs return nil
} }
// Budget returns the total budget of the set. // Budget returns the total budget of the set.
+12 -14
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: // AddWalletInputs:
// - error from ListUnspentWitnessFromDefaultAccount. // - error from ListUnspentWitnessFromDefaultAccount.
// - error from createWalletTxInput. // - error from createWalletTxInput.
// - error when wallet doesn't have utxos. // - error when wallet doesn't have utxos.
func TestAddWalletInputReturnErr(t *testing.T) { func TestAddWalletInputsReturnErr(t *testing.T) {
t.Parallel() t.Parallel()
wallet := &MockWallet{} wallet := &MockWallet{}
@@ -436,10 +436,9 @@ func TestAddWalletInputReturnErr(t *testing.T) {
require.ErrorIs(t, err, ErrNotEnoughInputs) require.ErrorIs(t, err, ErrNotEnoughInputs)
} }
// TestAddWalletInputNotEnoughInputs checks that when there are not enough // TestAddWalletInputsNotEnoughInputs checks that when there are not enough
// wallet utxos, an error is returned and the budget set is reset to its // wallet utxos, no error is returned as long as the wallet is not empty.
// initial state. func TestAddWalletInputsNotEnoughInputs(t *testing.T) {
func TestAddWalletInputNotEnoughInputs(t *testing.T) {
t.Parallel() t.Parallel()
wallet := &MockWallet{} wallet := &MockWallet{}
@@ -476,19 +475,18 @@ func TestAddWalletInputNotEnoughInputs(t *testing.T) {
// Initialize an input set with the pending input. // Initialize an input set with the pending input.
set := BudgetInputSet{inputs: []*SweeperInput{pi}} set := BudgetInputSet{inputs: []*SweeperInput{pi}}
// Add wallet inputs to the input set, which should give us an error as // Add wallet inputs to the input set, which should return no error
// the wallet cannot cover the budget. // although the wallet cannot cover the budget.
err := set.AddWalletInputs(wallet) err := set.AddWalletInputs(wallet)
require.ErrorIs(t, err, ErrNotEnoughInputs) require.NoError(t, err)
// Check that the budget set is reverted to its initial state. // Check that the budget set is updated.
require.Len(t, set.inputs, 1) require.Len(t, set.inputs, 2)
require.Equal(t, pi, set.inputs[0])
} }
// 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. // they are added to the input set.
func TestAddWalletInputSuccess(t *testing.T) { func TestAddWalletInputsSuccess(t *testing.T) {
t.Parallel() t.Parallel()
wallet := &MockWallet{} wallet := &MockWallet{}