Skip to content

Commit 4d91489

Browse files
committed
sweepbatcher: consider change in presigning and batch tx
Presigning sweeps takes change outputs into account. Each primary deposit id of a sweep group points to an optional change output. sweepbatcher.presign scans all passed sweeps for change outputs and passes them to constructUnsignedTx. Optional change of a swap is encoded in its sweeps as a pointer to the same change output. This change is taken into account when constructing the unsigned batch transaction when it comes to tx weight and outputs.
1 parent c87bd5d commit 4d91489

9 files changed

+859
-108
lines changed

sweepbatcher/greedy_batch_selection.go

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

213+
// Add change output weights.
214+
for _, s := range batch.sweeps {
215+
if s.change != nil {
216+
weight.AddOutput(s.change.PkScript)
217+
}
218+
}
219+
213220
// Add inputs.
214221
for _, sweep := range batch.sweeps {
215222
if sweep.nonCoopHint || sweep.coopFailed {

sweepbatcher/greedy_batch_selection_test.go

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/btcsuite/btcd/btcutil"
77
"github.com/btcsuite/btcd/chaincfg"
88
"github.com/btcsuite/btcd/chaincfg/chainhash"
9+
"github.com/btcsuite/btcd/txscript"
910
"github.com/btcsuite/btcd/wire"
1011
"github.com/lightninglabs/loop/swap"
1112
"github.com/lightningnetwork/lnd/input"
@@ -20,20 +21,23 @@ const (
2021
highFeeRate = chainfee.SatPerKWeight(30000)
2122

2223
coopInputWeight = lntypes.WeightUnit(230)
24+
batchOutputWeight = lntypes.WeightUnit(343)
2325
nonCoopInputWeight = lntypes.WeightUnit(393)
2426
nonCoopPenalty = nonCoopInputWeight - coopInputWeight
2527
coopNewBatchWeight = lntypes.WeightUnit(444)
2628
nonCoopNewBatchWeight = coopNewBatchWeight + nonCoopPenalty
29+
changeWeight = lntypes.WeightUnit(input.P2TROutputSize)
2730

2831
// p2pkhDiscount is weight discount P2PKH output has over P2TR output.
2932
p2pkhDiscount = lntypes.WeightUnit(
3033
input.P2TROutputSize-input.P2PKHOutputSize,
3134
) * 4
3235

33-
coopTwoSweepBatchWeight = coopNewBatchWeight + coopInputWeight
34-
nonCoopTwoSweepBatchWeight = coopTwoSweepBatchWeight + 2*nonCoopPenalty
35-
v2v3BatchWeight = nonCoopTwoSweepBatchWeight - 25
36-
mixedTwoSweepBatchWeight = coopTwoSweepBatchWeight + nonCoopPenalty
36+
coopTwoSweepBatchWeight = coopNewBatchWeight + coopInputWeight
37+
coopSingleSweepChangeBatchWeight = coopInputWeight + batchOutputWeight + changeWeight
38+
nonCoopTwoSweepBatchWeight = coopTwoSweepBatchWeight + 2*nonCoopPenalty
39+
v2v3BatchWeight = nonCoopTwoSweepBatchWeight - 25
40+
mixedTwoSweepBatchWeight = coopTwoSweepBatchWeight + nonCoopPenalty
3741
)
3842

3943
// testHtlcV2SuccessEstimator adds weight of non-cooperative input to estimator
@@ -265,6 +269,13 @@ func TestEstimateBatchWeight(t *testing.T) {
265269
se3 := testHtlcV3SuccessEstimator
266270
trAddr := (*btcutil.AddressTaproot)(nil)
267271

272+
changeAddr := "bc1pdx9ggvtjjcpaqfqk375qhdmzx9xu8dcu7w94lqfcxhh0rj" +
273+
"lwyyeq5ryn6r"
274+
changeAddress, err := btcutil.DecodeAddress(changeAddr, nil)
275+
require.NoError(t, err)
276+
changePkscript, err := txscript.PayToAddrScript(changeAddress)
277+
require.NoError(t, err)
278+
268279
cases := []struct {
269280
name string
270281
batch *batch
@@ -290,6 +301,29 @@ func TestEstimateBatchWeight(t *testing.T) {
290301
},
291302
},
292303

304+
{
305+
name: "one sweep regular batch with change",
306+
batch: &batch{
307+
id: 1,
308+
rbfCache: rbfCache{
309+
FeeRate: lowFeeRate,
310+
},
311+
sweeps: map[wire.OutPoint]sweep{
312+
outpoint1: {
313+
htlcSuccessEstimator: se3,
314+
change: &wire.TxOut{
315+
PkScript: changePkscript,
316+
},
317+
},
318+
},
319+
},
320+
wantBatchFeeDetails: feeDetails{
321+
BatchId: 1,
322+
FeeRate: lowFeeRate,
323+
Weight: coopSingleSweepChangeBatchWeight,
324+
},
325+
},
326+
293327
{
294328
name: "two sweeps regular batch",
295329
batch: &batch{

sweepbatcher/presigned.go

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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

@@ -493,10 +494,12 @@ func (b *batch) publishPresigned(ctx context.Context) (btcutil.Amount, error,
493494
signedFeeRate := chainfee.NewSatPerKWeight(fee, realWeight)
494495

495496
numSweeps := len(tx.TxIn)
497+
numChange := len(tx.TxOut) - 1
496498
b.Infof("attempting to publish custom signed tx=%v, desiredFeerate=%v,"+
497-
" signedFeeRate=%v, weight=%v, fee=%v, sweeps=%d, destAddr=%s",
499+
" signedFeeRate=%v, weight=%v, fee=%v, sweeps=%d, "+
500+
"changeOutputs=%d, destAddr=%s",
498501
txHash, feeRate, signedFeeRate, realWeight, fee, numSweeps,
499-
address)
502+
numChange, address)
500503
b.debugLogTx("serialized batch", tx)
501504

502505
// Publish the transaction.
@@ -593,23 +596,31 @@ func CheckSignedTx(unsignedTx, signedTx *wire.MsgTx, inputAmt btcutil.Amount,
593596
}
594597

595598
// 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",
599+
if len(unsignedTx.TxOut) != len(signedTx.TxOut) {
600+
return fmt.Errorf("unsigned tx has %d outputs, signed tx has "+
601+
"%d outputs, should be equal", len(unsignedTx.TxOut),
602602
len(signedTx.TxOut))
603603
}
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)
604+
for i, o := range unsignedTx.TxOut {
605+
if !bytes.Equal(o.PkScript, signedTx.TxOut[i].PkScript) {
606+
return fmt.Errorf("mismatch of output pkScript: %x, %x",
607+
o.PkScript, signedTx.TxOut[i].PkScript)
608+
}
609+
if i != 0 && o.Value != signedTx.TxOut[i].Value {
610+
return fmt.Errorf("mismatch of output value: %d, %d",
611+
o.Value, signedTx.TxOut[i].Value)
612+
}
613+
}
614+
615+
// Calculate the total value of all outputs to help determine the
616+
// transaction fee.
617+
totalOutputValue := btcutil.Amount(0)
618+
for _, o := range signedTx.TxOut {
619+
totalOutputValue += btcutil.Amount(o.Value)
609620
}
610621

611622
// Find the feerate of signedTx.
612-
fee := inputAmt - btcutil.Amount(signedOut.Value)
623+
fee := inputAmt - totalOutputValue
613624
weight := lntypes.WeightUnit(
614625
blockchain.GetTransactionWeight(btcutil.NewTx(signedTx)),
615626
)

sweepbatcher/presigned_test.go

Lines changed: 149 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,7 +1460,8 @@ func TestCheckSignedTx(t *testing.T) {
14601460
},
14611461
inputAmt: 3_000_000,
14621462
minRelayFee: 253,
1463-
wantErr: "unsigned tx has 2 outputs, want 1",
1463+
wantErr: "unsigned tx has 2 outputs, signed tx " +
1464+
"has 1 outputs, should be equal",
14641465
},
14651466

14661467
{
@@ -1517,7 +1518,153 @@ func TestCheckSignedTx(t *testing.T) {
15171518
},
15181519
inputAmt: 3_000_000,
15191520
minRelayFee: 253,
1520-
wantErr: "the signed tx has 2 outputs, want 1",
1521+
wantErr: "unsigned tx has 1 outputs, signed tx " +
1522+
"has 2 outputs, should be equal",
1523+
},
1524+
1525+
{
1526+
name: "pkscript mismatch",
1527+
unsignedTx: &wire.MsgTx{
1528+
Version: 2,
1529+
TxIn: []*wire.TxIn{
1530+
{
1531+
PreviousOutPoint: op2,
1532+
Sequence: 2,
1533+
},
1534+
},
1535+
TxOut: []*wire.TxOut{
1536+
{
1537+
Value: 2999374,
1538+
PkScript: batchPkScript,
1539+
},
1540+
},
1541+
LockTime: 800_000,
1542+
},
1543+
signedTx: &wire.MsgTx{
1544+
Version: 2,
1545+
TxIn: []*wire.TxIn{
1546+
{
1547+
PreviousOutPoint: op2,
1548+
Sequence: 2,
1549+
Witness: wire.TxWitness{
1550+
[]byte("test"),
1551+
},
1552+
},
1553+
},
1554+
TxOut: []*wire.TxOut{
1555+
{
1556+
Value: 2999374,
1557+
PkScript: []byte{0xaf, 0xfe}, // Just to make it different.
1558+
},
1559+
},
1560+
LockTime: 799_999,
1561+
},
1562+
inputAmt: 3_000_000,
1563+
minRelayFee: 253,
1564+
wantErr: "mismatch of output pkScript",
1565+
},
1566+
1567+
{
1568+
name: "value mismatch, first output",
1569+
unsignedTx: &wire.MsgTx{
1570+
Version: 2,
1571+
TxIn: []*wire.TxIn{
1572+
{
1573+
PreviousOutPoint: op2,
1574+
Sequence: 2,
1575+
},
1576+
},
1577+
TxOut: []*wire.TxOut{
1578+
{
1579+
Value: 2999374,
1580+
PkScript: batchPkScript,
1581+
},
1582+
},
1583+
LockTime: 800_000,
1584+
},
1585+
signedTx: &wire.MsgTx{
1586+
Version: 2,
1587+
TxIn: []*wire.TxIn{
1588+
{
1589+
PreviousOutPoint: op2,
1590+
Sequence: 2,
1591+
Witness: wire.TxWitness{
1592+
[]byte("test"),
1593+
},
1594+
},
1595+
},
1596+
TxOut: []*wire.TxOut{
1597+
{
1598+
Value: 1_337_000, // Just to make it different.
1599+
PkScript: batchPkScript,
1600+
},
1601+
},
1602+
LockTime: 799_999,
1603+
},
1604+
inputAmt: 3_000_000,
1605+
minRelayFee: 253,
1606+
wantErr: "",
1607+
},
1608+
1609+
{
1610+
name: "value mismatch, change output",
1611+
unsignedTx: &wire.MsgTx{
1612+
Version: 2,
1613+
TxIn: []*wire.TxIn{
1614+
{
1615+
PreviousOutPoint: op2,
1616+
Sequence: 2,
1617+
},
1618+
{
1619+
PreviousOutPoint: op1,
1620+
Sequence: 2,
1621+
},
1622+
},
1623+
TxOut: []*wire.TxOut{
1624+
{
1625+
Value: 2999374,
1626+
PkScript: batchPkScript,
1627+
},
1628+
{
1629+
Value: 1_337_000,
1630+
PkScript: batchPkScript,
1631+
},
1632+
},
1633+
LockTime: 800_000,
1634+
},
1635+
signedTx: &wire.MsgTx{
1636+
Version: 2,
1637+
TxIn: []*wire.TxIn{
1638+
{
1639+
PreviousOutPoint: op2,
1640+
Sequence: 2,
1641+
Witness: wire.TxWitness{
1642+
[]byte("test"),
1643+
},
1644+
},
1645+
{
1646+
PreviousOutPoint: op1,
1647+
Sequence: 2,
1648+
Witness: wire.TxWitness{
1649+
[]byte("test"),
1650+
},
1651+
},
1652+
},
1653+
TxOut: []*wire.TxOut{
1654+
{
1655+
Value: 2_493_300,
1656+
PkScript: batchPkScript,
1657+
},
1658+
{
1659+
Value: 1_338, // Just to make it different.
1660+
PkScript: batchPkScript,
1661+
},
1662+
},
1663+
LockTime: 799_999,
1664+
},
1665+
inputAmt: 3_000_000,
1666+
minRelayFee: 253,
1667+
wantErr: "mismatch of output value",
15211668
},
15221669

15231670
{

0 commit comments

Comments
 (0)