@@ -51,6 +51,7 @@ func ensurePresigned(ctx context.Context, newSweeps []*sweep,
51
51
outpoint : s .outpoint ,
52
52
value : s .value ,
53
53
presigned : s .presigned ,
54
+ change : s .change ,
54
55
}
55
56
}
56
57
@@ -66,14 +67,20 @@ func ensurePresigned(ctx context.Context, newSweeps []*sweep,
66
67
return fmt .Errorf ("failed to find destination address: %w" , err )
67
68
}
68
69
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
+
69
76
// Set LockTime to 0. It is not critical.
70
77
const currentHeight = 0
71
78
72
79
// Check if we can sign with minimum fee rate.
73
80
const feeRate = chainfee .FeePerKwFloor
74
81
75
82
tx , _ , _ , _ , err := constructUnsignedTx (
76
- sweeps , destAddr , currentHeight , feeRate ,
83
+ sweeps , destAddr , changeOutputs , currentHeight , feeRate ,
77
84
)
78
85
if err != nil {
79
86
return fmt .Errorf ("failed to construct unsigned tx " +
@@ -257,7 +264,7 @@ func (b *batch) presign(ctx context.Context, newSweeps []*sweep) error {
257
264
258
265
err = presign (
259
266
ctx , b .cfg .presignedHelper , destAddr , primarySweepID ,
260
- sweeps , nextBlockFeeRate ,
267
+ sweeps , nextBlockFeeRate , b . cfg . chainParams ,
261
268
)
262
269
if err != nil {
263
270
return fmt .Errorf ("failed to presign a transaction " +
@@ -299,7 +306,8 @@ type presigner interface {
299
306
// 10x of the current next block feerate.
300
307
func presign (ctx context.Context , presigner presigner , destAddr btcutil.Address ,
301
308
primarySweepID wire.OutPoint , sweeps []sweep ,
302
- nextBlockFeeRate chainfee.SatPerKWeight ) error {
309
+ nextBlockFeeRate chainfee.SatPerKWeight ,
310
+ chainParams * chaincfg.Params ) error {
303
311
304
312
if presigner == nil {
305
313
return fmt .Errorf ("presigner is not installed" )
@@ -328,6 +336,12 @@ func presign(ctx context.Context, presigner presigner, destAddr btcutil.Address,
328
336
return fmt .Errorf ("timeout is invalid: %d" , timeout )
329
337
}
330
338
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
+
331
345
// Go from the floor (1.01 sat/vbyte) to 2k sat/vbyte with step of 1.2x.
332
346
const (
333
347
start = chainfee .FeePerKwFloor
@@ -353,7 +367,7 @@ func presign(ctx context.Context, presigner presigner, destAddr btcutil.Address,
353
367
for fr := start ; fr <= stop ; fr = (fr * factorPPM ) / 1_000_000 {
354
368
// Construct an unsigned transaction for this fee rate.
355
369
tx , _ , feeForWeight , fee , err := constructUnsignedTx (
356
- sweeps , destAddr , currentHeight , fr ,
370
+ sweeps , destAddr , changeOutputs , currentHeight , fr ,
357
371
)
358
372
if err != nil {
359
373
return fmt .Errorf ("failed to construct unsigned tx " +
@@ -438,9 +452,15 @@ func (b *batch) publishPresigned(ctx context.Context) (btcutil.Amount, error,
438
452
err ), false
439
453
}
440
454
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
+
441
461
// Construct unsigned batch transaction.
442
462
tx , weight , _ , fee , err := constructUnsignedTx (
443
- sweeps , address , currentHeight , feeRate ,
463
+ sweeps , address , changeOutputs , currentHeight , feeRate ,
444
464
)
445
465
if err != nil {
446
466
return 0 , fmt .Errorf ("failed to construct tx: %w" , err ),
@@ -493,10 +513,12 @@ func (b *batch) publishPresigned(ctx context.Context) (btcutil.Amount, error,
493
513
signedFeeRate := chainfee .NewSatPerKWeight (fee , realWeight )
494
514
495
515
numSweeps := len (tx .TxIn )
516
+ numChange := len (tx .TxOut ) - 1
496
517
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" ,
498
520
txHash , feeRate , signedFeeRate , realWeight , fee , numSweeps ,
499
- address )
521
+ numChange , address )
500
522
b .debugLogTx ("serialized batch" , tx )
501
523
502
524
// Publish the transaction.
@@ -557,6 +579,46 @@ func getPresignedSweepsDestAddr(ctx context.Context, helper destPkScripter,
557
579
return address , nil
558
580
}
559
581
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
+
560
622
// CheckSignedTx makes sure that signedTx matches the unsignedTx. It checks
561
623
// according to criteria specified in the description of PresignedHelper.SignTx.
562
624
func CheckSignedTx (unsignedTx , signedTx * wire.MsgTx , inputAmt btcutil.Amount ,
@@ -593,23 +655,31 @@ func CheckSignedTx(unsignedTx, signedTx *wire.MsgTx, inputAmt btcutil.Amount,
593
655
}
594
656
595
657
// 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 ),
602
661
len (signedTx .TxOut ))
603
662
}
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 )
609
679
}
610
680
611
681
// Find the feerate of signedTx.
612
- fee := inputAmt - btcutil .Amount (signedOut .Value )
682
+ fee := inputAmt - btcutil .Amount (batchOutput .Value ) - totalChangeValue
613
683
weight := lntypes .WeightUnit (
614
684
blockchain .GetTransactionWeight (btcutil .NewTx (signedTx )),
615
685
)
0 commit comments