loopout: reject truncated skipped txids

Reject short skipped transaction IDs before passing them into the batcher.
This commit is contained in:
Boris Nagaev 2026-04-06 02:00:58 -05:00
parent dbd944e6ab
commit ed2fa132ee
No known key found for this signature in database
2 changed files with 67 additions and 8 deletions

View file

@ -21,6 +21,7 @@ import (
"github.com/lightninglabs/loop/sweep"
"github.com/lightninglabs/loop/sweepbatcher"
"github.com/lightninglabs/loop/utils"
"github.com/lightninglabs/loop/utils/chainhashutil"
"github.com/lightninglabs/taproot-assets/rpcutils"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/lntypes"
@ -272,14 +273,9 @@ func NewClient(dbDir string, loopDB loopdb.SwapStore,
}
if len(cfg.SkippedTxns) != 0 {
skippedTxns := make(map[chainhash.Hash]struct{})
for _, txid := range cfg.SkippedTxns {
txid, err := chainhash.NewHashFromStr(txid)
if err != nil {
return nil, nil, fmt.Errorf("failed to parse "+
"txid to skip %v: %w", txid, err)
}
skippedTxns[*txid] = struct{}{}
skippedTxns, err := parseSkippedTxns(cfg.SkippedTxns)
if err != nil {
return nil, nil, err
}
batcherOpts = append(batcherOpts, sweepbatcher.WithSkippedTxns(
skippedTxns,
@ -323,6 +319,24 @@ func NewClient(dbDir string, loopDB loopdb.SwapStore,
return client, cleanup, nil
}
// parseSkippedTxns parses the configured skipped transaction IDs and rejects
// any txid that is not fully specified.
func parseSkippedTxns(txids []string) (map[chainhash.Hash]struct{}, error) {
skippedTxns := make(map[chainhash.Hash]struct{}, len(txids))
for _, txid := range txids {
hash, err := chainhashutil.NewHashFromStrExact(txid)
if err != nil {
return nil, fmt.Errorf("failed to parse txid to skip %v: %w",
txid, err)
}
skippedTxns[hash] = struct{}{}
}
return skippedTxns, nil
}
// GetConn returns the gRPC connection to the server.
func (s *Client) GetConn() *grpc.ClientConn {
return s.clientConfig.Conn

View file

@ -4,6 +4,7 @@ import (
"context"
"crypto/sha256"
"errors"
"strings"
"testing"
"github.com/btcsuite/btcd/btcutil"
@ -46,6 +47,50 @@ var (
defaultConfirmations = int32(loopdb.DefaultLoopOutHtlcConfirmations)
)
// TestParseSkippedTxns verifies that skipped txids must be fully specified.
func TestParseSkippedTxns(t *testing.T) {
t.Parallel()
validTxid := strings.Repeat("01", 32)
validHash, err := chainhash.NewHashFromStr(validTxid)
require.NoError(t, err)
tests := []struct {
name string
txids []string
expected map[chainhash.Hash]struct{}
expectedErr string
}{
{
name: "valid",
txids: []string{validTxid},
expected: map[chainhash.Hash]struct{}{
*validHash: {},
},
},
{
name: "short",
txids: []string{"abcd"},
expectedErr: "failed to parse txid to skip abcd",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
skippedTxns, err := parseSkippedTxns(test.txids)
if test.expectedErr != "" {
require.ErrorContains(t, err, test.expectedErr)
return
}
require.NoError(t, err)
require.Equal(t, test.expected, skippedTxns)
})
}
}
var htlcKeys = func() loopdb.HtlcKeys {
var senderKey, receiverKey [33]byte