Skip to content

Commit e15549e

Browse files
authored
Merge pull request #263 from carlaKC/205-addlabelling
multi: add labelling to swaps
2 parents e2c54bd + f62d095 commit e15549e

File tree

15 files changed

+431
-138
lines changed

15 files changed

+431
-138
lines changed

cmd/loop/loopin.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/btcsuite/btcutil"
88
"github.com/lightninglabs/loop"
9+
"github.com/lightninglabs/loop/labels"
910
"github.com/lightninglabs/loop/looprpc"
1011
"github.com/lightningnetwork/lnd/routing/route"
1112
"github.com/urfave/cli"
@@ -24,6 +25,14 @@ var (
2425
"confirm within",
2526
}
2627

28+
labelFlag = cli.StringFlag{
29+
Name: "label",
30+
Usage: fmt.Sprintf("an optional label for this swap,"+
31+
"limited to %v characters. The label may not start "+
32+
"with our reserved prefix: %v.",
33+
labels.MaxLength, labels.Reserved),
34+
}
35+
2736
loopInCommand = cli.Command{
2837
Name: "in",
2938
Usage: "perform an on-chain to off-chain swap (loop in)",
@@ -51,6 +60,7 @@ var (
5160
},
5261
confTargetFlag,
5362
lastHopFlag,
63+
labelFlag,
5464
},
5565
Action: loopIn,
5666
}
@@ -93,6 +103,12 @@ func loopIn(ctx *cli.Context) error {
93103
return fmt.Errorf("external and conf_target both set")
94104
}
95105

106+
// Validate our label early so that we can fail before getting a quote.
107+
label := ctx.String(labelFlag.Name)
108+
if err := labels.Validate(label); err != nil {
109+
return err
110+
}
111+
96112
quote, err := client.GetLoopInQuote(
97113
context.Background(),
98114
&looprpc.QuoteRequest{
@@ -133,6 +149,7 @@ func loopIn(ctx *cli.Context) error {
133149
MaxSwapFee: int64(limits.maxSwapFee),
134150
ExternalHtlc: external,
135151
HtlcConfTarget: htlcConfTarget,
152+
Label: label,
136153
}
137154

138155
if ctx.IsSet(lastHopFlag.Name) {

cmd/loop/loopout.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/btcsuite/btcutil"
1111
"github.com/lightninglabs/loop"
12+
"github.com/lightninglabs/loop/labels"
1213
"github.com/lightninglabs/loop/looprpc"
1314
"github.com/urfave/cli"
1415
)
@@ -64,6 +65,7 @@ var loopOutCommand = cli.Command{
6465
"Not setting this flag therefore might " +
6566
"result in a lower swap fee.",
6667
},
68+
labelFlag,
6769
},
6870
Action: loopOut,
6971
}
@@ -104,6 +106,12 @@ func loopOut(ctx *cli.Context) error {
104106
}
105107
}
106108

109+
// Validate our label early so that we can fail before getting a quote.
110+
label := ctx.String(labelFlag.Name)
111+
if err := labels.Validate(label); err != nil {
112+
return err
113+
}
114+
107115
var destAddr string
108116
switch {
109117
case ctx.IsSet("addr"):
@@ -172,6 +180,7 @@ func loopOut(ctx *cli.Context) error {
172180
OutgoingChanSet: outgoingChanSet,
173181
SweepConfTarget: sweepConfTarget,
174182
SwapPublicationDeadline: uint64(swapDeadline.Unix()),
183+
Label: label,
175184
})
176185
if err != nil {
177186
return err

interface.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ type OutRequest struct {
7474

7575
// Expiry is the absolute expiry height of the on-chain htlc.
7676
Expiry int32
77+
78+
// Label contains an optional label for the swap.
79+
Label string
7780
}
7881

7982
// Out contains the full details of a loop out request. This includes things
@@ -186,6 +189,9 @@ type LoopInRequest struct {
186189
// ExternalHtlc specifies whether the htlc is published by an external
187190
// source.
188191
ExternalHtlc bool
192+
193+
// Label contains an optional label for the swap.
194+
Label string
189195
}
190196

191197
// LoopInTerms are the server terms on which it executes loop in swaps.

labels/labels.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package labels
2+
3+
import (
4+
"errors"
5+
)
6+
7+
const (
8+
// MaxLength is the maximum length we allow for labels.
9+
MaxLength = 500
10+
11+
// Reserved is used as a prefix to separate labels that are created by
12+
// loopd from those created by users.
13+
Reserved = "[reserved]"
14+
)
15+
16+
var (
17+
// ErrLabelTooLong is returned when a label exceeds our length limit.
18+
ErrLabelTooLong = errors.New("label exceeds maximum length")
19+
20+
// ErrReservedPrefix is returned when a label contains the prefix
21+
// which is reserved for internally produced labels.
22+
ErrReservedPrefix = errors.New("label contains reserved prefix")
23+
)
24+
25+
// Validate checks that a label is of appropriate length and is not in our list
26+
// of reserved labels.
27+
func Validate(label string) error {
28+
if len(label) > MaxLength {
29+
return ErrLabelTooLong
30+
}
31+
32+
// If the label is shorter than our reserved prefix, it cannot contain
33+
// it.
34+
if len(label) < len(Reserved) {
35+
return nil
36+
}
37+
38+
// Check if our label begins with our reserved prefix. We don't mind if
39+
// it has our reserved prefix in another case, we just need to be able
40+
// to reserve a subset of labels with this prefix.
41+
if label[0:len(Reserved)] == Reserved {
42+
return ErrReservedPrefix
43+
}
44+
45+
return nil
46+
}

labels/labels_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package labels
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"testing"
7+
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
// TestValidate tests validation of labels.
12+
func TestValidate(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
label string
16+
err error
17+
}{
18+
{
19+
name: "label ok",
20+
label: "label",
21+
err: nil,
22+
},
23+
{
24+
name: "exceeds limit",
25+
label: strings.Repeat(" ", MaxLength+1),
26+
err: ErrLabelTooLong,
27+
},
28+
{
29+
name: "exactly reserved prefix",
30+
label: Reserved,
31+
err: ErrReservedPrefix,
32+
},
33+
{
34+
name: "starts with reserved prefix",
35+
label: fmt.Sprintf("%v test", Reserved),
36+
err: ErrReservedPrefix,
37+
},
38+
{
39+
name: "ends with reserved prefix",
40+
label: fmt.Sprintf("test %v", Reserved),
41+
err: nil,
42+
},
43+
}
44+
45+
for _, test := range tests {
46+
test := test
47+
48+
t.Run(test.name, func(t *testing.T) {
49+
t.Parallel()
50+
require.Equal(t, test.err, Validate(test.label))
51+
})
52+
}
53+
}

loopd/swapclient_server.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ func (s *swapClientServer) LoopOut(ctx context.Context,
8989
SwapPublicationDeadline: time.Unix(
9090
int64(in.SwapPublicationDeadline), 0,
9191
),
92+
Label: in.Label,
9293
}
9394

9495
switch {
@@ -214,6 +215,7 @@ func (s *swapClientServer) marshallSwap(loopSwap *loop.SwapInfo) (
214215
CostServer: int64(loopSwap.Cost.Server),
215216
CostOnchain: int64(loopSwap.Cost.Onchain),
216217
CostOffchain: int64(loopSwap.Cost.Offchain),
218+
Label: loopSwap.Label,
217219
}, nil
218220
}
219221

@@ -478,6 +480,7 @@ func (s *swapClientServer) LoopIn(ctx context.Context,
478480
MaxSwapFee: btcutil.Amount(in.MaxSwapFee),
479481
HtlcConfTarget: htlcConfTarget,
480482
ExternalHtlc: in.ExternalHtlc,
483+
Label: in.Label,
481484
}
482485
if in.LastHop != nil {
483486
lastHop, err := route.NewVertexFromBytes(in.LastHop)

loopdb/loop.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ type SwapContract struct {
4343

4444
// InitiationTime is the time at which the swap was initiated.
4545
InitiationTime time.Time
46+
47+
// Label contains an optional label for the swap.
48+
Label string
4649
}
4750

4851
// Loop contains fields shared between LoopIn and LoopOut

loopdb/loopin.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"fmt"
77
"time"
88

9+
"github.com/coreos/bbolt"
10+
"github.com/lightninglabs/loop/labels"
911
"github.com/lightningnetwork/lnd/routing/route"
1012
)
1113

@@ -24,6 +26,10 @@ type LoopInContract struct {
2426
// ExternalHtlc specifies whether the htlc is published by an external
2527
// source.
2628
ExternalHtlc bool
29+
30+
// Label contains an optional label for the swap. Note that this field
31+
// is stored separately to the rest of the contract on disk.
32+
Label string
2733
}
2834

2935
// LoopIn is a combination of the contract and the updates.
@@ -112,6 +118,31 @@ func serializeLoopInContract(swap *LoopInContract) (
112118
return b.Bytes(), nil
113119
}
114120

121+
// putLabel performs validation of a label and writes it to the bucket provided
122+
// under the label key if it is non-zero.
123+
func putLabel(bucket *bbolt.Bucket, label string) error {
124+
if len(label) == 0 {
125+
return nil
126+
}
127+
128+
if err := labels.Validate(label); err != nil {
129+
return err
130+
}
131+
132+
return bucket.Put(labelKey, []byte(label))
133+
}
134+
135+
// getLabel attempts to get an optional label stored under the label key in a
136+
// bucket. If it is not present, an empty label is returned.
137+
func getLabel(bucket *bbolt.Bucket) string {
138+
label := bucket.Get(labelKey)
139+
if label == nil {
140+
return ""
141+
}
142+
143+
return string(label)
144+
}
145+
115146
// deserializeLoopInContract deserializes the loop in contract from a byte slice.
116147
func deserializeLoopInContract(value []byte) (*LoopInContract, error) {
117148
r := bytes.NewReader(value)

loopdb/store.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ var (
6060
// value: time || rawSwapState
6161
contractKey = []byte("contract")
6262

63+
// labelKey is the key that stores an optional label for the swap. If
64+
// a swap was created before we started adding labels, or was created
65+
// without a label, this key will not be present.
66+
//
67+
// path: loopInBucket/loopOutBucket -> swapBucket[hash] -> labelKey
68+
//
69+
// value: string label
70+
labelKey = []byte("label")
71+
6372
// outgoingChanSetKey is the key that stores a list of channel ids that
6473
// restrict the loop out swap payment.
6574
//
@@ -207,6 +216,9 @@ func (s *boltSwapStore) FetchLoopOutSwaps() ([]*LoopOut, error) {
207216
return err
208217
}
209218

219+
// Get our label for this swap, if it is present.
220+
contract.Label = getLabel(swapBucket)
221+
210222
// Read the list of concatenated outgoing channel ids
211223
// that form the outgoing set.
212224
setBytes := swapBucket.Get(outgoingChanSetKey)
@@ -352,6 +364,9 @@ func (s *boltSwapStore) FetchLoopInSwaps() ([]*LoopIn, error) {
352364
return err
353365
}
354366

367+
// Get our label for this swap, if it is present.
368+
contract.Label = getLabel(swapBucket)
369+
355370
updates, err := deserializeUpdates(swapBucket)
356371
if err != nil {
357372
return err
@@ -434,6 +449,10 @@ func (s *boltSwapStore) CreateLoopOut(hash lntypes.Hash,
434449
return err
435450
}
436451

452+
if err := putLabel(swapBucket, swap.Label); err != nil {
453+
return err
454+
}
455+
437456
// Write the outgoing channel set.
438457
var b bytes.Buffer
439458
for _, chanID := range swap.OutgoingChanSet {
@@ -447,6 +466,11 @@ func (s *boltSwapStore) CreateLoopOut(hash lntypes.Hash,
447466
return err
448467
}
449468

469+
// Write label to disk if we have one.
470+
if err := putLabel(swapBucket, swap.Label); err != nil {
471+
return err
472+
}
473+
450474
// Finally, we'll create an empty updates bucket for this swap
451475
// to track any future updates to the swap itself.
452476
_, err = swapBucket.CreateBucket(updatesBucketKey)
@@ -485,6 +509,11 @@ func (s *boltSwapStore) CreateLoopIn(hash lntypes.Hash,
485509
return err
486510
}
487511

512+
// Write label to disk if we have one.
513+
if err := putLabel(swapBucket, swap.Label); err != nil {
514+
return err
515+
}
516+
488517
// Finally, we'll create an empty updates bucket for this swap
489518
// to track any future updates to the swap itself.
490519
_, err = swapBucket.CreateBucket(updatesBucketKey)

0 commit comments

Comments
 (0)