@@ -110,14 +110,18 @@ type BumpRequest struct {
110110 DeadlineHeight int32
111111
112112 // DeliveryAddress is the script to send the change output to.
113- DeliveryAddress [] byte
113+ DeliveryAddress lnwallet. AddrWithKey
114114
115115 // MaxFeeRate is the maximum fee rate that can be used for fee bumping.
116116 MaxFeeRate chainfee.SatPerKWeight
117117
118118 // StartingFeeRate is an optional parameter that can be used to specify
119119 // the initial fee rate to use for the fee function.
120120 StartingFeeRate fn.Option [chainfee.SatPerKWeight ]
121+
122+ // ExtraTxOut tracks if this bump request has an optional set of extra
123+ // outputs to add to the transaction.
124+ ExtraTxOut fn.Option [SweepOutput ]
121125}
122126
123127// MaxFeeRateAllowed returns the maximum fee rate allowed for the given
@@ -127,7 +131,9 @@ type BumpRequest struct {
127131func (r * BumpRequest ) MaxFeeRateAllowed () (chainfee.SatPerKWeight , error ) {
128132 // Get the size of the sweep tx, which will be used to calculate the
129133 // budget fee rate.
130- size , err := calcSweepTxWeight (r .Inputs , r .DeliveryAddress )
134+ size , err := calcSweepTxWeight (
135+ r .Inputs , r .DeliveryAddress .DeliveryAddress ,
136+ )
131137 if err != nil {
132138 return 0 , err
133139 }
@@ -248,6 +254,10 @@ type TxPublisherConfig struct {
248254
249255 // Notifier is used to monitor the confirmation status of the tx.
250256 Notifier chainntnfs.ChainNotifier
257+
258+ // AuxSweeper is an optional interface that can be used to shape the
259+ // way the final sweep transaction is generated.
260+ AuxSweeper fn.Option [AuxSweeper ]
251261}
252262
253263// TxPublisher is an implementation of the Bumper interface. It utilizes the
@@ -401,16 +411,18 @@ func (t *TxPublisher) createRBFCompliantTx(req *BumpRequest,
401411 for {
402412 // Create a new tx with the given fee rate and check its
403413 // mempool acceptance.
404- tx , fee , err := t .createAndCheckTx (req , f )
414+ sweepCtx , err := t .createAndCheckTx (req , f )
405415
406416 switch {
407417 case err == nil :
408418 // The tx is valid, return the request ID.
409- requestID := t .storeRecord (tx , req , f , fee )
419+ requestID := t .storeRecord (
420+ sweepCtx .tx , req , f , sweepCtx .fee ,
421+ )
410422
411423 log .Infof ("Created tx %v for %v inputs: feerate=%v, " +
412- "fee=%v, inputs=%v" , tx .TxHash (),
413- len (req .Inputs ), f .FeeRate (), fee ,
424+ "fee=%v, inputs=%v" , sweepCtx . tx .TxHash (),
425+ len (req .Inputs ), f .FeeRate (), sweepCtx . fee ,
414426 inputTypeSummary (req .Inputs ))
415427
416428 return requestID , nil
@@ -421,8 +433,8 @@ func (t *TxPublisher) createRBFCompliantTx(req *BumpRequest,
421433 // We should at least start with a feerate above the
422434 // mempool min feerate, so if we get this error, it
423435 // means something is wrong earlier in the pipeline.
424- log .Errorf ("Current fee=%v, feerate=%v, %v" , fee ,
425- f .FeeRate (), err )
436+ log .Errorf ("Current fee=%v, feerate=%v, %v" ,
437+ sweepCtx . fee , f .FeeRate (), err )
426438
427439 fallthrough
428440
@@ -434,8 +446,8 @@ func (t *TxPublisher) createRBFCompliantTx(req *BumpRequest,
434446 // increased or maxed out.
435447 for ! increased {
436448 log .Debugf ("Increasing fee for next round, " +
437- "current fee=%v, feerate=%v" , fee ,
438- f .FeeRate ())
449+ "current fee=%v, feerate=%v" ,
450+ sweepCtx . fee , f .FeeRate ())
439451
440452 // If the fee function tells us that we have
441453 // used up the budget, we will return an error
@@ -484,49 +496,53 @@ func (t *TxPublisher) storeRecord(tx *wire.MsgTx, req *BumpRequest,
484496// script, and the fee rate. In addition, it validates the tx's mempool
485497// acceptance before returning a tx that can be published directly, along with
486498// its fee.
487- func (t * TxPublisher ) createAndCheckTx (req * BumpRequest , f FeeFunction ) (
488- * wire. MsgTx , btcutil. Amount , error ) {
499+ func (t * TxPublisher ) createAndCheckTx (req * BumpRequest ,
500+ f FeeFunction ) ( * sweepTxCtx , error ) {
489501
490502 // Create the sweep tx with max fee rate of 0 as the fee function
491503 // guarantees the fee rate used here won't exceed the max fee rate.
492- tx , fee , err := t .createSweepTx (
504+ sweepCtx , err := t .createSweepTx (
493505 req .Inputs , req .DeliveryAddress , f .FeeRate (),
494506 )
495507 if err != nil {
496- return nil , fee , fmt .Errorf ("create sweep tx: %w" , err )
508+ return sweepCtx , fmt .Errorf ("create sweep tx: %w" , err )
497509 }
498510
499511 // Sanity check the budget still covers the fee.
500- if fee > req .Budget {
501- return nil , fee , fmt .Errorf ("%w: budget=%v, fee=%v" ,
502- ErrNotEnoughBudget , req .Budget , fee )
512+ if sweepCtx . fee > req .Budget {
513+ return sweepCtx , fmt .Errorf ("%w: budget=%v, fee=%v" ,
514+ ErrNotEnoughBudget , req .Budget , sweepCtx . fee )
503515 }
504516
517+ // If we had an extra txOut, then we'll update the result to include
518+ // it.
519+ req .ExtraTxOut = sweepCtx .extraTxOut
520+
505521 // Validate the tx's mempool acceptance.
506- err = t .cfg .Wallet .CheckMempoolAcceptance (tx )
522+ err = t .cfg .Wallet .CheckMempoolAcceptance (sweepCtx . tx )
507523
508524 // Exit early if the tx is valid.
509525 if err == nil {
510- return tx , fee , nil
526+ return sweepCtx , nil
511527 }
512528
513529 // Print an error log if the chain backend doesn't support the mempool
514530 // acceptance test RPC.
515531 if errors .Is (err , rpcclient .ErrBackendVersion ) {
516532 log .Errorf ("TestMempoolAccept not supported by backend, " +
517533 "consider upgrading it to a newer version" )
518- return tx , fee , nil
534+ return sweepCtx , nil
519535 }
520536
521537 // We are running on a backend that doesn't implement the RPC
522538 // testmempoolaccept, eg, neutrino, so we'll skip the check.
523539 if errors .Is (err , chain .ErrUnimplemented ) {
524540 log .Debug ("Skipped testmempoolaccept due to not implemented" )
525- return tx , fee , nil
541+ return sweepCtx , nil
526542 }
527543
528- return nil , fee , fmt .Errorf ("tx=%v failed mempool check: %w" ,
529- tx .TxHash (), err )
544+ return sweepCtx , fmt .Errorf ("tx=%v failed mempool check: %w" ,
545+ sweepCtx . tx .TxHash (), err )
530546}
531547
532548// broadcast takes a monitored tx and publishes it to the network. Prior to the
@@ -547,14 +563,23 @@ func (t *TxPublisher) broadcast(requestID uint64) (*BumpResult, error) {
547563 log .Debugf ("Publishing sweep tx %v, num_inputs=%v, height=%v" ,
548564 txid , len (tx .TxIn ), t .currentHeight .Load ())
549565
566+ // Before we go to broadcast, we'll notify the aux sweeper, if it's
567+ // present of this new broadcast attempt.
568+ err := fn .MapOptionZ (t .cfg .AuxSweeper , func (aux AuxSweeper ) error {
569+ return aux .NotifyBroadcast (record .req , tx , record .fee )
570+ })
571+ if err != nil {
572+ return nil , fmt .Errorf ("unable to notify aux sweeper: %w" , err )
573+ }
574+
550575 // Set the event, and change it to TxFailed if the wallet fails to
551576 // publish it.
552577 event := TxPublished
553578
554579 // Publish the sweeping tx with customized label. If the publish fails,
555580 // this error will be saved in the `BumpResult` and it will be removed
556581 // from being monitored.
557- err : = t .cfg .Wallet .PublishTransaction (
582+ err = t .cfg .Wallet .PublishTransaction (
558583 tx , labels .MakeLabel (labels .LabelTypeSweepTransaction , nil ),
559584 )
560585 if err != nil {
@@ -933,7 +958,7 @@ func (t *TxPublisher) createAndPublishTx(requestID uint64,
933958 // NOTE: The fee function is expected to have increased its returned
934959 // fee rate after calling the SkipFeeBump method. So we can use it
935960 // directly here.
936- tx , fee , err := t .createAndCheckTx (r .req , r .feeFunction )
961+ sweepCtx , err := t .createAndCheckTx (r .req , r .feeFunction )
937962
938963 // If the error is fee related, we will return no error and let the fee
939964 // bumper retry it at next block.
@@ -980,17 +1005,17 @@ func (t *TxPublisher) createAndPublishTx(requestID uint64,
9801005 // The tx has been created without any errors, we now register a new
9811006 // record by overwriting the same requestID.
9821007 t .records .Store (requestID , & monitorRecord {
983- tx : tx ,
1008+ tx : sweepCtx . tx ,
9841009 req : r .req ,
9851010 feeFunction : r .feeFunction ,
986- fee : fee ,
1011+ fee : sweepCtx . fee ,
9871012 })
9881013
9891014 // Attempt to broadcast this new tx.
9901015 result , err := t .broadcast (requestID )
9911016 if err != nil {
9921017 log .Infof ("Failed to broadcast replacement tx %v: %v" ,
993- tx .TxHash (), err )
1018+ sweepCtx . tx .TxHash (), err )
9941019
9951020 return fn .None [BumpResult ]()
9961021 }
@@ -1016,7 +1041,8 @@ func (t *TxPublisher) createAndPublishTx(requestID uint64,
10161041 return fn .Some (* result )
10171042 }
10181043
1019- log .Infof ("Replaced tx=%v with new tx=%v" , oldTx .TxHash (), tx .TxHash ())
1044+ log .Infof ("Replaced tx=%v with new tx=%v" , oldTx .TxHash (),
1045+ sweepCtx .tx .TxHash ())
10201046
10211047 // Otherwise, it's a successful RBF, set the event and return.
10221048 result .Event = TxReplaced
@@ -1129,17 +1155,28 @@ func calcCurrentConfTarget(currentHeight, deadline int32) uint32 {
11291155 return confTarget
11301156}
11311157
1158+ // sweepTxCtx houses a sweep transaction with additional context.
1159+ type sweepTxCtx struct {
1160+ tx * wire.MsgTx
1161+
1162+ fee btcutil.Amount
1163+
1164+ extraTxOut fn.Option [SweepOutput ]
1165+ }
1166+
11321167// createSweepTx creates a sweeping tx based on the given inputs, change
11331168// address and fee rate.
1134- func (t * TxPublisher ) createSweepTx (inputs []input.Input , changePkScript []byte ,
1135- feeRate chainfee.SatPerKWeight ) (* wire.MsgTx , btcutil.Amount , error ) {
1169+ func (t * TxPublisher ) createSweepTx (inputs []input.Input ,
1170+ changePkScript lnwallet.AddrWithKey ,
1171+ feeRate chainfee.SatPerKWeight ) (* sweepTxCtx , error ) {
11361172
11371173 // Validate and calculate the fee and change amount.
11381174 txFee , changeAmtOpt , locktimeOpt , err := prepareSweepTx (
1139- inputs , changePkScript , feeRate , t .currentHeight .Load (),
1175+ inputs , changePkScript .DeliveryAddress , feeRate ,
1176+ t .currentHeight .Load (),
11401177 )
11411178 if err != nil {
1142- return nil , 0 , err
1179+ return nil , err
11431180 }
11441181
11451182 var (
@@ -1185,7 +1222,7 @@ func (t *TxPublisher) createSweepTx(inputs []input.Input, changePkScript []byte,
11851222 // If there's a change amount, add it to the transaction.
11861223 changeAmtOpt .WhenSome (func (changeAmt btcutil.Amount ) {
11871224 sweepTx .AddTxOut (& wire.TxOut {
1188- PkScript : changePkScript ,
1225+ PkScript : changePkScript . DeliveryAddress ,
11891226 Value : int64 (changeAmt ),
11901227 })
11911228 })
@@ -1196,7 +1233,7 @@ func (t *TxPublisher) createSweepTx(inputs []input.Input, changePkScript []byte,
11961233
11971234 prevInputFetcher , err := input .MultiPrevOutFetcher (inputs )
11981235 if err != nil {
1199- return nil , 0 , fmt .Errorf ("error creating prev input fetcher " +
1236+ return nil , fmt .Errorf ("error creating prev input fetcher " +
12001237 "for hash cache: %v" , err )
12011238 }
12021239 hashCache := txscript .NewTxSigHashes (sweepTx , prevInputFetcher )
@@ -1224,14 +1261,17 @@ func (t *TxPublisher) createSweepTx(inputs []input.Input, changePkScript []byte,
12241261
12251262 for idx , inp := range idxs {
12261263 if err := addInputScript (idx , inp ); err != nil {
1227- return nil , 0 , err
1264+ return nil , err
12281265 }
12291266 }
12301267
12311268 log .Debugf ("Created sweep tx %v for inputs:\n %v" , sweepTx .TxHash (),
12321269 inputTypeSummary (inputs ))
12331270
1234- return sweepTx , txFee , nil
1271+ return & sweepTxCtx {
1272+ tx : sweepTx ,
1273+ fee : txFee ,
1274+ }, nil
12351275}
12361276
12371277// prepareSweepTx returns the tx fee, an optional change amount and an optional
@@ -1323,7 +1363,8 @@ func prepareSweepTx(inputs []input.Input, changePkScript []byte,
13231363 changeFloor := lnwallet .DustLimitForSize (len (changePkScript ))
13241364
13251365 // If the change amount is dust, we'll move it into the fees.
1326- if changeAmt < changeFloor {
1366+ switch {
1367+ case changeAmt < changeFloor :
13271368 log .Infof ("Change amt %v below dustlimit %v, not adding " +
13281369 "change output" , changeAmt , changeFloor )
13291370
@@ -1340,6 +1381,10 @@ func prepareSweepTx(inputs []input.Input, changePkScript []byte,
13401381
13411382 // Set the change amount to none.
13421383 changeAmtOpt = fn .None [btcutil.Amount ]()
1384+
1385+ // Otherwise, we'll actually recognize it as a change output.
1386+ default :
1387+ // TODO(roasbeef): Implement (later commit in this PR).
13431388 }
13441389
13451390 // Optionally set the locktime.
0 commit comments