@@ -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
+ // If the change output is already in the map, skip it.
593
+ if _ , exists := changeOutputs [sweep .change ]; exists {
594
+ continue
595
+ }
596
+
597
+ // Convert the change output's pkScript to an
598
+ // address.
599
+ changePkScript , err := txscript .ParsePkScript (
600
+ sweep .change .PkScript ,
601
+ )
602
+ if err != nil {
603
+ return nil , fmt .Errorf ("failed to parse " +
604
+ "change output pkScript: %w" , err )
605
+ }
606
+
607
+ address , err := changePkScript .Address (chainParams )
608
+ if err != nil {
609
+ return nil , fmt .Errorf ("pkScript.Address " +
610
+ "failed for pkScript %x returned for " +
611
+ "change output: %w" ,
612
+ sweep .change .PkScript , err )
613
+ }
614
+
615
+ changeOutputs [sweep .change ] = address
616
+ }
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,23 @@ 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
+ }
609
668
}
610
669
670
+ // The first output is always the batch output.
671
+ batchOutput := signedTx .TxOut [0 ]
672
+
611
673
// Find the feerate of signedTx.
612
- fee := inputAmt - btcutil .Amount (signedOut .Value )
674
+ fee := inputAmt - btcutil .Amount (batchOutput .Value )
613
675
weight := lntypes .WeightUnit (
614
676
blockchain .GetTransactionWeight (btcutil .NewTx (signedTx )),
615
677
)
0 commit comments