Skip to content

staticaddr: fractional swap amount #887

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 32 additions & 42 deletions cmd/loop/staticaddr.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,13 @@ var staticAddressLoopInCommand = cli.Command{
"The client can retry the swap with adjusted " +
"parameters after the payment timed out.",
},
cli.IntFlag{
Name: "amount",
Usage: "the number of satoshis that should be " +
"swapped from the selected deposits. If there" +
"is change it is sent back to the static " +
"address.",
},
lastHopFlag,
labelFlag,
routeHintsFlag,
Expand All @@ -499,13 +506,15 @@ func staticAddressLoopIn(ctx *cli.Context) error {
defer cleanup()

var (
ctxb = context.Background()
isAllSelected = ctx.IsSet("all")
isUtxoSelected = ctx.IsSet("utxo")
label = ctx.String("static-loop-in")
hints []*swapserverrpc.RouteHint
lastHop []byte
paymentTimeoutSeconds = uint32(loopin.DefaultPaymentTimeoutSeconds)
ctxb = context.Background()
isAllSelected = ctx.IsSet("all")
isUtxoSelected = ctx.IsSet("utxo")
selectedAmount = ctx.Int64("amount")
selectDepositsForQuote bool
label = ctx.String("static-loop-in")
hints []*swapserverrpc.RouteHint
lastHop []byte
paymentTimeoutSeconds = uint32(loopin.DefaultPaymentTimeoutSeconds)
)

// Validate our label early so that we can fail before getting a quote.
Expand Down Expand Up @@ -541,7 +550,9 @@ func staticAddressLoopIn(ctx *cli.Context) error {
return err
}

if len(depositList.FilteredDeposits) == 0 {
allDeposits := depositList.FilteredDeposits

if len(allDeposits) == 0 {
errString := fmt.Sprintf("no confirmed deposits available, "+
"deposits need at least %v confirmations",
deposit.MinConfs)
Expand All @@ -551,17 +562,18 @@ func staticAddressLoopIn(ctx *cli.Context) error {

var depositOutpoints []string
switch {
case isAllSelected == isUtxoSelected:
return errors.New("must select either all or some utxos")
case isAllSelected && isUtxoSelected:
return errors.New("cannot select all and specific utxos")

case isAllSelected:
depositOutpoints = depositsToOutpoints(
depositList.FilteredDeposits,
)
depositOutpoints = depositsToOutpoints(allDeposits)

case isUtxoSelected:
depositOutpoints = ctx.StringSlice("utxo")

case selectedAmount > 0:
// If only an amount is selected we will trigger coin selection.

default:
return fmt.Errorf("unknown quote request")
}
Expand All @@ -570,11 +582,17 @@ func staticAddressLoopIn(ctx *cli.Context) error {
return errors.New("duplicate outpoints detected")
}

if len(depositOutpoints) == 0 && selectedAmount > 0 {
selectDepositsForQuote = true
}

quoteReq := &looprpc.QuoteRequest{
Amt: selectedAmount,
LoopInRouteHints: hints,
LoopInLastHop: lastHop,
Private: ctx.Bool(privateFlag.Name),
DepositOutpoints: depositOutpoints,
SelectDeposits: selectDepositsForQuote,
}
quote, err := client.GetLoopInQuote(ctxb, quoteReq)
if err != nil {
Expand All @@ -583,15 +601,6 @@ func staticAddressLoopIn(ctx *cli.Context) error {

limits := getInLimits(quote)

// populate the quote request with the sum of selected deposits and
// prompt the user for acceptance.
quoteReq.Amt, err = sumDeposits(
depositOutpoints, depositList.FilteredDeposits,
)
if err != nil {
return err
}

if !(ctx.Bool("force") || ctx.Bool("f")) {
err = displayInDetails(quoteReq, quote, ctx.Bool("verbose"))
if err != nil {
Expand All @@ -604,6 +613,7 @@ func staticAddressLoopIn(ctx *cli.Context) error {
}

req := &looprpc.StaticAddressLoopInRequest{
Amount: quoteReq.Amt,
Outpoints: depositOutpoints,
MaxSwapFeeSatoshis: int64(limits.maxSwapFee),
LastHop: lastHop,
Expand Down Expand Up @@ -636,26 +646,6 @@ func containsDuplicates(outpoints []string) bool {
return false
}

func sumDeposits(outpoints []string, deposits []*looprpc.Deposit) (int64,
error) {

var sum int64
depositMap := make(map[string]*looprpc.Deposit)
for _, deposit := range deposits {
depositMap[deposit.Outpoint] = deposit
}

for _, outpoint := range outpoints {
if _, ok := depositMap[outpoint]; !ok {
return 0, fmt.Errorf("deposit %v not found", outpoint)
}

sum += depositMap[outpoint].Value
}

return sum, nil
}

func depositsToOutpoints(deposits []*looprpc.Deposit) []string {
outpoints := make([]string, 0, len(deposits))
for _, deposit := range deposits {
Expand Down
5 changes: 5 additions & 0 deletions interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,11 @@ type StaticAddressLoopInRequest struct {
// swap payment. If the timeout is reached the swap will be aborted and
// the client can retry the swap if desired with different parameters.
PaymentTimeoutSeconds uint32

// SelectedAmount is the amount that the user selected for the swap. If
// the user did not select an amount, the amount of the selected
// deposits is used.
SelectedAmount btcutil.Amount
}

// LoopInTerms are the server terms on which it executes loop in swaps.
Expand Down
10 changes: 10 additions & 0 deletions loopd/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,16 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
clock.NewDefaultClock(), d.lnd.ChainParams,
)

// Run the selected amount migration.
err = loopin.MigrateSelectedSwapAmount(
d.mainCtx, swapDb, depositStore, staticAddressLoopInStore,
)
if err != nil {
errorf("Selected amount migration failed: %v", err)

return err
}

staticLoopInManager = loopin.NewManager(&loopin.Config{
Server: staticAddressClient,
QuoteGetter: swapClient.Server,
Expand Down
122 changes: 96 additions & 26 deletions loopd/swapclient_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -893,22 +893,62 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
infof("Loop in quote request received")

var (
numDeposits = uint32(len(req.DepositOutpoints))
err error
selectedAmount = btcutil.Amount(req.Amt)
totalDepositAmount btcutil.Amount
autoSelectDeposits = req.SelectDeposits
err error
)

htlcConfTarget, err := validateLoopInRequest(
req.ConfTarget, req.ExternalHtlc, numDeposits, req.Amt,
req.ConfTarget, req.ExternalHtlc,
uint32(len(req.DepositOutpoints)), int64(selectedAmount),
autoSelectDeposits,
)
if err != nil {
return nil, err
}

// Retrieve deposits to calculate their total value.
var depositList *looprpc.ListStaticAddressDepositsResponse
amount := btcutil.Amount(req.Amt)
if len(req.DepositOutpoints) > 0 {
depositList, err = s.ListStaticAddressDeposits(
// If deposits should be automatically selected we do so and count the
// number of deposits to quote for.
numDeposits := 0
if autoSelectDeposits {
deposits, err := s.depositManager.GetActiveDepositsInState(
deposit.Deposited,
)
if err != nil {
return nil, fmt.Errorf("unable to retrieve all "+
"deposits: %w", err)
}

// TODO(hieblmi): add params to deposit for multi-address
// support.
params, err := s.staticAddressManager.GetStaticAddressParameters(
ctx,
)
if err != nil {
return nil, fmt.Errorf("unable to retrieve static "+
"address parameters: %w", err)
}

info, err := s.lnd.Client.GetInfo(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get lnd info: %w",
err)
}
selectedDeposits, err := loopin.SelectDeposits(
selectedAmount, deposits, params.Expiry,
info.BlockHeight,
)
if err != nil {
return nil, fmt.Errorf("unable to select deposits: %w",
err)
}

numDeposits = len(selectedDeposits)
} else if len(req.DepositOutpoints) > 0 {
// If deposits are selected, we need to retrieve them to
// calculate the total value which we request a quote for.
depositList, err := s.ListStaticAddressDeposits(
ctx, &looprpc.ListStaticAddressDepositsRequest{
Outpoints: req.DepositOutpoints,
},
Expand All @@ -922,20 +962,34 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
"deposit outpoints")
}

// The requested amount should be 0 here if the request
// contained deposit outpoints.
if amount != 0 && len(depositList.FilteredDeposits) > 0 {
return nil, fmt.Errorf("amount should be 0 for " +
"deposit quotes")
if len(req.DepositOutpoints) !=
len(depositList.FilteredDeposits) {

return nil, fmt.Errorf("expected %d deposits, got %d",
numDeposits, len(depositList.FilteredDeposits))
} else {
numDeposits = len(depositList.FilteredDeposits)
}

// In case we quote for deposits we send the server both the
// total value and the number of deposits. This is so the server
// can probe the total amount and calculate the per input fee.
if amount == 0 && len(depositList.FilteredDeposits) > 0 {
for _, deposit := range depositList.FilteredDeposits {
amount += btcutil.Amount(deposit.Value)
}
// selected value and the number of deposits. This is so the
// server can probe the selected value and calculate the per
// input fee.
for _, deposit := range depositList.FilteredDeposits {
totalDepositAmount += btcutil.Amount(
deposit.Value,
)
}

// If a fractional amount is also selected, we check if it would
// lead to a dust change output.
selectedAmount, err = loopin.SwapAmountFromSelectedAmount(
totalDepositAmount, selectedAmount,
)
if err != nil {
return nil, fmt.Errorf("error calculating "+
"swap amount from selected amount: %v",
err)
}
}

Expand All @@ -962,14 +1016,14 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
}

quote, err := s.impl.LoopInQuote(ctx, &loop.LoopInQuoteRequest{
Amount: amount,
Amount: selectedAmount,
HtlcConfTarget: htlcConfTarget,
ExternalHtlc: req.ExternalHtlc,
LastHop: lastHop,
RouteHints: routeHints,
Private: req.Private,
Initiator: defaultLoopdInitiator,
NumDeposits: numDeposits,
NumDeposits: uint32(numDeposits),
})
if err != nil {
return nil, err
Expand Down Expand Up @@ -1065,8 +1119,11 @@ func (s *swapClientServer) LoopIn(ctx context.Context,

infof("Loop in request received")

selectDeposits := false
numDeposits := uint32(0)
htlcConfTarget, err := validateLoopInRequest(
in.HtlcConfTarget, in.ExternalHtlc, 0, in.Amt,
in.HtlcConfTarget, in.ExternalHtlc, numDeposits, in.Amt,
selectDeposits,
)
if err != nil {
return nil, err
Expand Down Expand Up @@ -1755,13 +1812,18 @@ func (s *swapClientServer) ListStaticAddressSwaps(ctx context.Context,
return nil, fmt.Errorf("error decoding swap invoice: "+
"%v", err)
}

swapAmount := swp.TotalDepositAmount()
if swp.SelectedAmount > 0 {
swapAmount = swp.SelectedAmount
}
swap := &looprpc.StaticAddressLoopInSwap{
SwapHash: swp.SwapHash[:],
DepositOutpoints: swp.DepositOutpoints,
State: toClientStaticAddressLoopInState(
swp.GetState(),
),
SwapAmountSatoshis: int64(swp.TotalDepositAmount()),
SwapAmountSatoshis: int64(swapAmount),
PaymentRequestAmountSatoshis: int64(
swapPayReq.MilliSat.ToSatoshis(),
),
Expand Down Expand Up @@ -1868,6 +1930,7 @@ func (s *swapClientServer) StaticAddressLoopIn(ctx context.Context,
}

req := &loop.StaticAddressLoopInRequest{
SelectedAmount: btcutil.Amount(in.Amount),
DepositOutpoints: in.Outpoints,
MaxSwapFee: btcutil.Amount(in.MaxSwapFeeSatoshis),
Label: in.Label,
Expand Down Expand Up @@ -2166,10 +2229,17 @@ func validateConfTarget(target, defaultTarget int32) (int32, error) {
// validateLoopInRequest fails if the mutually exclusive conf target and
// external parameters are both set.
func validateLoopInRequest(htlcConfTarget int32, external bool,
numDeposits uint32, amount int64) (int32, error) {
numDeposits uint32, amount int64, autoSelectDeposits bool) (int32,
error) {

if amount == 0 && numDeposits == 0 {
return 0, errors.New("either amount or deposits must be set")
return 0, errors.New("either amount, or deposits or both " +
"must be set")
}

if autoSelectDeposits && numDeposits > 0 {
return 0, errors.New("cannot auto-select deposits while " +
"providing deposits at the same time")
}

// If the htlc is going to be externally set, the htlcConfTarget should
Expand All @@ -2187,7 +2257,7 @@ func validateLoopInRequest(htlcConfTarget int32, external bool,

// If the loop in uses static address deposits, we do not need to set a
// confirmation target since the HTLC won't be published by the client.
if numDeposits > 0 {
if numDeposits > 0 || autoSelectDeposits {
return 0, nil
}

Expand Down
Loading