mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
instantout: close unfinished MuSig2 sessions
Clean up abandoned signing sessions on error paths while leaving completed sessions to lnd.
This commit is contained in:
parent
6853e69a05
commit
b689e361c1
3 changed files with 88 additions and 2 deletions
|
|
@ -293,6 +293,15 @@ func (f *FSM) BuildHTLCAction(ctx context.Context,
|
|||
}
|
||||
|
||||
f.htlcMusig2Sessions = htlcSessions
|
||||
defer func() {
|
||||
err := cleanupMuSig2Sessions(
|
||||
ctx, f.cfg.Signer, f.htlcMusig2Sessions,
|
||||
)
|
||||
if err != nil {
|
||||
f.Errorf("unable to clean up HTLC MuSig2 sessions: %v", err)
|
||||
}
|
||||
f.htlcMusig2Sessions = nil
|
||||
}()
|
||||
|
||||
// Send the server the client nonces.
|
||||
htlcInitRes, err := f.cfg.InstantOutClient.InitHtlcSig(
|
||||
|
|
@ -382,6 +391,15 @@ func (f *FSM) PushPreimageAction(ctx context.Context,
|
|||
}
|
||||
|
||||
f.sweeplessSweepSessions = coopSessions
|
||||
defer func() {
|
||||
err := cleanupMuSig2Sessions(
|
||||
ctx, f.cfg.Signer, f.sweeplessSweepSessions,
|
||||
)
|
||||
if err != nil {
|
||||
f.Errorf("unable to clean up sweep MuSig2 sessions: %v", err)
|
||||
}
|
||||
f.sweeplessSweepSessions = nil
|
||||
}()
|
||||
|
||||
// Get the feerate for the coop sweep.
|
||||
feeRate, err := f.cfg.Wallet.EstimateFeeRate(ctx, normalConfTarget)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
|
|
@ -25,6 +26,8 @@ import (
|
|||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
)
|
||||
|
||||
const muSig2CleanupTimeout = 5 * time.Second
|
||||
|
||||
// InstantOut holds the necessary information to execute an instant out swap.
|
||||
type InstantOut struct {
|
||||
// SwapHash is the hash of the swap.
|
||||
|
|
@ -112,7 +115,10 @@ func (i *InstantOut) createMusig2Session(ctx context.Context,
|
|||
for idx, reservation := range i.Reservations {
|
||||
session, err := reservation.Musig2CreateSession(ctx, signer)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
cleanupErr := cleanupMuSig2Sessions(
|
||||
ctx, signer, musig2Sessions[:idx],
|
||||
)
|
||||
return nil, nil, errors.Join(err, cleanupErr)
|
||||
}
|
||||
|
||||
musig2Sessions[idx] = session
|
||||
|
|
@ -122,6 +128,32 @@ func (i *InstantOut) createMusig2Session(ctx context.Context,
|
|||
return musig2Sessions, clientNonces, nil
|
||||
}
|
||||
|
||||
// cleanupMuSig2Sessions removes completed or abandoned MuSig2 sessions from
|
||||
// lnd. Cleanup uses a bounded context that survives cancellation of the swap
|
||||
// action that created the sessions.
|
||||
func cleanupMuSig2Sessions(ctx context.Context, signer lndclient.SignerClient,
|
||||
sessions []*input.MuSig2SessionInfo) error {
|
||||
|
||||
cleanupCtx, cancel := context.WithTimeout(
|
||||
context.WithoutCancel(ctx), muSig2CleanupTimeout,
|
||||
)
|
||||
defer cancel()
|
||||
|
||||
var cleanupErr error
|
||||
for _, session := range sessions {
|
||||
if session == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
err := signer.MuSig2Cleanup(cleanupCtx, session.SessionID)
|
||||
if err != nil {
|
||||
cleanupErr = errors.Join(cleanupErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
return cleanupErr
|
||||
}
|
||||
|
||||
// getInputReservations returns the input reservations for the instant out.
|
||||
func (i *InstantOut) getInputReservations() (InputReservations, error) {
|
||||
if len(i.Reservations) == 0 {
|
||||
|
|
@ -384,6 +416,11 @@ func (i *InstantOut) finalizeMusig2Transaction(ctx context.Context,
|
|||
return nil, fmt.Errorf("missing sigs")
|
||||
}
|
||||
|
||||
// lnd removes a MuSig2 session automatically once all signatures
|
||||
// have been combined. Clear the local entry so the caller's deferred
|
||||
// cleanup only targets sessions abandoned on an error path.
|
||||
musig2Sessions[idx] = nil
|
||||
|
||||
tx.TxIn[idx].Witness = wire.TxWitness{finalSig}
|
||||
|
||||
vm, err := txscript.NewEngine(
|
||||
|
|
|
|||
|
|
@ -23,6 +23,19 @@ func (s *invalidFinalSigSigner) MuSig2CombineSig(context.Context, [32]byte,
|
|||
return true, make([]byte, 64), nil
|
||||
}
|
||||
|
||||
type cleanupTrackingSigner struct {
|
||||
lndclient.SignerClient
|
||||
|
||||
cleaned [][32]byte
|
||||
}
|
||||
|
||||
func (s *cleanupTrackingSigner) MuSig2Cleanup(_ context.Context,
|
||||
sessionID [32]byte) error {
|
||||
|
||||
s.cleaned = append(s.cleaned, sessionID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestMuSig2VectorLengthValidation verifies that malformed server-controlled
|
||||
// vectors are rejected before they can be indexed.
|
||||
func TestMuSig2VectorLengthValidation(t *testing.T) {
|
||||
|
|
@ -76,9 +89,27 @@ func TestFinalizeMuSig2TransactionVerifiesSignature(t *testing.T) {
|
|||
tx.AddTxIn(&wire.TxIn{PreviousOutPoint: *res.Outpoint})
|
||||
tx.AddTxOut(&wire.TxOut{Value: 90_000})
|
||||
|
||||
sessions := []*input.MuSig2SessionInfo{{}}
|
||||
_, err := instantOut.finalizeMusig2Transaction(
|
||||
context.Background(), &invalidFinalSigSigner{},
|
||||
[]*input.MuSig2SessionInfo{{}}, tx, [][]byte{{1}},
|
||||
sessions, tx, [][]byte{{1}},
|
||||
)
|
||||
require.ErrorContains(t, err, "invalid final MuSig2 signature")
|
||||
require.Nil(t, sessions[0])
|
||||
}
|
||||
|
||||
// TestCleanupMuSig2Sessions verifies that all allocated sessions are released
|
||||
// while nil entries from partial session creation are skipped.
|
||||
func TestCleanupMuSig2Sessions(t *testing.T) {
|
||||
firstID := [32]byte{1}
|
||||
secondID := [32]byte{2}
|
||||
signer := &cleanupTrackingSigner{}
|
||||
|
||||
err := cleanupMuSig2Sessions(
|
||||
t.Context(), signer, []*input.MuSig2SessionInfo{
|
||||
{SessionID: firstID}, nil, {SessionID: secondID},
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, [][32]byte{firstID, secondID}, signer.cleaned)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue