Skip to content

Commit e1ddb50

Browse files
committed
loopout+sweepbatcher: calculate the per sweep onchain fees correctly
Previously we'd report the fees per sweep as the total sweep cost of a batch. With this change the reported cost will be the proportional fee which should be equal for all sweeps except if there's any rounding difference in which case that is paid by the sweep belonging to the first input of the batch tx.
1 parent b4ebb19 commit e1ddb50

File tree

4 files changed

+103
-17
lines changed

4 files changed

+103
-17
lines changed

loopout.go

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ func (s *loopOutSwap) executeSwap(globalCtx context.Context) error {
514514
}
515515

516516
// Try to spend htlc and continue (rbf) until a spend has confirmed.
517-
spendTx, err := s.waitForHtlcSpendConfirmedV2(
517+
spend, err := s.waitForHtlcSpendConfirmedV2(
518518
globalCtx, *htlcOutpoint, htlcValue,
519519
)
520520
if err != nil {
@@ -523,15 +523,15 @@ func (s *loopOutSwap) executeSwap(globalCtx context.Context) error {
523523

524524
// If spend details are nil, we resolved the swap without waiting for
525525
// its spend, so we can exit.
526-
if spendTx == nil {
526+
if spend == nil {
527527
return nil
528528
}
529529

530530
// Inspect witness stack to see if it is a success transaction. We
531531
// don't just try to match with the hash of our sweep tx, because it
532532
// may be swept by a different (fee) sweep tx from a previous run.
533533
htlcInput, err := swap.GetTxInputByOutpoint(
534-
spendTx, htlcOutpoint,
534+
spend.Tx, htlcOutpoint,
535535
)
536536
if err != nil {
537537
return err
@@ -540,9 +540,7 @@ func (s *loopOutSwap) executeSwap(globalCtx context.Context) error {
540540
sweepSuccessful := s.htlc.IsSuccessWitness(htlcInput.Witness)
541541
if sweepSuccessful {
542542
s.cost.Server -= htlcValue
543-
544-
s.cost.Onchain = htlcValue -
545-
btcutil.Amount(spendTx.TxOut[0].Value)
543+
s.cost.Onchain = spend.OnChainFeePortion
546544

547545
s.state = loopdb.StateSuccess
548546
} else {
@@ -1005,9 +1003,9 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) (
10051003
// sweep or a server revocation tx.
10061004
func (s *loopOutSwap) waitForHtlcSpendConfirmedV2(globalCtx context.Context,
10071005
htlcOutpoint wire.OutPoint, htlcValue btcutil.Amount) (
1008-
*wire.MsgTx, error) {
1006+
*sweepbatcher.SpendDetail, error) {
10091007

1010-
spendChan := make(chan *wire.MsgTx)
1008+
spendChan := make(chan *sweepbatcher.SpendDetail)
10111009
spendErrChan := make(chan error, 1)
10121010
quitChan := make(chan bool, 1)
10131011

@@ -1054,10 +1052,10 @@ func (s *loopOutSwap) waitForHtlcSpendConfirmedV2(globalCtx context.Context,
10541052
for {
10551053
select {
10561054
// Htlc spend, break loop.
1057-
case spendTx := <-spendChan:
1058-
s.log.Infof("Htlc spend by tx: %v", spendTx.TxHash())
1055+
case spend := <-spendChan:
1056+
s.log.Infof("Htlc spend by tx: %v", spend.Tx.TxHash())
10591057

1060-
return spendTx, nil
1058+
return spend, nil
10611059

10621060
// Spend notification error.
10631061
case err := <-spendErrChan:

sweepbatcher/sweep_batch.go

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,6 +1136,33 @@ func (b *batch) monitorConfirmations(ctx context.Context) error {
11361136
return nil
11371137
}
11381138

1139+
// getFeePortionForSweep calculates the fee portion that each sweep should pay
1140+
// for the batch transaction. The fee is split evenly among the sweeps, If the
1141+
// fee cannot be split evenly, the remainder is paid by the first sweep.
1142+
func getFeePortionForSweep(spendTx *wire.MsgTx, numSweeps int,
1143+
totalSweptAmt btcutil.Amount) (btcutil.Amount, btcutil.Amount) {
1144+
1145+
totalFee := spendTx.TxOut[0].Value - int64(totalSweptAmt)
1146+
feePortionPerSweep := (int64(totalSweptAmt) -
1147+
spendTx.TxOut[0].Value) / int64(numSweeps)
1148+
roundingDiff := totalFee - (int64(numSweeps) * feePortionPerSweep)
1149+
1150+
return btcutil.Amount(feePortionPerSweep), btcutil.Amount(roundingDiff)
1151+
}
1152+
1153+
// getFeePortionPaidBySweep returns the fee portion that the sweep should pay
1154+
// for the batch transaction. If the sweep is the first sweep in the batch, it
1155+
// pays the rounding difference.
1156+
func getFeePortionPaidBySweep(spendTx *wire.MsgTx, feePortionPerSweep,
1157+
roundingDiff btcutil.Amount, sweep *sweep) btcutil.Amount {
1158+
1159+
if bytes.Equal(spendTx.TxIn[0].SignatureScript, sweep.htlc.SigScript) {
1160+
return feePortionPerSweep + roundingDiff
1161+
}
1162+
1163+
return feePortionPerSweep
1164+
}
1165+
11391166
// handleSpend handles a spend notification.
11401167
func (b *batch) handleSpend(ctx context.Context, spendTx *wire.MsgTx) error {
11411168
var (
@@ -1151,12 +1178,14 @@ func (b *batch) handleSpend(ctx context.Context, spendTx *wire.MsgTx) error {
11511178
// sweeps that did not make it to the confirmed transaction and feed
11521179
// them back to the batcher. This will ensure that the sweeps will enter
11531180
// a new batch instead of remaining dangling.
1181+
var totalSweptAmt btcutil.Amount
11541182
for _, sweep := range b.sweeps {
11551183
found := false
11561184

11571185
for _, txIn := range spendTx.TxIn {
11581186
if txIn.PreviousOutPoint == sweep.outpoint {
11591187
found = true
1188+
totalSweptAmt += sweep.value
11601189
notifyList = append(notifyList, sweep)
11611190
}
11621191
}
@@ -1176,7 +1205,13 @@ func (b *batch) handleSpend(ctx context.Context, spendTx *wire.MsgTx) error {
11761205
}
11771206
}
11781207

1208+
// Calculate the fee portion that each sweep should pay for the batch.
1209+
feePortionPaidPerSweep, roundingDifference := getFeePortionForSweep(
1210+
spendTx, len(notifyList), totalSweptAmt,
1211+
)
1212+
11791213
for _, sweep := range notifyList {
1214+
sweep := sweep
11801215
// Save the sweep as completed.
11811216
err := b.persistSweep(ctx, sweep, true)
11821217
if err != nil {
@@ -1192,9 +1227,17 @@ func (b *batch) handleSpend(ctx context.Context, spendTx *wire.MsgTx) error {
11921227
continue
11931228
}
11941229

1230+
spendDetail := SpendDetail{
1231+
Tx: spendTx,
1232+
OnChainFeePortion: getFeePortionPaidBySweep(
1233+
spendTx, feePortionPaidPerSweep,
1234+
roundingDifference, &sweep,
1235+
),
1236+
}
1237+
11951238
// Dispatch the sweep notifier, we don't care about the outcome
11961239
// of this action so we don't wait for it.
1197-
go notifySweepSpend(ctx, sweep, spendTx)
1240+
go sweep.notifySweepSpend(ctx, &spendDetail)
11981241
}
11991242

12001243
// Proceed with purging the sweeps. This will feed the sweeps that
@@ -1318,10 +1361,12 @@ func (b *batch) insertAndAcquireID(ctx context.Context) (int32, error) {
13181361
}
13191362

13201363
// notifySweepSpend writes the spendTx to the sweep's notifier channel.
1321-
func notifySweepSpend(ctx context.Context, s sweep, spendTx *wire.MsgTx) {
1364+
func (s *sweep) notifySweepSpend(ctx context.Context,
1365+
spendDetail *SpendDetail) {
1366+
13221367
select {
13231368
// Try to write the update to the notification channel.
1324-
case s.notifier.SpendChan <- spendTx:
1369+
case s.notifier.SpendChan <- spendDetail:
13251370

13261371
// If a quit signal was provided by the swap, continue.
13271372
case <-s.notifier.QuitChan:

sweepbatcher/sweep_batcher.go

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,22 @@ type SweepRequest struct {
110110
Notifier *SpendNotifier
111111
}
112112

113+
type SpendDetail struct {
114+
// Tx is the transaction that spent the outpoint.
115+
Tx *wire.MsgTx
116+
117+
// OnChainFeePortion is the fee portion that was paid to get this sweep
118+
// confirmed on chain. This is the difference between the value of the
119+
// outpoint and the value of all sweeps that were included in the batch
120+
// divided by the number of sweeps.
121+
OnChainFeePortion btcutil.Amount
122+
}
123+
113124
// SpendNotifier is a notifier that is used to notify the requester of a sweep
114125
// that the sweep was successful.
115126
type SpendNotifier struct {
116127
// SpendChan is a channel where the spend details are received.
117-
SpendChan chan *wire.MsgTx
128+
SpendChan chan *SpendDetail
118129

119130
// SpendErrChan is a channel where spend errors are received.
120131
SpendErrChan chan error
@@ -521,6 +532,18 @@ func (b *Batcher) monitorSpendAndNotify(ctx context.Context, sweep *sweep,
521532
spendCtx, cancel := context.WithCancel(ctx)
522533
defer cancel()
523534

535+
// First get the batch that completed the sweep.
536+
parentBatch, err := b.store.GetParentBatch(ctx, sweep.swapHash)
537+
if err != nil {
538+
return err
539+
}
540+
541+
// Then we get the total amount that was swept by the batch.
542+
totalSwept, err := b.store.TotalSweptAmount(ctx, parentBatch.ID)
543+
if err != nil {
544+
return err
545+
}
546+
524547
spendChan, spendErr, err := b.chainNotifier.RegisterSpendNtfn(
525548
spendCtx, &sweep.outpoint, sweep.htlc.PkScript,
526549
sweep.initiationHeight,
@@ -538,8 +561,28 @@ func (b *Batcher) monitorSpendAndNotify(ctx context.Context, sweep *sweep,
538561
for {
539562
select {
540563
case spend := <-spendChan:
564+
spendTx := spend.SpendingTx
565+
// Calculate the fee portion that each sweep
566+
// should pay for the batch.
567+
feePortionPerSweep, roundingDifference :=
568+
getFeePortionForSweep(
569+
spendTx, len(spendTx.TxIn),
570+
totalSwept,
571+
)
572+
573+
// Notify the requester of the spend
574+
// with the spend details, including the fee
575+
// portion for this particular sweep.
576+
spendDetail := &SpendDetail{
577+
Tx: spendTx,
578+
OnChainFeePortion: getFeePortionPaidBySweep( // nolint:lll
579+
spendTx, feePortionPerSweep,
580+
roundingDifference, sweep,
581+
),
582+
}
583+
541584
select {
542-
case notifier.SpendChan <- spend.SpendingTx:
585+
case notifier.SpendChan <- spendDetail:
543586
case <-ctx.Done():
544587
}
545588

sweepbatcher/sweep_batcher_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func testMuSig2SignSweep(ctx context.Context,
3838
}
3939

4040
var dummyNotifier = SpendNotifier{
41-
SpendChan: make(chan *wire.MsgTx, ntfnBufferSize),
41+
SpendChan: make(chan *SpendDetail, ntfnBufferSize),
4242
SpendErrChan: make(chan error, ntfnBufferSize),
4343
QuitChan: make(chan bool, ntfnBufferSize),
4444
}

0 commit comments

Comments
 (0)