Skip to content

Commit 29161bb

Browse files
committed
sweepbatcher: consolidate identical change pkscripts
batch separate change outputs with identical pkscripts are tallied up and consolidated to a single change output.
1 parent 54652dc commit 29161bb

File tree

5 files changed

+319
-10
lines changed

5 files changed

+319
-10
lines changed

sweepbatcher/greedy_batch_selection.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,11 +210,21 @@ func estimateBatchWeight(batch *batch) (feeDetails, error) {
210210
err)
211211
}
212212

213-
// Add change output weights.
213+
// Add change output weights. Change outputs with identical pkscript
214+
// will be consolidated into a single output.
215+
changeOutputs := make(map[string]struct{})
214216
for _, s := range batch.sweeps {
215-
if s.change != nil {
216-
weight.AddOutput(s.change.PkScript)
217+
if s.change == nil {
218+
continue
217219
}
220+
221+
pkScriptString := string(s.change.PkScript)
222+
if _, has := changeOutputs[pkScriptString]; has {
223+
continue
224+
}
225+
226+
weight.AddOutput(s.change.PkScript)
227+
changeOutputs[pkScriptString] = struct{}{}
218228
}
219229

220230
// Add inputs.

sweepbatcher/greedy_batch_selection_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const (
3636

3737
coopTwoSweepBatchWeight = coopNewBatchWeight + coopInputWeight
3838
coopSingleSweepChangeBatchWeight = coopInputWeight + batchOutputWeight + changeOutputWeight
39+
coopDoubleSweepChangeBatchWeight = 2*coopInputWeight + batchOutputWeight + changeOutputWeight
3940
nonCoopTwoSweepBatchWeight = coopTwoSweepBatchWeight + 2*nonCoopPenalty
4041
v2v3BatchWeight = nonCoopTwoSweepBatchWeight - 25
4142
mixedTwoSweepBatchWeight = coopTwoSweepBatchWeight + nonCoopPenalty
@@ -325,6 +326,35 @@ func TestEstimateBatchWeight(t *testing.T) {
325326
},
326327
},
327328

329+
{
330+
name: "two sweeps regular batch with identical change",
331+
batch: &batch{
332+
id: 1,
333+
rbfCache: rbfCache{
334+
FeeRate: lowFeeRate,
335+
},
336+
sweeps: map[wire.OutPoint]sweep{
337+
outpoint1: {
338+
htlcSuccessEstimator: se3,
339+
change: &wire.TxOut{
340+
PkScript: changePkscript,
341+
},
342+
},
343+
outpoint2: {
344+
htlcSuccessEstimator: se3,
345+
change: &wire.TxOut{
346+
PkScript: changePkscript,
347+
},
348+
},
349+
},
350+
},
351+
wantBatchFeeDetails: feeDetails{
352+
BatchId: 1,
353+
FeeRate: lowFeeRate,
354+
Weight: coopDoubleSweepChangeBatchWeight,
355+
},
356+
},
357+
328358
{
329359
name: "two sweeps regular batch",
330360
batch: &batch{

sweepbatcher/sweep_batch.go

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"errors"
88
"fmt"
99
"math"
10+
"sort"
1011
"strings"
1112
"sync"
1213
"sync/atomic"
@@ -1311,10 +1312,22 @@ func constructUnsignedTx(sweeps []sweep, address btcutil.Address,
13111312
LockTime: uint32(currentHeight),
13121313
}
13131314

1314-
var changeOutputs []*wire.TxOut
1315-
for _, sweep := range sweeps {
1316-
if sweep.change != nil {
1317-
changeOutputs = append(changeOutputs, sweep.change)
1315+
// Consolidate change outputs with identical pkscript.
1316+
changeOutputs := make(map[string]*wire.TxOut)
1317+
for _, s := range sweeps {
1318+
if s.change == nil {
1319+
continue
1320+
}
1321+
1322+
stringPkScript := string(s.change.PkScript)
1323+
if _, has := changeOutputs[stringPkScript]; has {
1324+
changeOutputs[stringPkScript].Value += s.change.Value
1325+
continue
1326+
}
1327+
1328+
changeOutputs[stringPkScript] = &wire.TxOut{
1329+
Value: s.change.Value,
1330+
PkScript: s.change.PkScript,
13181331
}
13191332
}
13201333

@@ -1425,8 +1438,19 @@ func constructUnsignedTx(sweeps []sweep, address btcutil.Address,
14251438
PkScript: batchPkScript,
14261439
Value: int64(batchAmt-fee) - sumChange,
14271440
})
1428-
// Then add change outputs.
1429-
for _, txOut := range changeOutputs {
1441+
// Then add change outputs. Sort the keys first to make tests
1442+
// deterministic.
1443+
keys := make([]string, 0, len(changeOutputs))
1444+
for k := range changeOutputs {
1445+
keys = append(keys, k)
1446+
}
1447+
1448+
// Sort the keys
1449+
sort.Strings(keys)
1450+
1451+
// Add change outputs orderly.
1452+
for _, k := range keys {
1453+
txOut := changeOutputs[k]
14301454
batchTx.AddTxOut(&wire.TxOut{
14311455
PkScript: txOut.PkScript,
14321456
Value: txOut.Value,

sweepbatcher/sweep_batch_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ func TestConstructUnsignedTx(t *testing.T) {
5555
PkScript: change1Pkscript,
5656
}
5757

58+
change1PrimeAddr := "bc1pdx9ggvtjjcpaqfqk375qhdmzx9xu8dcu7w94lqfcxhh0rj" +
59+
"lwyyeq5ryn6r"
60+
change1PrimeAddress, err := btcutil.DecodeAddress(change1PrimeAddr, nil)
61+
require.NoError(t, err)
62+
change1PrimePkscript, err := txscript.PayToAddrScript(change1PrimeAddress)
63+
require.NoError(t, err)
64+
change1Prime := &wire.TxOut{
65+
Value: 200_000,
66+
PkScript: change1PrimePkscript,
67+
}
68+
5869
change2Addr := "bc1psw0nrrulq4pgyuyk09a3wsutygltys4gxjjw3zl2uz4ep8pa" +
5970
"r2vsvntfe0"
6071
change2Address, err := btcutil.DecodeAddress(change2Addr, nil)
@@ -395,6 +406,108 @@ func TestConstructUnsignedTx(t *testing.T) {
395406
wantFee: 1248,
396407
},
397408

409+
{
410+
name: "identical change pkscripts",
411+
sweeps: []sweep{
412+
{
413+
outpoint: op1,
414+
value: 1_000_000,
415+
},
416+
{
417+
outpoint: op2,
418+
value: 2_000_000,
419+
change: change1,
420+
},
421+
{
422+
outpoint: op3,
423+
value: 3_000_000,
424+
change: change1,
425+
},
426+
},
427+
address: p2trAddress,
428+
currentHeight: 800_000,
429+
feeRate: 1000,
430+
wantTx: &wire.MsgTx{
431+
Version: 2,
432+
LockTime: 800_000,
433+
TxIn: []*wire.TxIn{
434+
{
435+
PreviousOutPoint: op1,
436+
},
437+
{
438+
PreviousOutPoint: op2,
439+
},
440+
{
441+
PreviousOutPoint: op3,
442+
},
443+
},
444+
TxOut: []*wire.TxOut{
445+
{
446+
Value: 5_798_924,
447+
PkScript: p2trPkScript,
448+
},
449+
{
450+
Value: 2 * change1.Value,
451+
PkScript: change1.PkScript,
452+
},
453+
},
454+
},
455+
wantWeight: 1076,
456+
wantFeeForWeight: 1076,
457+
wantFee: 1076,
458+
},
459+
460+
{
461+
name: "identical change pkscripts different values",
462+
sweeps: []sweep{
463+
{
464+
outpoint: op1,
465+
value: 1_000_000,
466+
},
467+
{
468+
outpoint: op2,
469+
value: 2_000_000,
470+
change: change1,
471+
},
472+
{
473+
outpoint: op3,
474+
value: 3_000_000,
475+
change: change1Prime,
476+
},
477+
},
478+
address: p2trAddress,
479+
currentHeight: 800_000,
480+
feeRate: 1000,
481+
wantTx: &wire.MsgTx{
482+
Version: 2,
483+
LockTime: 800_000,
484+
TxIn: []*wire.TxIn{
485+
{
486+
PreviousOutPoint: op1,
487+
},
488+
{
489+
PreviousOutPoint: op2,
490+
},
491+
{
492+
PreviousOutPoint: op3,
493+
},
494+
},
495+
TxOut: []*wire.TxOut{
496+
{
497+
Value: 5_698_924,
498+
PkScript: p2trPkScript,
499+
},
500+
{
501+
Value: change1.Value + change1Prime.Value,
502+
PkScript: change1.PkScript,
503+
},
504+
},
505+
},
506+
wantWeight: 1076,
507+
wantFeeForWeight: 1076,
508+
wantFee: 1076,
509+
},
510+
398511
{
399512
name: "change exceeds input value",
400513
sweeps: []sweep{

0 commit comments

Comments
 (0)