Skip to content
Merged
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
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍
We'll soon tag our rc2 so eventually you might want to bump again to a tagged version, if preferred.

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
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
29 changes: 21 additions & 8 deletions sweepbatcher/presigned.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -389,21 +392,31 @@ 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 "+
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before we return, should we cancel grCtx so the other signing requests are stopped?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, if you're signing with sighash_all then I believe you should terminate early. If signatures in an otherwise "failed" batch can be re-used (i.e sighash_single?) then you should still sign them. Although I believe the former is the case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

grCtx is canceled automatically if an error is returned.

https://pkg.go.dev/golang.org/x/sync/errgroup#WithContext

The derived Context is canceled the first time a function passed to Go returns a non-nil error or the first time Wait returns, whichever occurs first.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@GeorgeTsagk Each goroutine signs a separate transaction (they are the same except that they have different fee rates). We need all of them to proceed. If some of them fail to sign, we cancel the whole process.

"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 {
break
}
}

if err := eg.Wait(); err != nil {
return fmt.Errorf("presigning of batch of primarySweepID %v "+
"failed: %w", primarySweepID, err)
}

return nil
}

Expand Down
38 changes: 34 additions & 4 deletions sweepbatcher/presigned_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sweepbatcher
import (
"context"
"fmt"
"sync"
"testing"

"github.com/btcsuite/btcd/btcutil"
Expand Down Expand Up @@ -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
Expand All @@ -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()
}
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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(
Expand All @@ -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),
)
}
})
}
Expand Down
Loading