From abf3bb4abfaa00e54c5d178fc71a489c00f6fe5a Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 14 Oct 2025 20:37:20 -0300 Subject: [PATCH 1/2] sweepbatcher: presign transactions in parallel If a SignTx call is slow, the whole presign() function could timeout if all the calls are done sequentially. In this commit each call is done in a separate goroutine to reduce total latency. --- sweepbatcher/presigned.go | 29 +++++++++++++++++++------- sweepbatcher/presigned_test.go | 38 ++++++++++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/sweepbatcher/presigned.go b/sweepbatcher/presigned.go index e7eed04eb..870e82a21 100644 --- a/sweepbatcher/presigned.go +++ b/sweepbatcher/presigned.go @@ -12,6 +12,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "golang.org/x/sync/errgroup" ) // ensurePresigned checks that there is a presigned transaction spending the @@ -371,6 +372,8 @@ func presign(ctx context.Context, presigner presigner, destAddr btcutil.Address, // Set LockTime to 0. It is not critical. const currentHeight = 0 + eg, grCtx := errgroup.WithContext(ctx) + for fr := start; fr <= stop; fr = (fr * factorPPM) / 1_000_000 { // Construct an unsigned transaction for this fee rate. tx, _, feeForWeight, fee, err := constructUnsignedTx( @@ -389,14 +392,19 @@ func presign(ctx context.Context, presigner presigner, destAddr btcutil.Address, // Try to presign this transaction. const loadOnly = false - _, err = presigner.SignTx( - ctx, primarySweepID, tx, batchAmt, minRelayFeeRate, fr, - loadOnly, - ) - if err != nil { - return fmt.Errorf("failed to presign unsigned tx %v "+ - "for feeRate %v: %w", tx.TxHash(), fr, err) - } + eg.Go(func() error { + _, err := presigner.SignTx( + grCtx, primarySweepID, tx, batchAmt, + minRelayFeeRate, fr, loadOnly, + ) + if err != nil { + return fmt.Errorf("failed to presign unsigned "+ + "tx %v for feeRate %v: %w", tx.TxHash(), + fr, err) + } + + return nil + }) // If fee was clamped, stop here, because fee rate won't grow. if fee < feeForWeight { @@ -404,6 +412,11 @@ func presign(ctx context.Context, presigner presigner, destAddr btcutil.Address, } } + if err := eg.Wait(); err != nil { + return fmt.Errorf("presigning of batch of primarySweepID %v "+ + "failed: %w", primarySweepID, err) + } + return nil } diff --git a/sweepbatcher/presigned_test.go b/sweepbatcher/presigned_test.go index 06220e696..bd37d83ad 100644 --- a/sweepbatcher/presigned_test.go +++ b/sweepbatcher/presigned_test.go @@ -3,6 +3,7 @@ package sweepbatcher import ( "context" "fmt" + "sync" "testing" "github.com/btcsuite/btcd/btcutil" @@ -630,6 +631,9 @@ type mockPresigner struct { // failAt is optional index of a call at which it fails, 1 based. failAt int + + // mu protects the state. + mu sync.Mutex } // SignTx memorizes the value of the output and fails if the number of @@ -639,6 +643,9 @@ func (p *mockPresigner) SignTx(ctx context.Context, minRelayFee, feeRate chainfee.SatPerKWeight, loadOnly bool) (*wire.MsgTx, error) { + p.mu.Lock() + defer p.mu.Unlock() + if ctx.Err() != nil { return nil, ctx.Err() } @@ -1037,7 +1044,8 @@ func TestPresign(t *testing.T) { }, { - name: "small amount => fewer steps until clamped", + name: "small amount => fewer steps until " + + "clamped", presigner: &mockPresigner{}, primarySweepID: op1, sweeps: []sweep{ @@ -1085,10 +1093,29 @@ func TestPresign(t *testing.T) { destAddr: destAddr, nextBlockFeeRate: chainfee.FeePerKwFloor, minRelayFeeRate: chainfee.FeePerKwFloor, - wantErr: "for feeRate 363 sat/kw", + wantErr: "test error in SignTx", }, } + type pair struct { + output btcutil.Amount + lockTime uint32 + } + + zip := func(outputs []btcutil.Amount, lockTimes []uint32) []pair { + require.Equal(t, len(outputs), len(lockTimes)) + + pairs := make([]pair, len(outputs)) + for i, output := range outputs { + pairs[i] = pair{ + output: output, + lockTime: lockTimes[i], + } + } + + return pairs + } + for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { err := presign( @@ -1102,8 +1129,11 @@ func TestPresign(t *testing.T) { } else { require.NoError(t, err) p := tc.presigner.(*mockPresigner) - require.Equal(t, tc.wantOutputs, p.outputs) - require.Equal(t, tc.wantLockTimes, p.lockTimes) + require.ElementsMatch( + t, + zip(tc.wantOutputs, tc.wantLockTimes), + zip(p.outputs, p.lockTimes), + ) } }) } From b458c5ebd83a03e682781d5a60dd407fef13e570 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 14 Oct 2025 23:16:10 -0300 Subject: [PATCH 2/2] go.mod: fix taproot-assets commit Loop was pinned to commit c44e078c33e0 (tapchannel: support stxo on aux sweeper) which was merged into "main" branch of taproot-assets as e6ae082c0b4b. --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 767c1cd0f..93bd5bfd6 100644 --- a/go.mod +++ b/go.mod @@ -23,8 +23,8 @@ require ( github.com/lightninglabs/lndclient v0.20.0-1 github.com/lightninglabs/loop/looprpc v1.0.7 github.com/lightninglabs/loop/swapserverrpc v1.0.14 - github.com/lightninglabs/taproot-assets v0.7.0-rc1.0.20250925100956-c44e078c33e0 - github.com/lightninglabs/taproot-assets/taprpc v1.0.10-0.20250925100956-c44e078c33e0 + github.com/lightninglabs/taproot-assets v0.7.0-rc1.0.20251014172227-e6ae082c0b4b + github.com/lightninglabs/taproot-assets/taprpc v1.0.10-0.20251014172227-e6ae082c0b4b github.com/lightningnetwork/lnd v0.20.0-beta.rc1 github.com/lightningnetwork/lnd/cert v1.2.2 github.com/lightningnetwork/lnd/clock v1.1.1 diff --git a/go.sum b/go.sum index adc59da2c..9581f9e7d 100644 --- a/go.sum +++ b/go.sum @@ -1120,10 +1120,10 @@ github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3 github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo= github.com/lightninglabs/protobuf-go-hex-display v1.34.2-hex-display h1:w7FM5LH9Z6CpKxl13mS48idsu6F+cEZf0lkyiV+Dq9g= github.com/lightninglabs/protobuf-go-hex-display v1.34.2-hex-display/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -github.com/lightninglabs/taproot-assets v0.7.0-rc1.0.20250925100956-c44e078c33e0 h1:NkPYeGOwPNITNQ9nLFJgLzD2Ig56S3NhD+NuHLV98nA= -github.com/lightninglabs/taproot-assets v0.7.0-rc1.0.20250925100956-c44e078c33e0/go.mod h1:qC9TBmn7gV+6LrDhacCe2DD0MnMbD1FgUzJ14LLb7E8= -github.com/lightninglabs/taproot-assets/taprpc v1.0.10-0.20250925100956-c44e078c33e0 h1:l4EQ6+oBbbPubipKhX1vLLwNWkTRgPOPqGjtKnPO9qM= -github.com/lightninglabs/taproot-assets/taprpc v1.0.10-0.20250925100956-c44e078c33e0/go.mod h1:ufuKxkMNdfRnv4IcnLw7ken69DcCUxO79WSpC8mIvdM= +github.com/lightninglabs/taproot-assets v0.7.0-rc1.0.20251014172227-e6ae082c0b4b h1:wXE4vhZlZGXLHzei+rj6lnWsCaxjJCfCAHJlcs3dZH4= +github.com/lightninglabs/taproot-assets v0.7.0-rc1.0.20251014172227-e6ae082c0b4b/go.mod h1:qC9TBmn7gV+6LrDhacCe2DD0MnMbD1FgUzJ14LLb7E8= +github.com/lightninglabs/taproot-assets/taprpc v1.0.10-0.20251014172227-e6ae082c0b4b h1:qT27GPShJpH3A/aK7VVi8Onn11GIjEnL/xbqbHMDz/U= +github.com/lightninglabs/taproot-assets/taprpc v1.0.10-0.20251014172227-e6ae082c0b4b/go.mod h1:ufuKxkMNdfRnv4IcnLw7ken69DcCUxO79WSpC8mIvdM= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9 h1:6D3LrdagJweLLdFm1JNodZsBk6iU4TTsBBFLQ4yiXfI= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9/go.mod h1:EDqJ3MuZIbMq0QI1czTIKDJ/GS8S14RXPwapHw8cw6w= github.com/lightningnetwork/lnd v0.20.0-beta.rc1 h1:8Rm3/pcSLQI+tpCjKfYADfMjmEVFkrtoEom470siKRA=