mirror of https://github.com/lightninglabs/loop
Merge pull request #418 from carlaKC/autoloop-builder
liquidity: add swap builder interfacepull/425/head
commit
4c58d0c5e8
@ -0,0 +1,66 @@
|
||||
package liquidity
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightninglabs/loop"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
)
|
||||
|
||||
// Compile-time assertion that loopOutSwapSuggestion satisfies the
|
||||
// swapSuggestion interface.
|
||||
var _ swapSuggestion = (*loopOutSwapSuggestion)(nil)
|
||||
|
||||
// loopOutSwapSuggestion is an implementation of the swapSuggestion interface
|
||||
// implemented to allow us to abstract away from the details of a specific
|
||||
// swap.
|
||||
type loopOutSwapSuggestion struct {
|
||||
loop.OutRequest
|
||||
}
|
||||
|
||||
// amount returns the amount being swapped.
|
||||
func (l *loopOutSwapSuggestion) amount() btcutil.Amount {
|
||||
return l.Amount
|
||||
}
|
||||
|
||||
// fees returns the maximum fees we could possibly pay for this swap.
|
||||
func (l *loopOutSwapSuggestion) fees() btcutil.Amount {
|
||||
return worstCaseOutFees(
|
||||
l.MaxPrepayRoutingFee, l.MaxSwapRoutingFee, l.MaxSwapFee,
|
||||
l.MaxMinerFee, l.MaxPrepayAmount,
|
||||
)
|
||||
}
|
||||
|
||||
// channels returns the set of channels the loop out swap is restricted to.
|
||||
func (l *loopOutSwapSuggestion) channels() []lnwire.ShortChannelID {
|
||||
channels := make([]lnwire.ShortChannelID, len(l.OutgoingChanSet))
|
||||
|
||||
for i, id := range l.OutgoingChanSet {
|
||||
channels[i] = lnwire.NewShortChanIDFromInt(id)
|
||||
}
|
||||
|
||||
return channels
|
||||
}
|
||||
|
||||
// peers returns the set of peers that the loop out swap is restricted to.
|
||||
func (l *loopOutSwapSuggestion) peers(
|
||||
knownChans map[uint64]route.Vertex) []route.Vertex {
|
||||
|
||||
peers := make(map[route.Vertex]struct{}, len(knownChans))
|
||||
|
||||
for _, channel := range l.OutgoingChanSet {
|
||||
peer, ok := knownChans[channel]
|
||||
if !ok {
|
||||
log.Warnf("peer for channel: %v unknown", channel)
|
||||
}
|
||||
|
||||
peers[peer] = struct{}{}
|
||||
}
|
||||
|
||||
peerList := make([]route.Vertex, 0, len(peers))
|
||||
for peer := range peers {
|
||||
peerList = append(peerList, peer)
|
||||
}
|
||||
|
||||
return peerList
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
package liquidity
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightninglabs/loop"
|
||||
"github.com/lightninglabs/loop/labels"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/swap"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
)
|
||||
|
||||
// Compile-time assertion that loopOutBuilder satisfies the swapBuilder
|
||||
// interface.
|
||||
var _ swapBuilder = (*loopOutBuilder)(nil)
|
||||
|
||||
func newLoopOutBuilder(cfg *Config) *loopOutBuilder {
|
||||
return &loopOutBuilder{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
type loopOutBuilder 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 *loopOutBuilder) swapType() swap.Type {
|
||||
return swap.TypeOut
|
||||
}
|
||||
|
||||
// maySwap checks whether we can currently execute a swap, examining the
|
||||
// current on-chain fee conditions relevant to our swap type against our fee
|
||||
// restrictions.
|
||||
//
|
||||
// For loop out, we check whether the fees required for our on-chain sweep
|
||||
// transaction exceed our fee limits.
|
||||
func (b *loopOutBuilder) maySwap(ctx context.Context, params Parameters) error {
|
||||
estimate, err := b.cfg.Lnd.WalletKit.EstimateFee(
|
||||
ctx, params.SweepConfTarget,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return params.FeeLimit.mayLoopOut(estimate)
|
||||
}
|
||||
|
||||
// inUse examines our current swap traffic to determine whether we should
|
||||
// we can perform a swap for the peer/ channels provided.
|
||||
func (b *loopOutBuilder) inUse(traffic *swapTraffic, peer route.Vertex,
|
||||
channels []lnwire.ShortChannelID) error {
|
||||
|
||||
for _, chanID := range channels {
|
||||
lastFail, recentFail := traffic.failedLoopOut[chanID]
|
||||
if recentFail {
|
||||
log.Debugf("Channel: %v not eligible for suggestions, "+
|
||||
"was part of a failed swap at: %v", chanID,
|
||||
lastFail)
|
||||
|
||||
return newReasonError(ReasonFailureBackoff)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildSwap creates a swap for the target peer/channels provided. The autoloop
|
||||
// boolean indicates whether this swap will actually be executed, because there
|
||||
// are some calls we can leave out if this swap is just for a dry run (ie, when
|
||||
// we are just demonstrating the actions that autoloop _would_ take, but not
|
||||
// actually executing the swap).
|
||||
//
|
||||
// For loop out, we don't bother generating a new wallet address if this is a
|
||||
// dry-run, and we do not add the autoloop label to the recommended swap.
|
||||
func (b *loopOutBuilder) buildSwap(ctx context.Context, pubkey route.Vertex,
|
||||
channels []lnwire.ShortChannelID, amount btcutil.Amount,
|
||||
autoloop bool, params Parameters) (swapSuggestion, error) {
|
||||
|
||||
quote, err := b.cfg.LoopOutQuote(
|
||||
ctx, &loop.LoopOutQuoteRequest{
|
||||
Amount: amount,
|
||||
SweepConfTarget: params.SweepConfTarget,
|
||||
SwapPublicationDeadline: b.cfg.Clock.Now(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("quote for suggestion: %v, swap fee: %v, "+
|
||||
"miner fee: %v, prepay: %v", amount, quote.SwapFee,
|
||||
quote.MinerFee, quote.PrepayAmount)
|
||||
|
||||
// Check that the estimated fees for the suggested swap are below the
|
||||
// fee limits configured.
|
||||
if err := params.FeeLimit.loopOutLimits(amount, quote); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Break down our fees into appropriate categories for our swap. Our
|
||||
// quote does not provide any off-chain routing estimates for us, so
|
||||
// we just set our fees from the amounts that we expect to route. We
|
||||
// don't have any off-chain fee estimation, so we just use the exact
|
||||
// prepay, swap and miner fee provided by the server and split our
|
||||
// remaining fees up from there.
|
||||
prepayMaxFee, routeMaxFee, minerFee := params.FeeLimit.loopOutFees(
|
||||
amount, quote,
|
||||
)
|
||||
|
||||
var chanSet loopdb.ChannelSet
|
||||
for _, channel := range channels {
|
||||
chanSet = append(chanSet, channel.ToUint64())
|
||||
}
|
||||
|
||||
// Create a request with our calculated routing fees. We can use the
|
||||
// swap fee, prepay amount and miner fee from the quote because we have
|
||||
// already validated them.
|
||||
request := loop.OutRequest{
|
||||
Amount: amount,
|
||||
OutgoingChanSet: chanSet,
|
||||
MaxPrepayRoutingFee: prepayMaxFee,
|
||||
MaxSwapRoutingFee: routeMaxFee,
|
||||
MaxMinerFee: minerFee,
|
||||
MaxSwapFee: quote.SwapFee,
|
||||
MaxPrepayAmount: quote.PrepayAmount,
|
||||
SweepConfTarget: params.SweepConfTarget,
|
||||
Initiator: autoloopSwapInitiator,
|
||||
}
|
||||
|
||||
if autoloop {
|
||||
request.Label = labels.AutoloopLabel(swap.TypeOut)
|
||||
|
||||
addr, err := b.cfg.Lnd.WalletKit.NextAddr(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.DestAddr = addr
|
||||
}
|
||||
|
||||
return &loopOutSwapSuggestion{
|
||||
OutRequest: request,
|
||||
}, nil
|
||||
}
|
Loading…
Reference in New Issue