2021-12-15 07:01:20 +00:00
|
|
|
package liquidity
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
|
2022-03-14 12:36:02 +00:00
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
2021-12-15 07:01:20 +00:00
|
|
|
"github.com/lightninglabs/loop"
|
|
|
|
"github.com/lightninglabs/loop/labels"
|
|
|
|
"github.com/lightninglabs/loop/swap"
|
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
"google.golang.org/grpc/status"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Compile-time assertion that loopInBuilder satisfies the swapBuilder
|
|
|
|
// interface.
|
|
|
|
var _ swapBuilder = (*loopInBuilder)(nil)
|
|
|
|
|
|
|
|
func newLoopInBuilder(cfg *Config) *loopInBuilder {
|
|
|
|
return &loopInBuilder{
|
|
|
|
cfg: cfg,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type loopInBuilder struct {
|
|
|
|
// cfg contains all the external functionality we require to create
|
|
|
|
// swaps.
|
|
|
|
cfg *Config
|
|
|
|
}
|
|
|
|
|
|
|
|
// swapType returns the swap type that the builder is responsible for creating.
|
|
|
|
func (b *loopInBuilder) swapType() swap.Type {
|
|
|
|
return swap.TypeIn
|
|
|
|
}
|
|
|
|
|
|
|
|
// maySwap checks whether we can currently execute a swap, examining the
|
|
|
|
// current on-chain fee conditions against relevant to our swap type against
|
|
|
|
// our fee restrictions.
|
|
|
|
//
|
|
|
|
// For loop in, we cannot check any upfront costs because we do not know how
|
|
|
|
// many inputs will be used for our on-chain htlc before it is made, so we can't
|
|
|
|
// make nay estimations.
|
|
|
|
func (b *loopInBuilder) maySwap(_ context.Context, _ Parameters) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// inUse examines our current swap traffic to determine whether we should
|
|
|
|
// suggest the builder's type of swap for the peer and channels suggested.
|
|
|
|
func (b *loopInBuilder) inUse(traffic *swapTraffic, peer route.Vertex,
|
|
|
|
channels []lnwire.ShortChannelID) error {
|
|
|
|
|
|
|
|
for _, chanID := range channels {
|
|
|
|
if traffic.ongoingLoopOut[chanID] {
|
|
|
|
log.Debugf("Channel: %v not eligible for suggestions, "+
|
|
|
|
"ongoing loop out utilizing channel", chanID)
|
|
|
|
|
|
|
|
return newReasonError(ReasonLoopOut)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if traffic.ongoingLoopIn[peer] {
|
|
|
|
log.Debugf("Peer: %x not eligible for suggestions ongoing "+
|
|
|
|
"loop in utilizing peer", peer)
|
|
|
|
|
|
|
|
return newReasonError(ReasonLoopIn)
|
|
|
|
}
|
|
|
|
|
|
|
|
lastFail, recentFail := traffic.failedLoopIn[peer]
|
|
|
|
if recentFail {
|
|
|
|
log.Debugf("Peer: %v not eligible for suggestions, "+
|
|
|
|
"was part of a failed swap at: %v", peer,
|
|
|
|
lastFail)
|
|
|
|
|
|
|
|
return newReasonError(ReasonFailureBackoff)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// buildSwap creates a swap for the target peer/channels provided. The autoloop
|
|
|
|
// boolean indicates whether this swap will actually be executed.
|
|
|
|
//
|
|
|
|
// For loop in, we do not add the autoloop label for dry runs.
|
|
|
|
func (b *loopInBuilder) buildSwap(ctx context.Context, pubkey route.Vertex,
|
|
|
|
_ []lnwire.ShortChannelID, amount btcutil.Amount,
|
2023-05-29 10:23:55 +00:00
|
|
|
params Parameters) (swapSuggestion, error) {
|
2021-12-15 07:01:20 +00:00
|
|
|
|
|
|
|
quote, err := b.cfg.LoopInQuote(ctx, &loop.LoopInQuoteRequest{
|
|
|
|
Amount: amount,
|
|
|
|
LastHop: &pubkey,
|
|
|
|
HtlcConfTarget: params.HtlcConfTarget,
|
2023-06-20 18:52:07 +00:00
|
|
|
Initiator: getInitiator(params),
|
2021-12-15 07:01:20 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
// If the server fails our quote, we're not reachable right
|
|
|
|
// now, so we want to catch this error and fail with a
|
|
|
|
// structured error so that we know why we can't swap.
|
|
|
|
status, ok := status.FromError(err)
|
|
|
|
if ok && status.Code() == codes.FailedPrecondition {
|
|
|
|
return nil, newReasonError(ReasonLoopInUnreachable)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := params.FeeLimit.loopInLimits(amount, quote); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
request := loop.LoopInRequest{
|
|
|
|
Amount: amount,
|
|
|
|
MaxSwapFee: quote.SwapFee,
|
|
|
|
MaxMinerFee: quote.MinerFee,
|
|
|
|
HtlcConfTarget: params.HtlcConfTarget,
|
|
|
|
LastHop: &pubkey,
|
2023-06-20 18:52:07 +00:00
|
|
|
Initiator: getInitiator(params),
|
2021-12-15 07:01:20 +00:00
|
|
|
}
|
|
|
|
|
2023-05-29 10:23:55 +00:00
|
|
|
if params.Autoloop {
|
2021-12-15 07:01:20 +00:00
|
|
|
request.Label = labels.AutoloopLabel(swap.TypeIn)
|
2023-05-29 10:33:18 +00:00
|
|
|
|
|
|
|
if params.EasyAutoloop {
|
|
|
|
request.Label = labels.EasyAutoloopLabel(swap.TypeIn)
|
|
|
|
}
|
2021-12-15 07:01:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &loopInSwapSuggestion{
|
|
|
|
LoopInRequest: request,
|
|
|
|
}, nil
|
|
|
|
}
|