@@ -51,6 +51,7 @@ func ensurePresigned(ctx context.Context, newSweeps []*sweep,
5151 outpoint : s .outpoint ,
5252 value : s .value ,
5353 presigned : s .presigned ,
54+ change : s .change ,
5455 }
5556 }
5657
@@ -66,14 +67,20 @@ func ensurePresigned(ctx context.Context, newSweeps []*sweep,
6667 return fmt .Errorf ("failed to find destination address: %w" , err )
6768 }
6869
70+ // Get the change outputs for each sweep group.
71+ changeOutputs , err := getChangeOutputs (sweeps , chainParams )
72+ if err != nil {
73+ return fmt .Errorf ("failed to get change outputs: %w" , err )
74+ }
75+
6976 // Set LockTime to 0. It is not critical.
7077 const currentHeight = 0
7178
7279 // Check if we can sign with minimum fee rate.
7380 const feeRate = chainfee .FeePerKwFloor
7481
7582 tx , _ , _ , _ , err := constructUnsignedTx (
76- sweeps , destAddr , currentHeight , feeRate ,
83+ sweeps , destAddr , changeOutputs , currentHeight , feeRate ,
7784 )
7885 if err != nil {
7986 return fmt .Errorf ("failed to construct unsigned tx " +
@@ -257,7 +264,7 @@ func (b *batch) presign(ctx context.Context, newSweeps []*sweep) error {
257264
258265 err = presign (
259266 ctx , b .cfg .presignedHelper , destAddr , primarySweepID ,
260- sweeps , nextBlockFeeRate ,
267+ sweeps , nextBlockFeeRate , b . cfg . chainParams ,
261268 )
262269 if err != nil {
263270 return fmt .Errorf ("failed to presign a transaction " +
@@ -299,7 +306,8 @@ type presigner interface {
299306// 10x of the current next block feerate.
300307func presign (ctx context.Context , presigner presigner , destAddr btcutil.Address ,
301308 primarySweepID wire.OutPoint , sweeps []sweep ,
302- nextBlockFeeRate chainfee.SatPerKWeight ) error {
309+ nextBlockFeeRate chainfee.SatPerKWeight ,
310+ chainParams * chaincfg.Params ) error {
303311
304312 if presigner == nil {
305313 return fmt .Errorf ("presigner is not installed" )
@@ -328,6 +336,12 @@ func presign(ctx context.Context, presigner presigner, destAddr btcutil.Address,
328336 return fmt .Errorf ("timeout is invalid: %d" , timeout )
329337 }
330338
339+ // Get the change outputs of each sweep group.
340+ changeOutputs , err := getChangeOutputs (sweeps , chainParams )
341+ if err != nil {
342+ return fmt .Errorf ("failed to get change outputs: %w" , err )
343+ }
344+
331345 // Go from the floor (1.01 sat/vbyte) to 2k sat/vbyte with step of 1.2x.
332346 const (
333347 start = chainfee .FeePerKwFloor
@@ -353,7 +367,7 @@ func presign(ctx context.Context, presigner presigner, destAddr btcutil.Address,
353367 for fr := start ; fr <= stop ; fr = (fr * factorPPM ) / 1_000_000 {
354368 // Construct an unsigned transaction for this fee rate.
355369 tx , _ , feeForWeight , fee , err := constructUnsignedTx (
356- sweeps , destAddr , currentHeight , fr ,
370+ sweeps , destAddr , changeOutputs , currentHeight , fr ,
357371 )
358372 if err != nil {
359373 return fmt .Errorf ("failed to construct unsigned tx " +
@@ -438,9 +452,15 @@ func (b *batch) publishPresigned(ctx context.Context) (btcutil.Amount, error,
438452 err ), false
439453 }
440454
455+ changeOutputs , err := getChangeOutputs (sweeps , b .cfg .chainParams )
456+ if err != nil {
457+ return 0 , fmt .Errorf ("failed to get change outputs: %w" , err ),
458+ false
459+ }
460+
441461 // Construct unsigned batch transaction.
442462 tx , weight , _ , fee , err := constructUnsignedTx (
443- sweeps , address , currentHeight , feeRate ,
463+ sweeps , address , changeOutputs , currentHeight , feeRate ,
444464 )
445465 if err != nil {
446466 return 0 , fmt .Errorf ("failed to construct tx: %w" , err ),
@@ -493,10 +513,12 @@ func (b *batch) publishPresigned(ctx context.Context) (btcutil.Amount, error,
493513 signedFeeRate := chainfee .NewSatPerKWeight (fee , realWeight )
494514
495515 numSweeps := len (tx .TxIn )
516+ numChange := len (tx .TxOut ) - 1
496517 b .Infof ("attempting to publish custom signed tx=%v, desiredFeerate=%v," +
497- " signedFeeRate=%v, weight=%v, fee=%v, sweeps=%d, destAddr=%s" ,
518+ " signedFeeRate=%v, weight=%v, fee=%v, sweeps=%d, " +
519+ "changeOutputs=%d, destAddr=%s" ,
498520 txHash , feeRate , signedFeeRate , realWeight , fee , numSweeps ,
499- address )
521+ numChange , address )
500522 b .debugLogTx ("serialized batch" , tx )
501523
502524 // Publish the transaction.
@@ -557,6 +579,46 @@ func getPresignedSweepsDestAddr(ctx context.Context, helper destPkScripter,
557579 return address , nil
558580}
559581
582+ // getChangeOutputs retrieves the change output references of each sweep and
583+ // de-duplicates them. The function must be used in presigned mode only.
584+ func getChangeOutputs (sweeps []sweep , chainParams * chaincfg.Params ) (
585+ map [* wire.TxOut ]btcutil.Address , error ) {
586+
587+ changeOutputs := make (map [* wire.TxOut ]btcutil.Address )
588+ for _ , sweep := range sweeps {
589+ // If the sweep has a change output, add it to the changeOutputs
590+ // map to avoid duplicates.
591+ if sweep .change == nil {
592+ continue
593+ }
594+
595+ // If the change output is already in the map, skip it.
596+ if _ , exists := changeOutputs [sweep .change ]; exists {
597+ continue
598+ }
599+
600+ // Convert the change output's pkScript to an address.
601+ changePkScript , err := txscript .ParsePkScript (
602+ sweep .change .PkScript ,
603+ )
604+ if err != nil {
605+ return nil , fmt .Errorf ("failed to parse change " +
606+ "output pkScript: %w" , err )
607+ }
608+
609+ address , err := changePkScript .Address (chainParams )
610+ if err != nil {
611+ return nil , fmt .Errorf ("pkScript.Address failed for " +
612+ "pkScript %x returned for change output: %w" ,
613+ sweep .change .PkScript , err )
614+ }
615+
616+ changeOutputs [sweep .change ] = address
617+ }
618+
619+ return changeOutputs , nil
620+ }
621+
560622// CheckSignedTx makes sure that signedTx matches the unsignedTx. It checks
561623// according to criteria specified in the description of PresignedHelper.SignTx.
562624func CheckSignedTx (unsignedTx , signedTx * wire.MsgTx , inputAmt btcutil.Amount ,
@@ -593,23 +655,31 @@ func CheckSignedTx(unsignedTx, signedTx *wire.MsgTx, inputAmt btcutil.Amount,
593655 }
594656
595657 // Compare outputs.
596- if len (unsignedTx .TxOut ) != 1 {
597- return fmt .Errorf ("unsigned tx has %d outputs, want 1" ,
598- len (unsignedTx .TxOut ))
599- }
600- if len (signedTx .TxOut ) != 1 {
601- return fmt .Errorf ("the signed tx has %d outputs, want 1" ,
658+ if len (unsignedTx .TxOut ) != len (signedTx .TxOut ) {
659+ return fmt .Errorf ("unsigned tx has %d outputs, signed tx has " +
660+ "%d outputs, should be equal" , len (unsignedTx .TxOut ),
602661 len (signedTx .TxOut ))
603662 }
604- unsignedOut := unsignedTx .TxOut [0 ]
605- signedOut := signedTx .TxOut [0 ]
606- if ! bytes .Equal (unsignedOut .PkScript , signedOut .PkScript ) {
607- return fmt .Errorf ("mismatch of output pkScript: %x, %x" ,
608- unsignedOut .PkScript , signedOut .PkScript )
663+ for i , o := range unsignedTx .TxOut {
664+ if ! bytes .Equal (o .PkScript , signedTx .TxOut [i ].PkScript ) {
665+ return fmt .Errorf ("mismatch of output pkScript: %x, %x" ,
666+ o .PkScript , signedTx .TxOut [i ].PkScript )
667+ }
668+ }
669+
670+ // The first output is always the batch output.
671+ // TODO(hieblmi): ensure this.
672+ batchOutput := signedTx .TxOut [0 ]
673+
674+ // Calculate the total value of change outputs to help determine the
675+ // transaction fee.
676+ totalChangeValue := btcutil .Amount (0 )
677+ for i := 1 ; i < len (signedTx .TxOut ); i ++ {
678+ totalChangeValue += btcutil .Amount (signedTx .TxOut [i ].Value )
609679 }
610680
611681 // Find the feerate of signedTx.
612- fee := inputAmt - btcutil .Amount (signedOut .Value )
682+ fee := inputAmt - btcutil .Amount (batchOutput .Value ) - totalChangeValue
613683 weight := lntypes .WeightUnit (
614684 blockchain .GetTransactionWeight (btcutil .NewTx (signedTx )),
615685 )
0 commit comments