Skip to content

Commit c0418e3

Browse files
committed
sweepbatcher: consider change in presigning and batch tx
Presigning sweeps takes change outputs into account. Each sweep belonging to the same sweep group points to the same change output, if existent. 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 26b38cf commit c0418e3

9 files changed

+787
-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)