Skip to content

Commit fc99c8b

Browse files
committed
multi: add confirmation target to loop in
1 parent f726fc2 commit fc99c8b

File tree

7 files changed

+258
-108
lines changed

7 files changed

+258
-108
lines changed

cmd/loop/loopin.go

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,29 @@ var (
1818
Usage: "the pubkey of the last hop to use for this swap",
1919
}
2020

21+
confTargetFlag = cli.Uint64Flag{
22+
Name: "conf_target",
23+
Usage: "the target number of blocks the on-chain " +
24+
"htlc broadcast by the swap client should " +
25+
"confirm within",
26+
}
27+
2128
loopInCommand = cli.Command{
2229
Name: "in",
2330
Usage: "perform an on-chain to off-chain swap (loop in)",
2431
ArgsUsage: "amt",
2532
Description: `
26-
Send the amount in satoshis specified by the amt argument off-chain.`,
33+
Send the amount in satoshis specified by the amt argument
34+
off-chain.
35+
36+
By default the swap client will create and broadcast the
37+
on-chain htlc. The fee priority of this transaction can
38+
optionally be set using the conf_target flag.
39+
40+
The external flag can be set to publish the on chain htlc
41+
independently. Note that this flag cannot be set with the
42+
conf_target flag.
43+
`,
2744
Flags: []cli.Flag{
2845
cli.Uint64Flag{
2946
Name: "amt",
@@ -33,6 +50,7 @@ var (
3350
Name: "external",
3451
Usage: "expect htlc to be published externally",
3552
},
53+
confTargetFlag,
3654
lastHopFlag,
3755
},
3856
Action: loopIn,
@@ -66,10 +84,21 @@ func loopIn(ctx *cli.Context) error {
6684
defer cleanup()
6785

6886
external := ctx.Bool("external")
87+
htlcConfTarget := int32(ctx.Uint64(confTargetFlag.Name))
88+
89+
// External and confirmation target are mutually exclusive; either the
90+
// on chain htlc is being externally broadcast, or we are creating the
91+
// on chain htlc with a desired confirmation target. Fail if both are
92+
// set.
93+
if external && htlcConfTarget != 0 {
94+
return fmt.Errorf("external and conf_target both set")
95+
}
96+
6997
quote, err := client.GetLoopInQuote(
7098
context.Background(),
7199
&looprpc.QuoteRequest{
72100
Amt: int64(amt),
101+
ConfTarget: htlcConfTarget,
73102
ExternalHtlc: external,
74103
},
75104
)
@@ -96,10 +125,11 @@ func loopIn(ctx *cli.Context) error {
96125
}
97126

98127
req := &looprpc.LoopInRequest{
99-
Amt: int64(amt),
100-
MaxMinerFee: int64(limits.maxMinerFee),
101-
MaxSwapFee: int64(limits.maxSwapFee),
102-
ExternalHtlc: external,
128+
Amt: int64(amt),
129+
MaxMinerFee: int64(limits.maxMinerFee),
130+
MaxSwapFee: int64(limits.maxSwapFee),
131+
ExternalHtlc: external,
132+
HtlcConfTarget: htlcConfTarget,
103133
}
104134

105135
if ctx.IsSet(lastHopFlag.Name) {

cmd/loop/quote.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,8 @@ var quoteInCommand = cli.Command{
2222
Usage: "get a quote for the cost of a loop in swap",
2323
ArgsUsage: "amt",
2424
Description: "Allows to determine the cost of a swap up front",
25-
Flags: []cli.Flag{
26-
cli.Uint64Flag{
27-
Name: "conf_target",
28-
Usage: "the target number of blocks the on-chain " +
29-
"htlc broadcast by the swap client should " +
30-
"confirm within",
31-
},
32-
},
33-
Action: quoteIn,
25+
Flags: []cli.Flag{confTargetFlag},
26+
Action: quoteIn,
3427
}
3528

3629
func quoteIn(ctx *cli.Context) error {

loopd/swapclient_server.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,9 +361,16 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
361361

362362
log.Infof("Loop in quote request received")
363363

364+
htlcConfTarget, err := validateLoopInRequest(
365+
req.ConfTarget, req.ExternalHtlc,
366+
)
367+
if err != nil {
368+
return nil, err
369+
}
370+
364371
quote, err := s.impl.LoopInQuote(ctx, &loop.LoopInQuoteRequest{
365372
Amount: btcutil.Amount(req.Amt),
366-
HtlcConfTarget: loop.DefaultHtlcConfTarget,
373+
HtlcConfTarget: htlcConfTarget,
367374
ExternalHtlc: req.ExternalHtlc,
368375
})
369376
if err != nil {
@@ -381,11 +388,18 @@ func (s *swapClientServer) LoopIn(ctx context.Context,
381388

382389
log.Infof("Loop in request received")
383390

391+
htlcConfTarget, err := validateLoopInRequest(
392+
in.HtlcConfTarget, in.ExternalHtlc,
393+
)
394+
if err != nil {
395+
return nil, err
396+
}
397+
384398
req := &loop.LoopInRequest{
385399
Amount: btcutil.Amount(in.Amt),
386400
MaxMinerFee: btcutil.Amount(in.MaxMinerFee),
387401
MaxSwapFee: btcutil.Amount(in.MaxSwapFee),
388-
HtlcConfTarget: loop.DefaultHtlcConfTarget,
402+
HtlcConfTarget: htlcConfTarget,
389403
ExternalHtlc: in.ExternalHtlc,
390404
}
391405
if in.LastHop != nil {
@@ -489,3 +503,22 @@ func validateConfTarget(target, defaultTarget int32) (int32, error) {
489503
return target, nil
490504
}
491505
}
506+
507+
// validateLoopInRequest fails if the mutually exclusive conf target and
508+
// external parameters are both set.
509+
func validateLoopInRequest(htlcConfTarget int32, external bool) (int32, error) {
510+
// If the htlc is going to be externally set, the htlcConfTarget should
511+
// not be set, because it has no relevance when the htlc is external.
512+
if external && htlcConfTarget != 0 {
513+
return 0, errors.New("external and htlc conf target cannot " +
514+
"both be set")
515+
}
516+
517+
// If the htlc is being externally published, we do not need to set a
518+
// confirmation target.
519+
if external {
520+
return 0, nil
521+
}
522+
523+
return validateConfTarget(htlcConfTarget, loop.DefaultHtlcConfTarget)
524+
}

loopd/swapclient_server_test.go

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package loopd
22

3-
import "testing"
3+
import (
4+
"testing"
5+
6+
"github.com/lightninglabs/loop"
7+
)
48

59
// TestValidateConfTarget tests all failure and success cases for our conf
610
// target validation function, including the case where we replace a zero
@@ -70,3 +74,72 @@ func TestValidateConfTarget(t *testing.T) {
7074
})
7175
}
7276
}
77+
78+
// TestValidateLoopInRequest tests validation of loop in requests.
79+
func TestValidateLoopInRequest(t *testing.T) {
80+
tests := []struct {
81+
name string
82+
external bool
83+
confTarget int32
84+
expectErr bool
85+
expectedTarget int32
86+
}{
87+
{
88+
name: "external and htlc conf set",
89+
external: true,
90+
confTarget: 1,
91+
expectErr: true,
92+
expectedTarget: 0,
93+
},
94+
{
95+
name: "external and no conf",
96+
external: true,
97+
confTarget: 0,
98+
expectErr: false,
99+
expectedTarget: 0,
100+
},
101+
{
102+
name: "not external, zero conf",
103+
external: false,
104+
confTarget: 0,
105+
expectErr: false,
106+
expectedTarget: loop.DefaultHtlcConfTarget,
107+
},
108+
{
109+
name: "not external, bad conf",
110+
external: false,
111+
confTarget: 1,
112+
expectErr: true,
113+
expectedTarget: 0,
114+
},
115+
{
116+
name: "not external, ok conf",
117+
external: false,
118+
confTarget: 5,
119+
expectErr: false,
120+
expectedTarget: 5,
121+
},
122+
}
123+
124+
for _, test := range tests {
125+
test := test
126+
127+
t.Run(test.name, func(t *testing.T) {
128+
external := test.external
129+
conf, err := validateLoopInRequest(
130+
test.confTarget, external,
131+
)
132+
133+
haveErr := err != nil
134+
if haveErr != test.expectErr {
135+
t.Fatalf("expected err: %v, got: %v",
136+
test.expectErr, err)
137+
}
138+
139+
if conf != test.expectedTarget {
140+
t.Fatalf("expected: %v, got: %v",
141+
test.expectedTarget, conf)
142+
}
143+
})
144+
}
145+
}

0 commit comments

Comments
 (0)