Skip to content

Commit 9b7de60

Browse files
Roasbeefguggero
authored andcommitted
sweep: update sweeper to use AuxSweeper to add extra change addr
In this commit, we start to use the AuxSweeper (if present) to obtain a new extra change addr we should add to the sweeping transaction. With this, we'll take the set of inputs and our change addr, and then maybe gain a new change addr to add to the sweep transaction. The extra change addr will be treated as an extra required tx out, shared across all the relevant inputs. This'll also be used in NeedWalletInput to make sure that we add an extra input if needed to be able to pay for the change addr.
1 parent 0cd4b04 commit 9b7de60

File tree

4 files changed

+139
-56
lines changed

4 files changed

+139
-56
lines changed

sweep/fee_bumper.go

Lines changed: 88 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/btcsuite/btcd/txscript"
1313
"github.com/btcsuite/btcd/wire"
1414
"github.com/btcsuite/btcwallet/chain"
15+
"github.com/davecgh/go-spew/spew"
1516
"github.com/lightningnetwork/lnd/chainntnfs"
1617
"github.com/lightningnetwork/lnd/fn"
1718
"github.com/lightningnetwork/lnd/input"
@@ -131,6 +132,8 @@ type BumpRequest struct {
131132
func (r *BumpRequest) MaxFeeRateAllowed() (chainfee.SatPerKWeight, error) {
132133
// Get the size of the sweep tx, which will be used to calculate the
133134
// budget fee rate.
135+
//
136+
// TODO(roasbeef): also wants the extra change output?
134137
size, err := calcSweepTxWeight(
135138
r.Inputs, r.DeliveryAddress.DeliveryAddress,
136139
)
@@ -175,7 +178,7 @@ func calcSweepTxWeight(inputs []input.Input,
175178
// TODO(yy): we should refactor the weight estimator to not require a
176179
// fee rate and max fee rate and make it a pure tx weight calculator.
177180
_, estimator, err := getWeightEstimate(
178-
inputs, nil, feeRate, 0, outputPkScript,
181+
inputs, nil, feeRate, 0, [][]byte{outputPkScript},
179182
)
180183
if err != nil {
181184
return 0, err
@@ -1171,9 +1174,9 @@ func (t *TxPublisher) createSweepTx(inputs []input.Input,
11711174
feeRate chainfee.SatPerKWeight) (*sweepTxCtx, error) {
11721175

11731176
// Validate and calculate the fee and change amount.
1174-
txFee, changeAmtOpt, locktimeOpt, err := prepareSweepTx(
1175-
inputs, changePkScript.DeliveryAddress, feeRate,
1176-
t.currentHeight.Load(),
1177+
txFee, changeOutputsOpt, locktimeOpt, err := prepareSweepTx(
1178+
inputs, changePkScript, feeRate, t.currentHeight.Load(),
1179+
t.cfg.AuxSweeper,
11771180
)
11781181
if err != nil {
11791182
return nil, err
@@ -1219,12 +1222,12 @@ func (t *TxPublisher) createSweepTx(inputs []input.Input,
12191222
})
12201223
}
12211224

1222-
// If there's a change amount, add it to the transaction.
1223-
changeAmtOpt.WhenSome(func(changeAmt btcutil.Amount) {
1224-
sweepTx.AddTxOut(&wire.TxOut{
1225-
PkScript: changePkScript.DeliveryAddress,
1226-
Value: int64(changeAmt),
1227-
})
1225+
// If we have change outputs to add, then add it the sweep transaction
1226+
// here.
1227+
changeOutputsOpt.WhenSome(func(changeOuts []SweepOutput) {
1228+
for i := range changeOuts {
1229+
sweepTx.AddTxOut(&changeOuts[i].TxOut)
1230+
}
12281231
})
12291232

12301233
// We'll default to using the current block height as locktime, if none
@@ -1268,31 +1271,67 @@ func (t *TxPublisher) createSweepTx(inputs []input.Input,
12681271
log.Debugf("Created sweep tx %v for inputs:\n%v", sweepTx.TxHash(),
12691272
inputTypeSummary(inputs))
12701273

1274+
// Try to locate the extra change output, though there might be None.
1275+
extraTxOut := fn.MapOption(
1276+
func(sweepOuts []SweepOutput) fn.Option[SweepOutput] {
1277+
for _, sweepOut := range sweepOuts {
1278+
if sweepOut.IsExtra {
1279+
log.Infof("Sweep produced "+
1280+
"extra_sweep_out=%v",
1281+
spew.Sdump(sweepOut))
1282+
1283+
return fn.Some(sweepOut)
1284+
}
1285+
}
1286+
1287+
return fn.None[SweepOutput]()
1288+
},
1289+
)(changeOutputsOpt)
1290+
12711291
return &sweepTxCtx{
1272-
tx: sweepTx,
1273-
fee: txFee,
1292+
tx: sweepTx,
1293+
fee: txFee,
1294+
extraTxOut: fn.FlattenOption(extraTxOut),
12741295
}, nil
12751296
}
12761297

1277-
// prepareSweepTx returns the tx fee, an optional change amount and an optional
1278-
// locktime after a series of validations:
1298+
// prepareSweepTx returns the tx fee, a set of optional change outputs and an
1299+
// optional locktime after a series of validations:
12791300
// 1. check the locktime has been reached.
12801301
// 2. check the locktimes are the same.
12811302
// 3. check the inputs cover the outputs.
12821303
//
12831304
// NOTE: if the change amount is below dust, it will be added to the tx fee.
1284-
func prepareSweepTx(inputs []input.Input, changePkScript []byte,
1285-
feeRate chainfee.SatPerKWeight, currentHeight int32) (
1286-
btcutil.Amount, fn.Option[btcutil.Amount], fn.Option[int32], error) {
1305+
func prepareSweepTx(inputs []input.Input, changePkScript lnwallet.AddrWithKey,
1306+
feeRate chainfee.SatPerKWeight, currentHeight int32,
1307+
auxSweeper fn.Option[AuxSweeper]) (
1308+
btcutil.Amount, fn.Option[[]SweepOutput], fn.Option[int32], error) {
12871309

1288-
noChange := fn.None[btcutil.Amount]()
1310+
noChange := fn.None[[]SweepOutput]()
12891311
noLocktime := fn.None[int32]()
12901312

1313+
// Given the set of inputs we have, if we have an aux sweeper, then
1314+
// we'll attempt to see if we have any other change outputs we'll need
1315+
// to add to the sweep transaction.
1316+
changePkScripts := [][]byte{changePkScript.DeliveryAddress}
1317+
extraChangeOut := fn.MapOptionZ(
1318+
auxSweeper, func(aux AuxSweeper) fn.Result[SweepOutput] {
1319+
return aux.DeriveSweepAddr(inputs, changePkScript)
1320+
},
1321+
)
1322+
if err := extraChangeOut.Err(); err != nil {
1323+
return 0, noChange, noLocktime, err
1324+
}
1325+
1326+
extraChangeOut.WhenResult(func(o SweepOutput) {
1327+
changePkScripts = append(changePkScripts, o.PkScript)
1328+
})
1329+
12911330
// Creating a weight estimator with nil outputs and zero max fee rate.
12921331
// We don't allow adding customized outputs in the sweeping tx, and the
12931332
// fee rate is already being managed before we get here.
12941333
inputs, estimator, err := getWeightEstimate(
1295-
inputs, nil, feeRate, 0, changePkScript,
1334+
inputs, nil, feeRate, 0, changePkScripts,
12961335
)
12971336
if err != nil {
12981337
return 0, noChange, noLocktime, err
@@ -1310,6 +1349,12 @@ func prepareSweepTx(inputs []input.Input, changePkScript []byte,
13101349
requiredOutput btcutil.Amount
13111350
)
13121351

1352+
// If we have an extra change output, then we'll add it as a required
1353+
// output amt.
1354+
extraChangeOut.WhenResult(func(o SweepOutput) {
1355+
requiredOutput += btcutil.Amount(o.Value)
1356+
})
1357+
13131358
// Go through each input and check if the required lock times have
13141359
// reached and are the same.
13151360
for _, o := range inputs {
@@ -1356,14 +1401,21 @@ func prepareSweepTx(inputs []input.Input, changePkScript []byte,
13561401
// The value remaining after the required output and fees is the
13571402
// change output.
13581403
changeAmt := totalInput - requiredOutput - txFee
1359-
changeAmtOpt := fn.Some(changeAmt)
1404+
changeOuts := make([]SweepOutput, 0, 2)
1405+
1406+
extraChangeOut.WhenResult(func(o SweepOutput) {
1407+
changeOuts = append(changeOuts, o)
1408+
})
13601409

13611410
// We'll calculate the dust limit for the given changePkScript since it
13621411
// is variable.
1363-
changeFloor := lnwallet.DustLimitForSize(len(changePkScript))
1412+
changeFloor := lnwallet.DustLimitForSize(
1413+
len(changePkScript.DeliveryAddress),
1414+
)
13641415

1365-
// If the change amount is dust, we'll move it into the fees.
13661416
switch {
1417+
// If the change amount is dust, we'll move it into the fees, and
1418+
// ignore it.
13671419
case changeAmt < changeFloor:
13681420
log.Infof("Change amt %v below dustlimit %v, not adding "+
13691421
"change output", changeAmt, changeFloor)
@@ -1379,12 +1431,16 @@ func prepareSweepTx(inputs []input.Input, changePkScript []byte,
13791431
// The dust amount is added to the fee.
13801432
txFee += changeAmt
13811433

1382-
// Set the change amount to none.
1383-
changeAmtOpt = fn.None[btcutil.Amount]()
1384-
13851434
// Otherwise, we'll actually recognize it as a change output.
13861435
default:
1387-
// TODO(roasbeef): Implement (later commit in this PR).
1436+
changeOuts = append(changeOuts, SweepOutput{
1437+
TxOut: wire.TxOut{
1438+
Value: int64(changeAmt),
1439+
PkScript: changePkScript.DeliveryAddress,
1440+
},
1441+
IsExtra: false,
1442+
InternalKey: changePkScript.InternalKey,
1443+
})
13881444
}
13891445

13901446
// Optionally set the locktime.
@@ -1393,12 +1449,17 @@ func prepareSweepTx(inputs []input.Input, changePkScript []byte,
13931449
locktimeOpt = noLocktime
13941450
}
13951451

1452+
var changeOutsOpt fn.Option[[]SweepOutput]
1453+
if len(changeOuts) > 0 {
1454+
changeOutsOpt = fn.Some(changeOuts)
1455+
}
1456+
13961457
log.Debugf("Creating sweep tx for %v inputs (%s) using %v, "+
13971458
"tx_weight=%v, tx_fee=%v, locktime=%v, parents_count=%v, "+
13981459
"parents_fee=%v, parents_weight=%v, current_height=%v",
13991460
len(inputs), inputTypeSummary(inputs), feeRate,
14001461
estimator.weight(), txFee, locktimeOpt, len(estimator.parents),
14011462
estimator.parentsFee, estimator.parentsWeight, currentHeight)
14021463

1403-
return txFee, changeAmtOpt, locktimeOpt, nil
1464+
return txFee, changeOutsOpt, locktimeOpt, nil
14041465
}

sweep/tx_input_set.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,26 @@ func (b *BudgetInputSet) NeedWalletInput() bool {
220220
budgetBorrowable btcutil.Amount
221221
)
222222

223+
// If any of the outputs in the set have a resolution blob, then this
224+
// means we'll end up needing an extra change output. We'll tack this
225+
// on now as an extra portion of the budget.
226+
extraRequiredTxOut := fn.Any(func(i *SweeperInput) bool {
227+
// If there's a required txout, then we don't count this as
228+
// it'll be a second level HTLC.
229+
if i.RequiredTxOut() != nil {
230+
return false
231+
}
232+
233+
// Otherwise, we need one if we have a resolution blob.
234+
return i.ResolutionBlob().IsSome()
235+
}, b.inputs)
236+
237+
if extraRequiredTxOut {
238+
// TODO(roasbeef): aux sweeper ext to ask for extra output
239+
// params and value?
240+
budgetNeeded += 1_000
241+
}
242+
223243
for _, inp := range b.inputs {
224244
// If this input has a required output, we can assume it's a
225245
// second-level htlc txns input. Although this input must have

sweep/txgenerator.go

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func createSweepTx(inputs []input.Input, outputs []*wire.TxOut,
3838
signer input.Signer) (*wire.MsgTx, btcutil.Amount, error) {
3939

4040
inputs, estimator, err := getWeightEstimate(
41-
inputs, outputs, feeRate, maxFeeRate, changePkScript,
41+
inputs, outputs, feeRate, maxFeeRate, [][]byte{changePkScript},
4242
)
4343
if err != nil {
4444
return nil, 0, err
@@ -221,7 +221,7 @@ func createSweepTx(inputs []input.Input, outputs []*wire.TxOut,
221221
// Additionally, it returns counts for the number of csv and cltv inputs.
222222
func getWeightEstimate(inputs []input.Input, outputs []*wire.TxOut,
223223
feeRate, maxFeeRate chainfee.SatPerKWeight,
224-
outputPkScript []byte) ([]input.Input, *weightEstimator, error) {
224+
outputPkScripts [][]byte) ([]input.Input, *weightEstimator, error) {
225225

226226
// We initialize a weight estimator so we can accurately asses the
227227
// amount of fees we need to pay for this sweep transaction.
@@ -237,31 +237,33 @@ func getWeightEstimate(inputs []input.Input, outputs []*wire.TxOut,
237237

238238
// If there is any leftover change after paying to the given outputs
239239
// and required outputs, it will go to a single segwit p2wkh or p2tr
240-
// address. This will be our change address, so ensure it contributes to
241-
// our weight estimate. Note that if we have other outputs, we might end
242-
// up creating a sweep tx without a change output. It is okay to add the
243-
// change output to the weight estimate regardless, since the estimated
244-
// fee will just be subtracted from this already dust output, and
245-
// trimmed.
246-
switch {
247-
case txscript.IsPayToTaproot(outputPkScript):
248-
weightEstimate.addP2TROutput()
249-
250-
case txscript.IsPayToWitnessScriptHash(outputPkScript):
251-
weightEstimate.addP2WSHOutput()
252-
253-
case txscript.IsPayToWitnessPubKeyHash(outputPkScript):
254-
weightEstimate.addP2WKHOutput()
255-
256-
case txscript.IsPayToPubKeyHash(outputPkScript):
257-
weightEstimate.estimator.AddP2PKHOutput()
258-
259-
case txscript.IsPayToScriptHash(outputPkScript):
260-
weightEstimate.estimator.AddP2SHOutput()
261-
262-
default:
263-
// Unknown script type.
264-
return nil, nil, errors.New("unknown script type")
240+
// address. This will be our change address, so ensure it contributes
241+
// to our weight estimate. Note that if we have other outputs, we might
242+
// end up creating a sweep tx without a change output. It is okay to
243+
// add the change output to the weight estimate regardless, since the
244+
// estimated fee will just be subtracted from this already dust output,
245+
// and trimmed.
246+
for _, outputPkScript := range outputPkScripts {
247+
switch {
248+
case txscript.IsPayToTaproot(outputPkScript):
249+
weightEstimate.addP2TROutput()
250+
251+
case txscript.IsPayToWitnessScriptHash(outputPkScript):
252+
weightEstimate.addP2WSHOutput()
253+
254+
case txscript.IsPayToWitnessPubKeyHash(outputPkScript):
255+
weightEstimate.addP2WKHOutput()
256+
257+
case txscript.IsPayToPubKeyHash(outputPkScript):
258+
weightEstimate.estimator.AddP2PKHOutput()
259+
260+
case txscript.IsPayToScriptHash(outputPkScript):
261+
weightEstimate.estimator.AddP2SHOutput()
262+
263+
default:
264+
// Unknown script type.
265+
return nil, nil, errors.New("unknown script type")
266+
}
265267
}
266268

267269
// For each output, use its witness type to determine the estimate

sweep/txgenerator_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func TestWeightEstimate(t *testing.T) {
5151
}
5252

5353
_, estimator, err := getWeightEstimate(
54-
inputs, nil, 0, 0, changePkScript,
54+
inputs, nil, 0, 0, [][]byte{changePkScript},
5555
)
5656
require.NoError(t, err)
5757

@@ -153,7 +153,7 @@ func testUnknownScriptInner(t *testing.T, pkscript []byte, expectFail bool) {
153153
))
154154
}
155155

156-
_, _, err := getWeightEstimate(inputs, nil, 0, 0, pkscript)
156+
_, _, err := getWeightEstimate(inputs, nil, 0, 0, [][]byte{pkscript})
157157
if expectFail {
158158
require.Error(t, err)
159159
} else {

0 commit comments

Comments
 (0)