Skip to content

Commit a67a666

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 3e36856 commit a67a666

File tree

3 files changed

+164
-27
lines changed

3 files changed

+164
-27
lines changed

sweepbatcher/sweep_batch.go

+31-10
Original file line numberDiff line numberDiff line change
@@ -1917,12 +1917,12 @@ func getFeePortionForSweep(spendTx *wire.MsgTx, numSweeps int,
19171917
}
19181918

19191919
// getFeePortionPaidBySweep returns the fee portion that the sweep should pay
1920-
// for the batch transaction. If the sweep is the first sweep in the batch, it
1920+
// for the batch transaction. If the sweep is the primary sweep in the batch, it
19211921
// pays the rounding difference.
19221922
func getFeePortionPaidBySweep(spendTx *wire.MsgTx, feePortionPerSweep,
1923-
roundingDiff btcutil.Amount, sweep *sweep) btcutil.Amount {
1923+
roundingDiff btcutil.Amount, primary bool) btcutil.Amount {
19241924

1925-
if bytes.Equal(spendTx.TxIn[0].SignatureScript, sweep.htlc.SigScript) {
1925+
if primary {
19261926
return feePortionPerSweep + roundingDiff
19271927
}
19281928

@@ -1974,22 +1974,43 @@ func (b *batch) handleSpend(ctx context.Context, spendTx *wire.MsgTx) error {
19741974
spendTx, len(notifyList), totalSweptAmt,
19751975
)
19761976

1977+
// Calculate fees per swaps. Only the first sweep in a swap has a
1978+
// notifier, so we calculate total fee per swap and send it to a sweep
1979+
// having that swap and a notifier.
1980+
swap2fee := make(map[lntypes.Hash]btcutil.Amount)
1981+
for _, sweep := range notifyList {
1982+
primary := sweep.outpoint == b.primarySweepID
1983+
1984+
swap2fee[sweep.swapHash] += getFeePortionPaidBySweep(
1985+
spendTx, feePortionPaidPerSweep,
1986+
roundingDifference, primary,
1987+
)
1988+
}
1989+
1990+
// Now send notifications to notifiers.
19771991
for _, sweep := range notifyList {
19781992
// If the sweep's notifier is empty then this means that a swap
1979-
// is not waiting to read an update from it, so we can skip
1980-
// the notification part.
1993+
// is not waiting to read an update from it or this is not the
1994+
// first sweep in a swap, so we can skip the notification part.
19811995
if sweep.notifier == nil ||
19821996
*sweep.notifier == (SpendNotifier{}) {
19831997

19841998
continue
19851999
}
19862000

2001+
// Make sure there is only one sweep with a notifier per swap
2002+
// hash, otherwise our fee calculation is incorrect.
2003+
fee, has := swap2fee[sweep.swapHash]
2004+
if !has {
2005+
return fmt.Errorf("no fee for swap %v; maybe "+
2006+
"multiple sweeps with a notifier per swap?",
2007+
sweep.swapHash)
2008+
}
2009+
delete(swap2fee, sweep.swapHash)
2010+
19872011
spendDetail := SpendDetail{
1988-
Tx: spendTx,
1989-
OnChainFeePortion: getFeePortionPaidBySweep(
1990-
spendTx, feePortionPaidPerSweep,
1991-
roundingDifference, &sweep,
1992-
),
2012+
Tx: spendTx,
2013+
OnChainFeePortion: fee,
19932014
}
19942015

19952016
// Dispatch the sweep notifier, we don't care about the outcome

sweepbatcher/sweep_batcher.go

+25-7
Original file line numberDiff line numberDiff line change
@@ -778,7 +778,7 @@ func (b *Batcher) handleSweeps(ctx context.Context, sweeps []*sweep,
778778
// and we won't be able to attach this sweep to it.
779779
if parentBatch.Confirmed {
780780
return b.monitorSpendAndNotify(
781-
ctx, sweep, parentBatch.ID, notifier,
781+
ctx, sweeps, parentBatch.ID, notifier,
782782
)
783783
}
784784
}
@@ -1061,7 +1061,7 @@ func (b *Batcher) FetchUnconfirmedBatches(ctx context.Context) ([]*batch,
10611061
// the response back to the response channel. It is called if the batch is fully
10621062
// confirmed and we just need to deliver the data back to the caller though
10631063
// SpendNotifier.
1064-
func (b *Batcher) monitorSpendAndNotify(ctx context.Context, sweep *sweep,
1064+
func (b *Batcher) monitorSpendAndNotify(ctx context.Context, sweeps []*sweep,
10651065
parentBatchID int32, notifier *SpendNotifier) error {
10661066

10671067
// If the caller has not provided a notifier, stop.
@@ -1079,6 +1079,17 @@ func (b *Batcher) monitorSpendAndNotify(ctx context.Context, sweep *sweep,
10791079
return err
10801080
}
10811081

1082+
// Find the primarySweepID.
1083+
dbSweeps, err := b.store.FetchBatchSweeps(ctx, parentBatchID)
1084+
if err != nil {
1085+
cancel()
1086+
1087+
return err
1088+
}
1089+
primarySweepID := dbSweeps[0].Outpoint
1090+
1091+
sweep := sweeps[0]
1092+
10821093
spendChan, spendErr, err := b.chainNotifier.RegisterSpendNtfn(
10831094
spendCtx, &sweep.outpoint, sweep.htlc.PkScript,
10841095
sweep.initiationHeight,
@@ -1099,6 +1110,7 @@ func (b *Batcher) monitorSpendAndNotify(ctx context.Context, sweep *sweep,
10991110
select {
11001111
case spend := <-spendChan:
11011112
spendTx := spend.SpendingTx
1113+
11021114
// Calculate the fee portion that each sweep should pay
11031115
// for the batch.
11041116
feePortionPerSweep, roundingDifference :=
@@ -1107,17 +1119,23 @@ func (b *Batcher) monitorSpendAndNotify(ctx context.Context, sweep *sweep,
11071119
totalSwept,
11081120
)
11091121

1110-
onChainFeePortion := getFeePortionPaidBySweep(
1111-
spendTx, feePortionPerSweep,
1112-
roundingDifference, sweep,
1113-
)
1122+
// Add onchain fee accross all the sweeps of the swap.
1123+
var fee btcutil.Amount
1124+
for _, s := range sweeps {
1125+
isFirst := s.outpoint == primarySweepID
1126+
1127+
fee += getFeePortionPaidBySweep(
1128+
spendTx, feePortionPerSweep,
1129+
roundingDifference, isFirst,
1130+
)
1131+
}
11141132

11151133
// Notify the requester of the spend with the spend
11161134
// details, including the fee portion for this
11171135
// particular sweep.
11181136
spendDetail := &SpendDetail{
11191137
Tx: spendTx,
1120-
OnChainFeePortion: onChainFeePortion,
1138+
OnChainFeePortion: fee,
11211139
}
11221140

11231141
select {

sweepbatcher/sweep_batcher_presigned_test.go

+108-10
Original file line numberDiff line numberDiff line change
@@ -1341,7 +1341,12 @@ func testPresigned_purging(t *testing.T, numSwaps, numConfirmedSwaps int,
13411341

13421342
require.LessOrEqual(t, numConfirmedSwaps, numSwaps)
13431343

1344-
const sweepsPerSwap = 2
1344+
const (
1345+
sweepsPerSwap = 2
1346+
feeRate = chainfee.SatPerKWeight(10_000)
1347+
swapAmount = 3_000_001
1348+
)
1349+
sweepAmounts := []btcutil.Amount{1_000_001, 2_000_000}
13451350

13461351
lnd := test.NewMockLnd()
13471352

@@ -1351,7 +1356,7 @@ func testPresigned_purging(t *testing.T, numSwaps, numConfirmedSwaps int,
13511356
customFeeRate := func(_ context.Context,
13521357
_ lntypes.Hash) (chainfee.SatPerKWeight, error) {
13531358

1354-
return chainfee.SatPerKWeight(10_000), nil
1359+
return feeRate, nil
13551360
}
13561361

13571362
presignedHelper := newMockPresignedHelper()
@@ -1369,12 +1374,17 @@ func testPresigned_purging(t *testing.T, numSwaps, numConfirmedSwaps int,
13691374
checkBatcherError(t, err)
13701375
}()
13711376

1377+
swapHashes := make([]lntypes.Hash, numSwaps)
1378+
groups := make([][]Input, numSwaps)
13721379
txs := make([]*wire.MsgTx, numSwaps)
13731380
allOps := make([]wire.OutPoint, 0, numSwaps*sweepsPerSwap)
1381+
spendChans := make([]<-chan *SpendDetail, numSwaps)
1382+
confChans := make([]<-chan *chainntnfs.TxConfirmation, numSwaps)
13741383

13751384
for i := range numSwaps {
13761385
// Create a swap of sweepsPerSwap sweeps.
13771386
swapHash := lntypes.Hash{byte(i + 1)}
1387+
swapHashes[i] = swapHash
13781388
ops := make([]wire.OutPoint, sweepsPerSwap)
13791389
group := make([]Input, sweepsPerSwap)
13801390
for j := range sweepsPerSwap {
@@ -1386,15 +1396,16 @@ func testPresigned_purging(t *testing.T, numSwaps, numConfirmedSwaps int,
13861396

13871397
group[j] = Input{
13881398
Outpoint: ops[j],
1389-
Value: btcutil.Amount(1_000_000 * (j + 1)),
1399+
Value: sweepAmounts[j],
13901400
}
13911401
}
1402+
groups[i] = group
13921403

13931404
// Create a swap in DB.
13941405
swap := &loopdb.LoopOutContract{
13951406
SwapContract: loopdb.SwapContract{
13961407
CltvExpiry: 111,
1397-
AmountRequested: 3_000_000,
1408+
AmountRequested: swapAmount,
13981409
ProtocolVersion: loopdb.ProtocolVersionMuSig2,
13991410
HtlcKeys: htlcKeys,
14001411

@@ -1421,11 +1432,24 @@ func testPresigned_purging(t *testing.T, numSwaps, numConfirmedSwaps int,
14211432
)
14221433
require.NoError(t, err)
14231434

1435+
// Create a spending notification channel.
1436+
spendChan := make(chan *SpendDetail, 1)
1437+
spendChans[i] = spendChan
1438+
confChan := make(chan *chainntnfs.TxConfirmation, 1)
1439+
confChans[i] = confChan
1440+
notifier := &SpendNotifier{
1441+
SpendChan: spendChan,
1442+
SpendErrChan: make(chan error, 1),
1443+
ConfChan: confChan,
1444+
ConfErrChan: make(chan error, 1),
1445+
QuitChan: make(chan bool, 1),
1446+
}
1447+
14241448
// Add the sweep, triggering the publish attempt.
14251449
require.NoError(t, batcher.AddSweep(&SweepRequest{
14261450
SwapHash: swapHash,
14271451
Inputs: group,
1428-
Notifier: &dummyNotifier,
1452+
Notifier: notifier,
14291453
}))
14301454

14311455
// For the first group it should register for the sweep's spend
@@ -1463,6 +1487,34 @@ func testPresigned_purging(t *testing.T, numSwaps, numConfirmedSwaps int,
14631487
SpendingHeight: int32(601 + numSwaps + 1),
14641488
}
14651489
lnd.SpendChannel <- spendDetail
1490+
1491+
// Calculate the expected on-chain fee of the swap.
1492+
wantFee := make([]btcutil.Amount, numConfirmedSwaps)
1493+
for i := range numConfirmedSwaps {
1494+
batchAmount := swapAmount * btcutil.Amount(numConfirmedSwaps)
1495+
txFee := batchAmount - btcutil.Amount(tx.TxOut[0].Value)
1496+
numConfirmedSweeps := numConfirmedSwaps * sweepsPerSwap
1497+
feePerSweep := txFee / btcutil.Amount(numConfirmedSweeps)
1498+
roundingDiff := txFee - feePerSweep*btcutil.Amount(
1499+
numConfirmedSweeps,
1500+
)
1501+
swapFee := feePerSweep * 2
1502+
1503+
// Add rounding difference to the first swap.
1504+
if i == 0 {
1505+
swapFee += roundingDiff
1506+
}
1507+
1508+
wantFee[i] = swapFee
1509+
}
1510+
1511+
// Make sure that notifiers of confirmed sweeps received notifications.
1512+
for i := range numConfirmedSwaps {
1513+
spend := <-spendChans[i]
1514+
require.Equal(t, txHash, spend.Tx.TxHash())
1515+
require.Equal(t, wantFee[i], spend.OnChainFeePortion)
1516+
}
1517+
14661518
<-lnd.RegisterConfChannel
14671519
require.NoError(t, lnd.NotifyHeight(
14681520
int32(601+numSwaps+1+batchConfHeight),
@@ -1474,16 +1526,61 @@ func testPresigned_purging(t *testing.T, numSwaps, numConfirmedSwaps int,
14741526
// CleanupTransactions is called here.
14751527
<-presignedHelper.cleanupCalled
14761528

1477-
// If all the swaps were confirmed, stop.
1478-
if numConfirmedSwaps == numSwaps {
1479-
return
1529+
// Make sure that notifiers of confirmed sweeps received notifications.
1530+
for i := range numConfirmedSwaps {
1531+
conf := <-confChans[i]
1532+
require.Equal(t, txHash, conf.Tx.TxHash())
14801533
}
14811534

14821535
// Missing sweeps in the confirmed transaction should be re-added to the
14831536
// batcher as new batch. The groups are added incrementally, so we need
14841537
// to wait until the batch reaches the expected size.
1485-
<-lnd.RegisterSpendChannel
1486-
<-lnd.TxPublishChannel
1538+
if numConfirmedSwaps != numSwaps {
1539+
<-lnd.RegisterSpendChannel
1540+
<-lnd.TxPublishChannel
1541+
}
1542+
1543+
// Now make sure that a correct spenf and conf contification is sent if
1544+
// AddSweep is called after confirming the sweeps.
1545+
for i := range numConfirmedSwaps {
1546+
// Create a spending notification channel.
1547+
spendChan := make(chan *SpendDetail, 1)
1548+
confChan := make(chan *chainntnfs.TxConfirmation)
1549+
notifier := &SpendNotifier{
1550+
SpendChan: spendChan,
1551+
SpendErrChan: make(chan error, 1),
1552+
ConfChan: confChan,
1553+
ConfErrChan: make(chan error, 1),
1554+
QuitChan: make(chan bool, 1),
1555+
}
1556+
1557+
// Add the sweep, triggering the publish attempt.
1558+
require.NoError(t, batcher.AddSweep(&SweepRequest{
1559+
SwapHash: swapHashes[i],
1560+
Inputs: groups[i],
1561+
Notifier: notifier,
1562+
}))
1563+
1564+
spendReg := <-lnd.RegisterSpendChannel
1565+
spendReg.SpendChannel <- spendDetail
1566+
1567+
spend := <-spendChan
1568+
require.Equal(t, txHash, spend.Tx.TxHash())
1569+
require.Equal(t, wantFee[i], spend.OnChainFeePortion)
1570+
1571+
<-lnd.RegisterConfChannel
1572+
lnd.ConfChannel <- &chainntnfs.TxConfirmation{
1573+
Tx: tx,
1574+
}
1575+
1576+
conf := <-confChan
1577+
require.Equal(t, tx.TxHash(), conf.Tx.TxHash())
1578+
}
1579+
1580+
// If all the swaps were confirmed, stop.
1581+
if numConfirmedSwaps == numSwaps {
1582+
return
1583+
}
14871584

14881585
// Wait to new batch to appear and to have the expected size.
14891586
wantSize := (numSwaps - numConfirmedSwaps) * sweepsPerSwap
@@ -1575,5 +1672,6 @@ func TestPresigned(t *testing.T) {
15751672
testPurging(3, 1)
15761673
testPurging(3, 2)
15771674
testPurging(5, 2)
1675+
testPurging(5, 3)
15781676
})
15791677
}

0 commit comments

Comments
 (0)