loop/staticaddr/loopin/txout_checker.go
Slyghtning f8e9d11d04
staticaddr/loopin: add lnd txout checker
Add a TxOutChecker interface for checking whether a selected deposit
outpoint is still available before signing the HTLC transaction.

Back the implementation with lnd wallet transaction data so known
confirmed and mempool spends mark the outpoint unavailable.
2026-07-08 09:00:56 +02:00

79 lines
2.1 KiB
Go

package loopin
import (
"context"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/lndclient"
)
// lndTxOutChecker checks outpoint availability using lnd's wallet transaction
// view. It omits outputs already spent by a wallet-known transaction.
type lndTxOutChecker struct {
client lndclient.LightningClient
}
// NewLndTxOutChecker creates a TxOutChecker backed by lnd.
func NewLndTxOutChecker(client lndclient.LightningClient) TxOutChecker {
return &lndTxOutChecker{
client: client,
}
}
// GetTxOuts returns all requested tx outputs that lnd's transaction view still
// reports as unspent.
func (c *lndTxOutChecker) GetTxOuts(ctx context.Context,
outpoints []wire.OutPoint) (map[wire.OutPoint]*wire.TxOut, error) {
outpointByString := make(map[string]wire.OutPoint, len(outpoints))
outpointsByHash := make(map[string][]wire.OutPoint, len(outpoints))
for _, outpoint := range outpoints {
outpointByString[outpoint.String()] = outpoint
outpointsByHash[outpoint.Hash.String()] = append(
outpointsByHash[outpoint.Hash.String()], outpoint,
)
}
// We need lnd's wallet transaction view rather than only the funding
// transaction: a matching previous outpoint tells us the deposit has
// already been spent by a wallet-known transaction. Use endHeight=-1 so
// lnd includes unconfirmed transactions and mempool spends.
txs, err := c.client.ListTransactions(ctx, 0, -1)
if err != nil {
return nil, err
}
txOuts := make(map[wire.OutPoint]*wire.TxOut, len(outpoints))
spent := make(map[wire.OutPoint]struct{}, len(outpoints))
for _, tx := range txs {
for _, prevOutpoint := range tx.PreviousOutpoints {
outpoint, ok := outpointByString[prevOutpoint.GetOutpoint()]
if ok {
spent[outpoint] = struct{}{}
}
}
if tx.Tx == nil {
continue
}
txHash := tx.TxHash
if txHash == "" {
txHash = tx.Tx.TxHash().String()
}
for _, outpoint := range outpointsByHash[txHash] {
if int(outpoint.Index) >= len(tx.Tx.TxOut) {
continue
}
txOuts[outpoint] = tx.Tx.TxOut[outpoint.Index]
}
}
for outpoint := range spent {
delete(txOuts, outpoint)
}
return txOuts, nil
}