Merge pull request #1022 from starius/parallel-presign

sweepbatcher: presign transactions in parallel
This commit is contained in:
Boris Nagaev 2025-10-15 11:46:46 -03:00 committed by GitHub
commit db907aa623
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 61 additions and 18 deletions

4
go.mod
View file

@ -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

8
go.sum
View file

@ -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=

View file

@ -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
}

View file

@ -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),
)
}
})
}