Skip to content

sweepbatcher: fixes for presigned mode #952

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions loopdb/sqlc/batch.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion loopdb/sqlc/querier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 0 additions & 8 deletions loopdb/sqlc/queries/batch.sql
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,6 @@ UPDATE sweep_batches SET
last_rbf_sat_per_kw = $6
WHERE id = $1;

-- name: ConfirmBatch :exec
UPDATE
sweep_batches
SET
confirmed = TRUE
WHERE
id = $1;

-- name: UpsertSweep :exec
INSERT INTO sweeps (
swap_hash,
Expand Down
2 changes: 1 addition & 1 deletion loopout.go
Original file line number Diff line number Diff line change
Expand Up @@ -1148,7 +1148,7 @@ func (s *loopOutSwap) waitForHtlcSpendConfirmedV2(globalCtx context.Context,
quitChan := make(chan bool, 1)

defer func() {
quitChan <- true
close(quitChan)
}()

notifier := sweepbatcher.SpendNotifier{
Expand Down
4 changes: 3 additions & 1 deletion loopout_feerate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/swap"
"github.com/lightninglabs/loop/utils"
Expand Down Expand Up @@ -71,7 +72,8 @@ func newLoopOutSweepFeerateProvider(sweeper sweeper,

// GetMinFeeRate returns minimum required feerate for a sweep by swap hash.
func (p *loopOutSweepFeerateProvider) GetMinFeeRate(ctx context.Context,
swapHash lntypes.Hash) (chainfee.SatPerKWeight, error) {
swapHash lntypes.Hash,
_ wire.OutPoint) (chainfee.SatPerKWeight, error) {

_, feeRate, err := p.GetConfTargetAndFeeRate(ctx, swapHash)

Expand Down
55 changes: 31 additions & 24 deletions sweepbatcher/presigned.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sweepbatcher
import (
"bytes"
"context"
"encoding/hex"
"fmt"

"github.com/btcsuite/btcd/blockchain"
Expand Down Expand Up @@ -36,13 +37,7 @@ func (b *batch) ensurePresigned(ctx context.Context, newSweeps []*sweep,
// presignedTxChecker has methods to check if the inputs are presigned.
type presignedTxChecker interface {
destPkScripter

// SignTx signs an unsigned transaction or returns a pre-signed tx.
// It is only called with loadOnly=true by ensurePresigned.
SignTx(ctx context.Context, primarySweepID wire.OutPoint,
tx *wire.MsgTx, inputAmt btcutil.Amount,
minRelayFee, feeRate chainfee.SatPerKWeight,
loadOnly bool) (*wire.MsgTx, error)
presigner
}

// ensurePresigned checks that there is a presigned transaction spending the
Expand Down Expand Up @@ -251,7 +246,7 @@ func (b *batch) presign(ctx context.Context, newSweeps []*sweep) error {

// Cache the destination address.
destAddr, err := getPresignedSweepsDestAddr(
ctx, b.cfg.presignedHelper, b.primarySweepID,
ctx, b.cfg.presignedHelper, primarySweepID,
b.cfg.chainParams,
)
if err != nil {
Expand Down Expand Up @@ -287,11 +282,12 @@ func (b *batch) presign(ctx context.Context, newSweeps []*sweep) error {

// presigner tries to presign a batch transaction.
type presigner interface {
// Presign tries to presign a batch transaction. If the method returns
// nil, it is guaranteed that future calls to SignTx on this set of
// sweeps return valid signed transactions.
Presign(ctx context.Context, primarySweepID wire.OutPoint,
tx *wire.MsgTx, inputAmt btcutil.Amount) error
// SignTx signs an unsigned transaction or returns a pre-signed tx.
// It is only called with loadOnly=true by ensurePresigned.
SignTx(ctx context.Context, primarySweepID wire.OutPoint,
tx *wire.MsgTx, inputAmt btcutil.Amount,
minRelayFee, feeRate chainfee.SatPerKWeight,
loadOnly bool) (*wire.MsgTx, error)
}

// presign tries to presign batch sweep transactions of the sweeps. It signs
Expand Down Expand Up @@ -370,7 +366,14 @@ func presign(ctx context.Context, presigner presigner, destAddr btcutil.Address,
}

// Try to presign this transaction.
err = presigner.Presign(ctx, primarySweepID, tx, batchAmt)
const (
loadOnly = false
minRelayFee = chainfee.AbsoluteFeePerKwFloor
)
_, err = presigner.SignTx(
ctx, primarySweepID, tx, batchAmt, minRelayFee, fr,
loadOnly,
)
if err != nil {
return fmt.Errorf("failed to presign unsigned tx %v "+
"for feeRate %v: %w", tx.TxHash(), fr, err)
Expand Down Expand Up @@ -405,9 +408,16 @@ func (b *batch) publishPresigned(ctx context.Context) (btcutil.Amount, error,
}
}

// Determine the current minimum relay fee based on our chain backend.
minRelayFee, err := b.wallet.MinRelayFee(ctx)
if err != nil {
return 0, fmt.Errorf("failed to get minRelayFee: %w", err),
false
}

// Cache current height and desired feerate of the batch.
currentHeight := b.currentHeight
feeRate := b.rbfCache.FeeRate
feeRate := max(b.rbfCache.FeeRate, minRelayFee)

// Append this sweep to an array of sweeps. This is needed to keep the
// order of sweeps stored, as iterating the sweeps map does not
Expand Down Expand Up @@ -445,13 +455,6 @@ func (b *batch) publishPresigned(ctx context.Context) (btcutil.Amount, error,
batchAmt += sweep.value
}

// Determine the current minimum relay fee based on our chain backend.
minRelayFee, err := b.wallet.MinRelayFee(ctx)
if err != nil {
return 0, fmt.Errorf("failed to get minRelayFee: %w", err),
false
}

// Get a pre-signed transaction.
const loadOnly = false
signedTx, err := b.cfg.presignedHelper.SignTx(
Expand Down Expand Up @@ -506,6 +509,9 @@ func (b *batch) publishPresigned(ctx context.Context) (btcutil.Amount, error,
b.batchTxid = &txHash
b.batchPkScript = tx.TxOut[0].PkScript

// Update cached FeeRate not to broadcast a tx with lower feeRate.
b.rbfCache.FeeRate = max(b.rbfCache.FeeRate, signedFeeRate)

return fee, nil, true
}

Expand Down Expand Up @@ -597,8 +603,9 @@ func CheckSignedTx(unsignedTx, signedTx *wire.MsgTx, inputAmt btcutil.Amount,
unsignedOut := unsignedTx.TxOut[0]
signedOut := signedTx.TxOut[0]
if !bytes.Equal(unsignedOut.PkScript, signedOut.PkScript) {
return fmt.Errorf("mismatch of output pkScript: %v, %v",
unsignedOut.PkScript, signedOut.PkScript)
return fmt.Errorf("mismatch of output pkScript: %s, %s",
hex.EncodeToString(unsignedOut.PkScript),
hex.EncodeToString(signedOut.PkScript))
}

// Find the feerate of signedTx.
Expand Down
20 changes: 13 additions & 7 deletions sweepbatcher/presigned_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -553,24 +553,30 @@ type mockPresigner struct {
failAt int
}

// Presign memorizes the value of the output and fails if the number of
// SignTx memorizes the value of the output and fails if the number of
// calls previously made is failAt.
func (p *mockPresigner) Presign(ctx context.Context,
primarySweepID wire.OutPoint, tx *wire.MsgTx,
inputAmt btcutil.Amount) error {
func (p *mockPresigner) SignTx(ctx context.Context,
primarySweepID wire.OutPoint, tx *wire.MsgTx, inputAmt btcutil.Amount,
minRelayFee, feeRate chainfee.SatPerKWeight,
loadOnly bool) (*wire.MsgTx, error) {

if ctx.Err() != nil {
return nil, ctx.Err()
}

if !hasInput(tx, primarySweepID) {
return fmt.Errorf("primarySweepID %v not in tx", primarySweepID)
return nil, fmt.Errorf("primarySweepID %v not in tx",
primarySweepID)
}

if len(p.outputs)+1 == p.failAt {
return fmt.Errorf("test error in Presign")
return nil, fmt.Errorf("test error in SignTx")
}

p.outputs = append(p.outputs, btcutil.Amount(tx.TxOut[0].Value))
p.lockTimes = append(p.lockTimes, tx.LockTime)

return nil
return tx, nil
}

// TestPresign checks that function presign presigns correct set of transactions
Expand Down
10 changes: 1 addition & 9 deletions sweepbatcher/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ import (
// Querier is the interface that contains all the queries generated
// by sqlc for sweep batcher.
type Querier interface {
// ConfirmBatch confirms a batch by setting the state to confirmed.
ConfirmBatch(ctx context.Context, id int32) error

// GetBatchSweeps fetches all the sweeps that are part a batch.
GetBatchSweeps(ctx context.Context, batchID int32) (
[]sqlc.Sweep, error)
Expand Down Expand Up @@ -136,11 +133,6 @@ func (s *SQLStore) UpdateSweepBatch(ctx context.Context, batch *dbBatch) error {
return s.baseDb.UpdateBatch(ctx, batchToUpdateArgs(*batch))
}

// ConfirmBatch confirms a batch by setting the state to confirmed.
func (s *SQLStore) ConfirmBatch(ctx context.Context, id int32) error {
return s.baseDb.ConfirmBatch(ctx, id)
}

// FetchBatchSweeps fetches all the sweeps that are part a batch.
func (s *SQLStore) FetchBatchSweeps(ctx context.Context, id int32) (
[]*dbSweep, error) {
Expand Down Expand Up @@ -248,7 +240,7 @@ type dbSweep struct {
// Amount is the amount of the sweep.
Amount btcutil.Amount

// Completed indicates whether this sweep is completed.
// Completed indicates whether this sweep is fully-confirmed.
Completed bool
}

Expand Down
16 changes: 0 additions & 16 deletions sweepbatcher/store_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,22 +81,6 @@ func (s *StoreMock) UpdateSweepBatch(ctx context.Context,
return nil
}

// ConfirmBatch confirms a batch.
func (s *StoreMock) ConfirmBatch(ctx context.Context, id int32) error {
s.mu.Lock()
defer s.mu.Unlock()

batch, ok := s.batches[id]
if !ok {
return errors.New("batch not found")
}

batch.Confirmed = true
s.batches[batch.ID] = batch

return nil
}

// FetchBatchSweeps fetches all the sweeps that belong to a batch.
func (s *StoreMock) FetchBatchSweeps(ctx context.Context,
id int32) ([]*dbSweep, error) {
Expand Down
Loading