@@ -11,6 +11,7 @@ import (
11
11
12
12
"github.com/lightninglabs/lightning-terminal/litrpc"
13
13
"github.com/lightninglabs/lightning-terminal/rules"
14
+ "github.com/lightningnetwork/lnd/lnrpc"
14
15
"github.com/urfave/cli"
15
16
)
16
17
@@ -39,10 +40,48 @@ var addAutopilotSessionCmd = cli.Command{
39
40
Name : "add" ,
40
41
ShortName : "a" ,
41
42
Usage : "Initialize an Autopilot session." ,
42
- Description : "Initialize an Autopilot session.\n \n " +
43
- " If set for any feature, configuration flags need to be " +
44
- "repeated for each feature that is registered, corresponding " +
45
- "to the order of features." ,
43
+ Description : `
44
+ Initialize an Autopilot session.
45
+
46
+ If one of the 'feature-' flags is set for any 'feature', then that flag
47
+ must be provided for each 'feature'.
48
+
49
+ The rules and configuration options available for each feature can be
50
+ seen in the 'autopilot features' output. For a rule, all fields must be
51
+ set since the unset ones are interpreteded as zero values. Rule values
52
+ must adhere to the limits found in 'autopilot features'. If a rule is
53
+ not set, default values are used.
54
+
55
+ An example call for AutoFees reads:
56
+
57
+ #!/bin/bash
58
+ ./litcli autopilot add --label=customRules \
59
+ --feature=AutoFees \
60
+ --feature-rules='{
61
+ "rules": {
62
+ "channel-policy-bounds": {
63
+ "chan_policy_bounds": {
64
+ "min_base_msat": "0",
65
+ "max_base_msat": "10000",
66
+ "min_rate_ppm": 10,
67
+ "max_rate_ppm": 5000,
68
+ "min_cltv_delta": 60,
69
+ "max_cltv_delta": 120,
70
+ "min_htlc_msat": "1",
71
+ "max_htlc_msat": "100000000000"
72
+ }
73
+ },
74
+ "peer-restriction": {
75
+ "peer_restrict": {
76
+ "peer_ids": [
77
+ "abcabc",
78
+ "defdef"
79
+ ]
80
+ }
81
+ }
82
+ }
83
+ }' \
84
+ --feature-config='{}'` ,
46
85
Action : initAutopilotSession ,
47
86
Flags : []cli.Flag {
48
87
labelFlag ,
@@ -53,19 +92,21 @@ var addAutopilotSessionCmd = cli.Command{
53
92
Name : "feature" ,
54
93
Required : true ,
55
94
},
56
- cli.StringFlag {
95
+ cli.StringSliceFlag {
57
96
Name : "channel-restrict-list" ,
58
- Usage : "List of channel IDs that the " +
97
+ Usage : "[deprecated] List of channel IDs that the " +
59
98
"Autopilot server should not " +
60
99
"perform actions on. In the " +
61
100
"form of: chanID1,chanID2,..." ,
101
+ Hidden : true ,
62
102
},
63
- cli.StringFlag {
103
+ cli.StringSliceFlag {
64
104
Name : "peer-restrict-list" ,
65
- Usage : "List of peer IDs that the " +
105
+ Usage : "[deprecated] List of peer IDs that the " +
66
106
"Autopilot server should not " +
67
107
"perform actions on. In the " +
68
108
"form of: peerID1,peerID2,..." ,
109
+ Hidden : true ,
69
110
},
70
111
cli.StringFlag {
71
112
Name : "group_id" ,
@@ -81,6 +122,13 @@ var addAutopilotSessionCmd = cli.Command{
81
122
"configuration is allowed with {} to use the " +
82
123
"default configuration." ,
83
124
},
125
+ cli.StringSliceFlag {
126
+ Name : "feature-rules" ,
127
+ Usage : `JSON-serialized rule map (see main ` +
128
+ `description for a format example).` +
129
+ `An empty rule map is allowed with {} to ` +
130
+ `use the default rules.` ,
131
+ },
84
132
},
85
133
}
86
134
@@ -190,74 +238,160 @@ func initAutopilotSession(ctx *cli.Context) error {
190
238
defer cleanup ()
191
239
client := litrpc .NewAutopilotClient (clientConn )
192
240
193
- ruleMap := & litrpc.RulesMap {
194
- Rules : make (map [string ]* litrpc.RuleValue ),
241
+ features := ctx .StringSlice ("feature" )
242
+
243
+ // Check that the user only sets unique features.
244
+ fs := make (map [string ]struct {})
245
+ for _ , feature := range features {
246
+ if _ , ok := fs [feature ]; ok {
247
+ return fmt .Errorf ("feature %v is set multiple times" ,
248
+ feature )
249
+ }
250
+ fs [feature ] = struct {}{}
195
251
}
196
252
197
- chanRestrictList := ctx .String ("channel-restrict-list" )
198
- if chanRestrictList != "" {
199
- var chanIDs []uint64
200
- chans := strings .Split (chanRestrictList , "," )
201
- for _ , c := range chans {
202
- i , err := strconv .ParseUint (c , 10 , 64 )
203
- if err != nil {
204
- return err
205
- }
206
- chanIDs = append (chanIDs , i )
253
+ // Check that the user did not set multiple restrict lists.
254
+ var chanRestrictList , peerRestrictList string
255
+
256
+ channelRestrictSlice := ctx .StringSlice ("channel-restrict-list" )
257
+ if len (channelRestrictSlice ) > 1 {
258
+ return fmt .Errorf ("channel-restrict-list can only be used once" )
259
+ } else if len (channelRestrictSlice ) == 1 {
260
+ chanRestrictList = channelRestrictSlice [0 ]
261
+ }
262
+
263
+ peerRestrictSlice := ctx .StringSlice ("peer-restrict-list" )
264
+ if len (peerRestrictSlice ) > 1 {
265
+ return fmt .Errorf ("peer-restrict-list can only be used once" )
266
+ } else if len (peerRestrictSlice ) == 1 {
267
+ peerRestrictList = peerRestrictSlice [0 ]
268
+ }
269
+
270
+ // rulesMap stores the rules per each feature.
271
+ rulesMap := make (map [string ]* litrpc.RulesMap )
272
+ rulesFlags := ctx .StringSlice ("feature-rules" )
273
+
274
+ // For legacy flags, we allow setting the channel and peer restrict
275
+ // lists when only a single feature is added.
276
+ if chanRestrictList != "" || peerRestrictList != "" {
277
+ // Check that the user did not set both the legacy flags and the
278
+ // generic rules flags together.
279
+ if len (rulesFlags ) > 0 {
280
+ return fmt .Errorf ("either set channel-restrict-list/" +
281
+ "peer-restrict-list or feature-rules, not both" )
207
282
}
208
283
209
- ruleMap .Rules [rules .ChannelRestrictName ] = & litrpc.RuleValue {
210
- Value : & litrpc.RuleValue_ChannelRestrict {
211
- ChannelRestrict : & litrpc.ChannelRestrict {
212
- ChannelIds : chanIDs ,
284
+ if len (features ) > 1 {
285
+ return fmt .Errorf ("cannot set channel-restrict-list/" +
286
+ "peer-restrict-list when multiple features " +
287
+ "are set" )
288
+ }
289
+
290
+ feature := features [0 ]
291
+
292
+ // Init the rule map for this feature.
293
+ ruleMap := make (map [string ]* litrpc.RuleValue )
294
+
295
+ if chanRestrictList != "" {
296
+ var chanIDs []uint64
297
+ chans := strings .Split (chanRestrictList , "," )
298
+ for _ , c := range chans {
299
+ i , err := strconv .ParseUint (c , 10 , 64 )
300
+ if err != nil {
301
+ return err
302
+ }
303
+ chanIDs = append (chanIDs , i )
304
+ }
305
+
306
+ channelRestrict := & litrpc.ChannelRestrict {
307
+ ChannelIds : chanIDs ,
308
+ }
309
+
310
+ ruleMap [rules .ChannelRestrictName ] = & litrpc.RuleValue {
311
+ Value : & litrpc.RuleValue_ChannelRestrict {
312
+ ChannelRestrict : channelRestrict ,
213
313
},
214
- },
314
+ }
215
315
}
216
- }
217
316
218
- peerRestrictList := ctx .String ("peer-restrict-list" )
219
- if peerRestrictList != "" {
220
- peerIDs := strings .Split (peerRestrictList , "," )
317
+ if peerRestrictList != "" {
318
+ peerIDs := strings .Split (peerRestrictList , "," )
221
319
222
- ruleMap .Rules [rules .PeersRestrictName ] = & litrpc.RuleValue {
223
- Value : & litrpc.RuleValue_PeerRestrict {
224
- PeerRestrict : & litrpc.PeerRestrict {
225
- PeerIds : peerIDs ,
320
+ ruleMap [rules .PeersRestrictName ] = & litrpc.RuleValue {
321
+ Value : & litrpc.RuleValue_PeerRestrict {
322
+ PeerRestrict : & litrpc.PeerRestrict {
323
+ PeerIds : peerIDs ,
324
+ },
226
325
},
227
- },
326
+ }
327
+ }
328
+
329
+ rulesMap [feature ] = & litrpc.RulesMap {Rules : ruleMap }
330
+ } else {
331
+ // We make sure that if the rules or configs flags are set, they
332
+ // are set for all features, to avoid ambiguity.
333
+ if len (rulesFlags ) > 0 && len (features ) != len (rulesFlags ) {
334
+ return fmt .Errorf ("number of features (%v) and rules " +
335
+ "(%v) must match" , len (features ),
336
+ len (rulesFlags ))
337
+ }
338
+
339
+ // Parse the rules and store them in the rulesMap.
340
+ for i , rulesFlag := range rulesFlags {
341
+ var ruleMap litrpc.RulesMap
342
+
343
+ // We allow empty rules, to signal the usage of the
344
+ // default rules when the session is registered.
345
+ if rulesFlag != "{}" {
346
+ err = lnrpc .ProtoJSONUnmarshalOpts .Unmarshal (
347
+ []byte (rulesFlag ), & ruleMap ,
348
+ )
349
+ if err != nil {
350
+ return err
351
+ }
352
+ }
353
+
354
+ rulesMap [features [i ]] = & ruleMap
228
355
}
229
356
}
230
357
231
- features := ctx .StringSlice ("feature" )
232
358
configs := ctx .StringSlice ("feature-config" )
233
359
if len (configs ) > 0 && len (features ) != len (configs ) {
234
360
return fmt .Errorf ("number of features (%v) and configurations " +
235
361
"(%v) must match" , len (features ), len (configs ))
236
362
}
237
363
238
- featureMap := make (map [string ]* litrpc.FeatureConfig )
239
- for i , feature := range ctx .StringSlice ("feature" ) {
364
+ // Parse the configs and store them in the configsMap.
365
+ configsMap := make (map [string ][]byte )
366
+ for i , configFlag := range configs {
240
367
var config []byte
241
368
242
369
// We allow empty configs, to signal the usage of the default
243
370
// configuration when the session is registered.
244
- if len ( configs ) > 0 && configs [ i ] != "{}" {
371
+ if configFlag != "{}" {
245
372
// We expect the config to be a JSON dictionary, so we
246
373
// unmarshal it into a map to do a first validation.
247
374
var configMap map [string ]interface {}
248
375
err := json .Unmarshal ([]byte (configs [i ]), & configMap )
249
376
if err != nil {
250
377
return fmt .Errorf ("could not parse " +
251
378
"configuration for feature %v: %v" ,
252
- feature , err )
379
+ features [ i ] , err )
253
380
}
254
381
255
382
config = []byte (configs [i ])
256
383
}
257
384
385
+ configsMap [features [i ]] = config
386
+ }
387
+
388
+ featureMap := make (map [string ]* litrpc.FeatureConfig )
389
+ for _ , feature := range features {
390
+ // Map access for unknown features will return their zero value
391
+ // if not set, which is what we want to signal default usage.
258
392
featureMap [feature ] = & litrpc.FeatureConfig {
259
- Rules : ruleMap ,
260
- Config : config ,
393
+ Rules : rulesMap [ feature ] ,
394
+ Config : configsMap [ feature ] ,
261
395
}
262
396
}
263
397
0 commit comments