Skip to content

Commit 97403b9

Browse files
authored
Merge pull request #179 from carlaKC/loopin-addconftarget
loopin: allow user to configure confirmation target
2 parents a90971a + 5a5ac94 commit 97403b9

13 files changed

+365
-117
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ _testmain.go
2424
*.test
2525
*.prof
2626

27+
/loop-debug
28+
/loopd-debug
29+
2730
output*.log
2831

2932
loop

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ sudo: required
1818

1919
script:
2020
- export GO111MODULE=on
21-
- make lint unit
21+
- make lint unit build
2222

2323
after_script:
2424
- echo "Uploading to termbin.com..." && find *.log | xargs -I{} sh -c "cat {} | nc termbin.com 9999 | xargs -r0 printf '{} uploaded to %s'"

Makefile

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ PKG := github.com/lightninglabs/loop
33
GOTEST := GO111MODULE=on go test -v
44

55
GO_BIN := ${GOPATH}/bin
6+
GOBUILD := GO111MODULE=on go build -v
7+
GOINSTALL := GO111MODULE=on go install -v
8+
9+
COMMIT := $(shell git describe --abbrev=40 --dirty)
10+
LDFLAGS := -ldflags "-X $(PKG)/build.Commit=$(COMMIT)"
611

712
GOFILES_NOVENDOR = $(shell find . -type f -name '*.go' -not -path "./vendor/*")
813
GOLIST := go list $(PKG)/... | grep -v '/vendor/'
@@ -33,4 +38,18 @@ fmt:
3338

3439
lint: $(LINT_BIN)
3540
@$(call print, "Linting source.")
36-
$(LINT)
41+
$(LINT)
42+
43+
# ============
44+
# INSTALLATION
45+
# ============
46+
47+
build:
48+
@$(call print, "Building debug loop and loopd.")
49+
$(GOBUILD) -o loop-debug $(LDFLAGS) $(PKG)/cmd/loop
50+
$(GOBUILD) -o loopd-debug $(LDFLAGS) $(PKG)/cmd/loopd
51+
52+
install:
53+
@$(call print, "Installing loop and loopd.")
54+
$(GOINSTALL) $(LDFLAGS) $(PKG)/cmd/loop
55+
$(GOINSTALL) $(LDFLAGS) $(PKG)/cmd/loopd

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,9 @@ DESCRIPTION:
195195
Send the amount in satoshis specified by the amt argument off-chain.
196196
197197
OPTIONS:
198-
--amt value the amount in satoshis to loop in (default: 0)
199-
--external expect htlc to be published externally
198+
--amt value the amount in satoshis to loop in (default: 0)
199+
--external expect htlc to be published externally
200+
--conf_target the confirmation target for the on chain htlc, if not being published externally
200201
```
201202

202203
The `--external` argument allows the on-chain HTLC transacting to be published

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: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +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 number of blocks from the swap " +
29-
"initiation height that the on-chain HTLC " +
30-
"should be swept within in a Loop Out",
31-
Value: 6,
32-
},
33-
},
34-
Action: quoteIn,
25+
Flags: []cli.Flag{confTargetFlag},
26+
Action: quoteIn,
3527
}
3628

3729
func quoteIn(ctx *cli.Context) error {
@@ -89,7 +81,7 @@ var quoteOutCommand = cli.Command{
8981
Usage: "the number of blocks from the swap " +
9082
"initiation height that the on-chain HTLC " +
9183
"should be swept within in a Loop Out",
92-
Value: 6,
84+
Value: uint64(loop.DefaultSweepConfTarget),
9385
},
9486
cli.BoolFlag{
9587
Name: "fast",

loopd/start.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@ import (
1414
"github.com/lightningnetwork/lnd/build"
1515
)
1616

17-
const (
18-
defaultConfTarget = int32(6)
19-
defaultConfigFilename = "loopd.conf"
20-
)
17+
const defaultConfigFilename = "loopd.conf"
2118

2219
// RPCConfig holds optional options that can be used to make the loop daemon
2320
// communicate on custom connections.

loopd/swapclient_server.go

Lines changed: 38 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: defaultConfTarget,
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: defaultConfTarget,
402+
HtlcConfTarget: htlcConfTarget,
389403
ExternalHtlc: in.ExternalHtlc,
390404
}
391405
if in.LastHop != nil {
@@ -477,6 +491,9 @@ func (s *swapClientServer) processStatusUpdates(mainCtx context.Context) {
477491
// isn't specified (0 value), then the default target is used.
478492
func validateConfTarget(target, defaultTarget int32) (int32, error) {
479493
switch {
494+
case target == 0:
495+
return defaultTarget, nil
496+
480497
// Ensure the target respects our minimum threshold.
481498
case target < minConfTarget:
482499
return 0, fmt.Errorf("a confirmation target of at least %v "+
@@ -486,3 +503,22 @@ func validateConfTarget(target, defaultTarget int32) (int32, error) {
486503
return target, nil
487504
}
488505
}
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: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package loopd
2+
3+
import (
4+
"testing"
5+
6+
"github.com/lightninglabs/loop"
7+
)
8+
9+
// TestValidateConfTarget tests all failure and success cases for our conf
10+
// target validation function, including the case where we replace a zero
11+
// target with the default provided.
12+
func TestValidateConfTarget(t *testing.T) {
13+
const (
14+
// Various input confirmation values for tests.
15+
zeroConf int32 = 0
16+
oneConf int32 = 1
17+
twoConf int32 = 2
18+
fiveConf int32 = 5
19+
20+
// defaultConf is the default confirmation target we use for
21+
// all tests.
22+
defaultConf = 6
23+
)
24+
25+
tests := []struct {
26+
name string
27+
confTarget int32
28+
expectedTarget int32
29+
expectErr bool
30+
}{
31+
{
32+
name: "zero conf, get default",
33+
confTarget: zeroConf,
34+
expectedTarget: defaultConf,
35+
expectErr: false,
36+
},
37+
{
38+
name: "one conf, get error",
39+
confTarget: oneConf,
40+
expectErr: true,
41+
},
42+
{
43+
name: "two conf, ok",
44+
confTarget: twoConf,
45+
expectedTarget: twoConf,
46+
expectErr: false,
47+
},
48+
{
49+
name: "five conf, ok",
50+
confTarget: fiveConf,
51+
expectedTarget: fiveConf,
52+
expectErr: false,
53+
},
54+
}
55+
56+
for _, test := range tests {
57+
test := test
58+
59+
t.Run(test.name, func(t *testing.T) {
60+
target, err := validateConfTarget(
61+
test.confTarget, defaultConf,
62+
)
63+
64+
haveErr := err != nil
65+
if haveErr != test.expectErr {
66+
t.Fatalf("expected err: %v, got: %v",
67+
test.expectErr, err)
68+
}
69+
70+
if target != test.expectedTarget {
71+
t.Fatalf("expected: %v, got: %v",
72+
test.expectedTarget, target)
73+
}
74+
})
75+
}
76+
}
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)