loop/loopdb/loopout.go
Boris Nagaev d554b42ef6
multi: migrate to btcd v2 modules
Update LND, Aperture, and Taproot Assets to revisions using the
btcd v2 modules, and update lndclient to v0.21.0-3. Migrate Loop
chain, transaction, and address types to their corresponding v2
packages.

The lndclient release includes the migration from:
https://github.com/lightninglabs/lndclient/pull/280

Taproot Assets is temporarily replaced with its btcd v2 revision
because the v0.8 release branch has not adopted the new modules.

This raises the minimum Go version to 1.26 and changes exported
address types.
2026-08-12 23:39:26 +00:00

329 lines
8.3 KiB
Go

package loopdb
import (
"bytes"
"encoding/binary"
"fmt"
"strconv"
"strings"
"time"
btcaddr "github.com/btcsuite/btcd/address/v2"
"github.com/btcsuite/btcd/btcutil/v2"
"github.com/btcsuite/btcd/chaincfg/v2"
"github.com/btcsuite/btcd/wire/v2"
)
// LoopOutContract contains the data that is serialized to persistent storage
// for pending swaps.
type LoopOutContract struct {
// SwapContract contains basic information pertaining to this swap.
// Each swap type has a base contract, then swap specific information
// on top of it.
SwapContract
// DestAddr is the destination address of the loop out swap.
DestAddr btcaddr.Address
// IsExternalAddr indicates whether the destination address does not
// belong to the backing lnd node.
IsExternalAddr bool
// SwapInvoice is the invoice that is to be paid by the client to
// initiate the loop out swap.
SwapInvoice string
// MaxSwapRoutingFee is the maximum off-chain fee in msat that may be
// paid for the swap payment to the server.
MaxSwapRoutingFee btcutil.Amount
// SweepConfTarget specifies the targeted confirmation target for the
// client sweep tx.
SweepConfTarget int32
// HtlcConfirmations is the number of confirmations we require the on
// chain htlc to have before proceeding with the swap.
HtlcConfirmations uint32
// OutgoingChanSet is the set of short ids of channels that may be used.
// If empty, any channel may be used.
OutgoingChanSet ChannelSet
// PrepayInvoice is the invoice that the client should pay to the
// server that will be returned if the swap is complete.
PrepayInvoice string
// MaxPrepayRoutingFee is the maximum off-chain fee in msat that may be
// paid for the prepayment to the server.
MaxPrepayRoutingFee btcutil.Amount
// SwapPublicationDeadline is a timestamp that the server commits to
// have the on-chain swap published by. It is set by the client to
// allow the server to delay the publication in exchange for possibly
// lower fees.
SwapPublicationDeadline time.Time
// PaymentTimeout is the timeout for any individual off-chain payment
// attempt.
PaymentTimeout time.Duration
// AssetSwapInfo contains information, should the loop out swpa be
// paid via an asset channel.
AssetSwapInfo *LoopOutAssetSwap
}
type LoopOutAssetSwap struct {
// AssetId is the optional asset id that is used to pay the swap invoice.
AssetId []byte
// PrepayRfqId is the rfq id that is used to pay the prepay invoice.
PrepayRfqId []byte
// SwapRfqId is the rfq id that is used to pay the swap invoice.
SwapRfqId []byte
// PrepayPaidAmt is the asset amount that was paid for the prepay
// invoice.
PrepayPaidAmt uint64
// SwapPaidAmt is the asset amount that was paid for the swap invoice.
SwapPaidAmt uint64
}
// ChannelSet stores a set of channels.
type ChannelSet []uint64
// String returns the human-readable representation of a channel set.
func (c ChannelSet) String() string {
channelStrings := make([]string, len(c))
for i, chanID := range c {
channelStrings[i] = strconv.FormatUint(chanID, 10)
}
return strings.Join(channelStrings, ",")
}
// NewChannelSet instantiates a new channel set and verifies that there are no
// duplicates present.
func NewChannelSet(set []uint64) (ChannelSet, error) {
// Check channel set for duplicates.
chanSet := make(map[uint64]struct{})
for _, chanID := range set {
if _, exists := chanSet[chanID]; exists {
return nil, fmt.Errorf("duplicate chan in set: id=%v",
chanID)
}
chanSet[chanID] = struct{}{}
}
return ChannelSet(set), nil
}
// LoopOut is a combination of the contract and the updates.
type LoopOut struct {
Loop
// Contract is the active contract for this swap. It describes the
// precise details of the swap including the final fee, CLTV value,
// etc.
Contract *LoopOutContract
}
// LastUpdateTime returns the last update time of this swap.
func (s *LoopOut) LastUpdateTime() time.Time {
lastUpdate := s.LastUpdate()
if lastUpdate == nil {
return s.Contract.InitiationTime
}
return lastUpdate.Time
}
func deserializeLoopOutContract(value []byte, chainParams *chaincfg.Params) (
*LoopOutContract, error) {
r := bytes.NewReader(value)
contract := LoopOutContract{}
var err error
var unixNano int64
if err := binary.Read(r, byteOrder, &unixNano); err != nil {
return nil, err
}
contract.InitiationTime = time.Unix(0, unixNano)
if err := binary.Read(r, byteOrder, &contract.Preimage); err != nil {
return nil, err
}
err = binary.Read(r, byteOrder, &contract.AmountRequested)
if err != nil {
return nil, err
}
contract.PrepayInvoice, err = wire.ReadVarString(r, 0)
if err != nil {
return nil, err
}
n, err := r.Read(contract.HtlcKeys.SenderScriptKey[:])
if err != nil {
return nil, err
}
if n != keyLength {
return nil, fmt.Errorf("sender key has invalid length")
}
n, err = r.Read(contract.HtlcKeys.ReceiverScriptKey[:])
if err != nil {
return nil, err
}
if n != keyLength {
return nil, fmt.Errorf("receiver key has invalid length")
}
if err := binary.Read(r, byteOrder, &contract.CltvExpiry); err != nil {
return nil, err
}
if err := binary.Read(r, byteOrder, &contract.MaxMinerFee); err != nil {
return nil, err
}
if err := binary.Read(r, byteOrder, &contract.MaxSwapFee); err != nil {
return nil, err
}
if err := binary.Read(r, byteOrder, &contract.MaxPrepayRoutingFee); err != nil {
return nil, err
}
if err := binary.Read(r, byteOrder, &contract.InitiationHeight); err != nil {
return nil, err
}
addr, err := wire.ReadVarString(r, 0)
if err != nil {
return nil, err
}
contract.DestAddr, err = btcaddr.DecodeAddress(addr, chainParams)
if err != nil {
return nil, err
}
contract.SwapInvoice, err = wire.ReadVarString(r, 0)
if err != nil {
return nil, err
}
if err := binary.Read(r, byteOrder, &contract.SweepConfTarget); err != nil {
return nil, err
}
if err := binary.Read(r, byteOrder, &contract.MaxSwapRoutingFee); err != nil {
return nil, err
}
var unchargeChannel uint64
if err := binary.Read(r, byteOrder, &unchargeChannel); err != nil {
return nil, err
}
if unchargeChannel != 0 {
contract.OutgoingChanSet = ChannelSet{unchargeChannel}
}
var deadlineNano int64
err = binary.Read(r, byteOrder, &deadlineNano)
if err != nil {
return nil, err
}
contract.SwapPublicationDeadline = time.Unix(0, deadlineNano)
return &contract, nil
}
func serializeLoopOutContract(swap *LoopOutContract) (
[]byte, error) {
var b bytes.Buffer
if err := binary.Write(&b, byteOrder, swap.InitiationTime.UnixNano()); err != nil {
return nil, err
}
if err := binary.Write(&b, byteOrder, swap.Preimage); err != nil {
return nil, err
}
if err := binary.Write(&b, byteOrder, swap.AmountRequested); err != nil {
return nil, err
}
if err := wire.WriteVarString(&b, 0, swap.PrepayInvoice); err != nil {
return nil, err
}
n, err := b.Write(swap.HtlcKeys.SenderScriptKey[:])
if err != nil {
return nil, err
}
if n != keyLength {
return nil, fmt.Errorf("sender key has invalid length")
}
n, err = b.Write(swap.HtlcKeys.ReceiverScriptKey[:])
if err != nil {
return nil, err
}
if n != keyLength {
return nil, fmt.Errorf("receiver key has invalid length")
}
if err := binary.Write(&b, byteOrder, swap.CltvExpiry); err != nil {
return nil, err
}
if err := binary.Write(&b, byteOrder, swap.MaxMinerFee); err != nil {
return nil, err
}
if err := binary.Write(&b, byteOrder, swap.MaxSwapFee); err != nil {
return nil, err
}
if err := binary.Write(&b, byteOrder, swap.MaxPrepayRoutingFee); err != nil {
return nil, err
}
if err := binary.Write(&b, byteOrder, swap.InitiationHeight); err != nil {
return nil, err
}
addr := swap.DestAddr.String()
if err := wire.WriteVarString(&b, 0, addr); err != nil {
return nil, err
}
if err := wire.WriteVarString(&b, 0, swap.SwapInvoice); err != nil {
return nil, err
}
if err := binary.Write(&b, byteOrder, swap.SweepConfTarget); err != nil {
return nil, err
}
if err := binary.Write(&b, byteOrder, swap.MaxSwapRoutingFee); err != nil {
return nil, err
}
// Always write no outgoing channel. This field is replaced by an
// outgoing channel set.
unchargeChannel := uint64(0)
if err := binary.Write(&b, byteOrder, unchargeChannel); err != nil {
return nil, err
}
err = binary.Write(&b, byteOrder, swap.SwapPublicationDeadline.UnixNano())
if err != nil {
return nil, err
}
return b.Bytes(), nil
}