Skip to content

Commit b961e18

Browse files
committed
sweepbatcher: fix OnChainFeePortion values
There were two mistakes. In case of a swap with multiple sweeps only the fee of the first sweep of a swap was accounted. Rounding diff (the remainder) was attributed to all the sweeps rather than to the first (primary) sweep of the batch. The sweep to attribute the remainder was chosen by comparing SignatureScript which is always empty. New approach is to find the primary sweep and to compare its outpoint directly.
1 parent 650cf20 commit b961e18

File tree

3 files changed

+105
-24
lines changed

3 files changed

+105
-24
lines changed

sweepbatcher/sweep_batch.go

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1927,12 +1927,12 @@ func getFeePortionForSweep(spendTx *wire.MsgTx, numSweeps int,
19271927
}
19281928

19291929
// getFeePortionPaidBySweep returns the fee portion that the sweep should pay
1930-
// for the batch transaction. If the sweep is the first sweep in the batch, it
1930+
// for the batch transaction. If the sweep is the primary sweep in the batch, it
19311931
// pays the rounding difference.
1932-
func getFeePortionPaidBySweep(spendTx *wire.MsgTx, feePortionPerSweep,
1933-
roundingDiff btcutil.Amount, sweep *sweep) btcutil.Amount {
1932+
func getFeePortionPaidBySweep(feePortionPerSweep, roundingDiff btcutil.Amount,
1933+
primary bool) btcutil.Amount {
19341934

1935-
if bytes.Equal(spendTx.TxIn[0].SignatureScript, sweep.htlc.SigScript) {
1935+
if primary {
19361936
return feePortionPerSweep + roundingDiff
19371937
}
19381938

@@ -1983,22 +1983,42 @@ func (b *batch) handleSpend(ctx context.Context, spendTx *wire.MsgTx) error {
19831983
spendTx, len(notifyList), totalSweptAmt,
19841984
)
19851985

1986+
// Calculate fees per swaps. Only the first sweep in a swap has a
1987+
// notifier, so we calculate total fee per swap and send it to a sweep
1988+
// having that swap and a notifier.
1989+
swap2fee := make(map[lntypes.Hash]btcutil.Amount)
1990+
for _, sweep := range notifyList {
1991+
primary := sweep.outpoint == b.primarySweepID
1992+
1993+
swap2fee[sweep.swapHash] += getFeePortionPaidBySweep(
1994+
feePortionPaidPerSweep, roundingDifference, primary,
1995+
)
1996+
}
1997+
1998+
// Now send notifications to notifiers.
19861999
for _, sweep := range notifyList {
19872000
// If the sweep's notifier is empty then this means that a swap
1988-
// is not waiting to read an update from it, so we can skip
1989-
// the notification part.
2001+
// is not waiting to read an update from it or this is not the
2002+
// first sweep in a swap, so we can skip the notification part.
19902003
if sweep.notifier == nil ||
19912004
*sweep.notifier == (SpendNotifier{}) {
19922005

19932006
continue
19942007
}
19952008

2009+
// Make sure there is only one sweep with a notifier per swap
2010+
// hash, otherwise our fee calculation is incorrect.
2011+
fee, has := swap2fee[sweep.swapHash]
2012+
if !has {
2013+
return fmt.Errorf("no fee for swap %v; maybe "+
2014+
"multiple sweeps with a notifier per swap?",
2015+
sweep.swapHash)
2016+
}
2017+
delete(swap2fee, sweep.swapHash)
2018+
19962019
spendDetail := SpendDetail{
1997-
Tx: spendTx,
1998-
OnChainFeePortion: getFeePortionPaidBySweep(
1999-
spendTx, feePortionPaidPerSweep,
2000-
roundingDifference, &sweep,
2001-
),
2020+
Tx: spendTx,
2021+
OnChainFeePortion: fee,
20022022
}
20032023

20042024
// Dispatch the sweep notifier, we don't care about the outcome
@@ -2193,6 +2213,18 @@ func (b *batch) handleConf(ctx context.Context,
21932213
spendTx, len(b.sweeps), totalSweptAmt,
21942214
)
21952215

2216+
// Calculate fees per swaps. Only the first sweep in a swap has a
2217+
// notifier, so we calculate total fee per swap and send it to a sweep
2218+
// having that swap and a notifier.
2219+
swap2fee := make(map[lntypes.Hash]btcutil.Amount)
2220+
for _, sweep := range b.sweeps {
2221+
primary := sweep.outpoint == b.primarySweepID
2222+
2223+
swap2fee[sweep.swapHash] += getFeePortionPaidBySweep(
2224+
feePortionPaidPerSweep, roundingDifference, primary,
2225+
)
2226+
}
2227+
21962228
// Send the confirmation to all the notifiers.
21972229
for _, s := range b.sweeps {
21982230
// If the sweep's notifier is empty then this means that
@@ -2202,12 +2234,19 @@ func (b *batch) handleConf(ctx context.Context,
22022234
continue
22032235
}
22042236

2237+
// Make sure there is only one sweep with a notifier per swap
2238+
// hash, otherwise our fee calculation is incorrect.
2239+
fee, has := swap2fee[s.swapHash]
2240+
if !has {
2241+
return fmt.Errorf("no fee for swap %v; maybe "+
2242+
"multiple sweeps with a notifier per swap?",
2243+
s.swapHash)
2244+
}
2245+
delete(swap2fee, s.swapHash)
2246+
22052247
confDetail := &ConfDetail{
2206-
TxConfirmation: conf,
2207-
OnChainFeePortion: getFeePortionPaidBySweep(
2208-
spendTx, feePortionPaidPerSweep,
2209-
roundingDifference, &s,
2210-
),
2248+
TxConfirmation: conf,
2249+
OnChainFeePortion: fee,
22112250
}
22122251

22132252
// Notify the caller in a goroutine to avoid possible dead-lock.

sweepbatcher/sweep_batcher.go

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -886,7 +886,7 @@ func (b *Batcher) handleSweeps(ctx context.Context, sweeps []*sweep,
886886
// Instead we directly detect and return the spend here.
887887
if completed && parentBatch.Confirmed {
888888
return b.monitorSpendAndNotify(
889-
ctx, sweep, parentBatch.ID, notifier,
889+
ctx, sweeps, parentBatch.ID, notifier,
890890
)
891891
}
892892

@@ -1186,7 +1186,7 @@ func (b *Batcher) FetchUnconfirmedBatches(ctx context.Context) ([]*batch,
11861186
// the response back to the response channel. It is called if the batch is fully
11871187
// confirmed and we just need to deliver the data back to the caller though
11881188
// SpendNotifier.
1189-
func (b *Batcher) monitorSpendAndNotify(ctx context.Context, sweep *sweep,
1189+
func (b *Batcher) monitorSpendAndNotify(ctx context.Context, sweeps []*sweep,
11901190
parentBatchID int32, notifier *SpendNotifier) error {
11911191

11921192
// If the caller has not provided a notifier, stop.
@@ -1204,6 +1204,17 @@ func (b *Batcher) monitorSpendAndNotify(ctx context.Context, sweep *sweep,
12041204
return err
12051205
}
12061206

1207+
// Find the primarySweepID.
1208+
dbSweeps, err := b.store.FetchBatchSweeps(ctx, parentBatchID)
1209+
if err != nil {
1210+
cancel()
1211+
1212+
return err
1213+
}
1214+
primarySweepID := dbSweeps[0].Outpoint
1215+
1216+
sweep := sweeps[0]
1217+
12071218
spendChan, spendErr, err := b.chainNotifier.RegisterSpendNtfn(
12081219
spendCtx, &sweep.outpoint, sweep.htlc.PkScript,
12091220
sweep.initiationHeight,
@@ -1224,6 +1235,7 @@ func (b *Batcher) monitorSpendAndNotify(ctx context.Context, sweep *sweep,
12241235
select {
12251236
case spend := <-spendChan:
12261237
spendTx := spend.SpendingTx
1238+
12271239
// Calculate the fee portion that each sweep should pay
12281240
// for the batch.
12291241
feePortionPerSweep, roundingDifference :=
@@ -1232,25 +1244,31 @@ func (b *Batcher) monitorSpendAndNotify(ctx context.Context, sweep *sweep,
12321244
totalSwept,
12331245
)
12341246

1235-
onChainFeePortion := getFeePortionPaidBySweep(
1236-
spendTx, feePortionPerSweep,
1237-
roundingDifference, sweep,
1238-
)
1247+
// Sum onchain fee across all the sweeps of the swap.
1248+
var fee btcutil.Amount
1249+
for _, s := range sweeps {
1250+
isFirst := s.outpoint == primarySweepID
1251+
1252+
fee += getFeePortionPaidBySweep(
1253+
feePortionPerSweep, roundingDifference,
1254+
isFirst,
1255+
)
1256+
}
12391257

12401258
// Notify the requester of the spend with the spend
12411259
// details, including the fee portion for this
12421260
// particular sweep.
12431261
spendDetail := &SpendDetail{
12441262
Tx: spendTx,
1245-
OnChainFeePortion: onChainFeePortion,
1263+
OnChainFeePortion: fee,
12461264
}
12471265

12481266
select {
12491267
// Try to write the update to the notification channel.
12501268
case notifier.SpendChan <- spendDetail:
12511269
err := b.monitorConfAndNotify(
12521270
ctx, sweep, notifier, spendTx,
1253-
onChainFeePortion,
1271+
fee,
12541272
)
12551273
if err != nil {
12561274
b.writeToErrChan(

sweepbatcher/sweep_batcher_presigned_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,10 +1568,31 @@ func testPresigned_purging(t *testing.T, numSwaps, numConfirmedSwaps int,
15681568
}
15691569
lnd.SpendChannel <- spendDetail
15701570

1571+
// Calculate the expected on-chain fee of the swap.
1572+
wantFee := make([]btcutil.Amount, numConfirmedSwaps)
1573+
for i := range numConfirmedSwaps {
1574+
batchAmount := swapAmount * btcutil.Amount(numConfirmedSwaps)
1575+
txFee := batchAmount - btcutil.Amount(tx.TxOut[0].Value)
1576+
numConfirmedSweeps := numConfirmedSwaps * sweepsPerSwap
1577+
feePerSweep := txFee / btcutil.Amount(numConfirmedSweeps)
1578+
roundingDiff := txFee - feePerSweep*btcutil.Amount(
1579+
numConfirmedSweeps,
1580+
)
1581+
swapFee := feePerSweep * 2
1582+
1583+
// Add rounding difference to the first swap.
1584+
if i == 0 {
1585+
swapFee += roundingDiff
1586+
}
1587+
1588+
wantFee[i] = swapFee
1589+
}
1590+
15711591
// Make sure that notifiers of confirmed sweeps received notifications.
15721592
for i := range numConfirmedSwaps {
15731593
spend := <-spendChans[i]
15741594
require.Equal(t, txHash, spend.Tx.TxHash())
1595+
require.Equal(t, wantFee[i], spend.OnChainFeePortion)
15751596
}
15761597

15771598
<-lnd.RegisterConfChannel
@@ -1594,6 +1615,7 @@ func testPresigned_purging(t *testing.T, numSwaps, numConfirmedSwaps int,
15941615
for i := range numConfirmedSwaps {
15951616
conf := <-confChans[i]
15961617
require.Equal(t, txHash, conf.Tx.TxHash())
1618+
require.Equal(t, wantFee[i], conf.OnChainFeePortion)
15971619
}
15981620

15991621
if !online && numConfirmedSwaps != numSwaps {
@@ -1631,6 +1653,7 @@ func testPresigned_purging(t *testing.T, numSwaps, numConfirmedSwaps int,
16311653

16321654
spend := <-spendChan
16331655
require.Equal(t, txHash, spend.Tx.TxHash())
1656+
require.Equal(t, wantFee[i], spend.OnChainFeePortion)
16341657

16351658
<-lnd.RegisterConfChannel
16361659
lnd.ConfChannel <- &chainntnfs.TxConfirmation{
@@ -1639,6 +1662,7 @@ func testPresigned_purging(t *testing.T, numSwaps, numConfirmedSwaps int,
16391662

16401663
conf := <-confChan
16411664
require.Equal(t, tx.TxHash(), conf.Tx.TxHash())
1665+
require.Equal(t, wantFee[i], conf.OnChainFeePortion)
16421666
}
16431667

16441668
// If all the swaps were confirmed, stop.

0 commit comments

Comments
 (0)