Skip to content

Commit cfa19f8

Browse files
committed
litcli: session registration with generic rules
This commit adds generic feature rule flags. Legacy flags `channel-restrict-list` and `peer-restrict-list` are deprecated.
1 parent c03b040 commit cfa19f8

File tree

1 file changed

+150
-39
lines changed

1 file changed

+150
-39
lines changed

cmd/litcli/autopilot.go

Lines changed: 150 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/lightninglabs/lightning-terminal/litrpc"
1313
"github.com/lightninglabs/lightning-terminal/rules"
14+
"github.com/lightningnetwork/lnd/lnrpc"
1415
"github.com/urfave/cli"
1516
)
1617

@@ -39,10 +40,48 @@ var addAutopilotSessionCmd = cli.Command{
3940
Name: "add",
4041
ShortName: "a",
4142
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.
51+
52+
An example call reads:
53+
54+
./litcli autopilot add --label=customRules \
55+
--feature=SampleFeature \
56+
--feature-rules='{
57+
"rules": {
58+
"rate-limit": {
59+
"rate_limit": {
60+
"read_limit": {
61+
"iterations": 10,
62+
"num_hours": 1
63+
},
64+
"write_limit": {
65+
"iterations": 1,
66+
"num_hours": 1
67+
}
68+
}
69+
},
70+
"channel-restriction": {
71+
"channel_restrict": {
72+
"channel_ids": [
73+
18283782789,
74+
22891928322,
75+
31878293727
76+
]
77+
}
78+
}
79+
}
80+
}' \
81+
--feature-config='{
82+
"version": 0,
83+
"complexity": "complex"
84+
}'`,
4685
Action: initAutopilotSession,
4786
Flags: []cli.Flag{
4887
labelFlag,
@@ -55,17 +94,19 @@ var addAutopilotSessionCmd = cli.Command{
5594
},
5695
cli.StringFlag{
5796
Name: "channel-restrict-list",
58-
Usage: "List of channel IDs that the " +
97+
Usage: "[deprecated] List of channel IDs that the " +
5998
"Autopilot server should not " +
6099
"perform actions on. In the " +
61100
"form of: chanID1,chanID2,...",
101+
Hidden: true,
62102
},
63103
cli.StringFlag{
64104
Name: "peer-restrict-list",
65-
Usage: "List of peer IDs that the " +
105+
Usage: "[deprecated] List of peer IDs that the " +
66106
"Autopilot server should not " +
67107
"perform actions on. In the " +
68108
"form of: peerID1,peerID2,...",
109+
Hidden: true,
69110
},
70111
cli.StringFlag{
71112
Name: "group_id",
@@ -81,6 +122,13 @@ var addAutopilotSessionCmd = cli.Command{
81122
"configuration is allowed with {} to use the " +
82123
"default configuration.",
83124
},
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+
},
84132
},
85133
}
86134

@@ -190,74 +238,137 @@ func initAutopilotSession(ctx *cli.Context) error {
190238
defer cleanup()
191239
client := litrpc.NewAutopilotClient(clientConn)
192240

193-
ruleMap := &litrpc.RulesMap{
194-
Rules: make(map[string]*litrpc.RuleValue),
195-
}
241+
features := ctx.StringSlice("feature")
196242

243+
rulesFlags := ctx.StringSlice("feature-rules")
197244
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)
245+
peerRestrictList := ctx.String("peer-restrict-list")
246+
247+
// rulesMap stores the rules per each feature.
248+
rulesMap := make(map[string]*litrpc.RulesMap)
249+
250+
// For legacy flags, we allow setting the channel and peer restrict
251+
// lists when only a single feature is added.
252+
if chanRestrictList != "" || peerRestrictList != "" {
253+
// Check that the user did not set both the legacy flags and the
254+
// generic rules flags together.
255+
if len(rulesFlags) > 0 {
256+
return fmt.Errorf("either set channel-restrict-list/" +
257+
"peer-restrict-list or feature-rules, not both")
258+
}
259+
260+
if len(features) > 1 {
261+
return fmt.Errorf("cannot set channel-restrict-list/" +
262+
"peer-restrict-list when multiple features " +
263+
"are set")
207264
}
208265

209-
ruleMap.Rules[rules.ChannelRestrictName] = &litrpc.RuleValue{
210-
Value: &litrpc.RuleValue_ChannelRestrict{
211-
ChannelRestrict: &litrpc.ChannelRestrict{
212-
ChannelIds: chanIDs,
266+
feature := features[0]
267+
268+
// Init the rule map for this feature.
269+
ruleMap := make(map[string]*litrpc.RuleValue)
270+
271+
if chanRestrictList != "" {
272+
var chanIDs []uint64
273+
chans := strings.Split(chanRestrictList, ",")
274+
for _, c := range chans {
275+
i, err := strconv.ParseUint(c, 10, 64)
276+
if err != nil {
277+
return err
278+
}
279+
chanIDs = append(chanIDs, i)
280+
}
281+
282+
channelRestrict := &litrpc.ChannelRestrict{
283+
ChannelIds: chanIDs,
284+
}
285+
286+
ruleMap[rules.ChannelRestrictName] = &litrpc.RuleValue{
287+
Value: &litrpc.RuleValue_ChannelRestrict{
288+
ChannelRestrict: channelRestrict,
213289
},
214-
},
290+
}
215291
}
216-
}
217292

218-
peerRestrictList := ctx.String("peer-restrict-list")
219-
if peerRestrictList != "" {
220-
peerIDs := strings.Split(peerRestrictList, ",")
293+
if peerRestrictList != "" {
294+
peerIDs := strings.Split(peerRestrictList, ",")
221295

222-
ruleMap.Rules[rules.PeersRestrictName] = &litrpc.RuleValue{
223-
Value: &litrpc.RuleValue_PeerRestrict{
224-
PeerRestrict: &litrpc.PeerRestrict{
225-
PeerIds: peerIDs,
296+
ruleMap[rules.PeersRestrictName] = &litrpc.RuleValue{
297+
Value: &litrpc.RuleValue_PeerRestrict{
298+
PeerRestrict: &litrpc.PeerRestrict{
299+
PeerIds: peerIDs,
300+
},
226301
},
227-
},
302+
}
303+
}
304+
305+
rulesMap[feature] = &litrpc.RulesMap{Rules: ruleMap}
306+
307+
} else {
308+
// We make sure that if the rules or configs flags are set, they
309+
// are set for all features, to avoid ambiguity.
310+
if len(rulesFlags) > 0 && len(features) != len(rulesFlags) {
311+
return fmt.Errorf("number of features (%v) and rules "+
312+
"(%v) must match", len(features),
313+
len(rulesFlags))
314+
}
315+
316+
// Parse the rules and store them in the rulesMap.
317+
for i, rulesFlag := range rulesFlags {
318+
var ruleMap litrpc.RulesMap
319+
320+
// We allow empty rules, to signal the usage of the
321+
// default rules when the session is registered.
322+
if rulesFlag != "{}" {
323+
err = lnrpc.ProtoJSONUnmarshalOpts.Unmarshal(
324+
[]byte(rulesFlag), &ruleMap,
325+
)
326+
if err != nil {
327+
return err
328+
}
329+
}
330+
331+
rulesMap[features[i]] = &ruleMap
228332
}
229333
}
230334

231-
features := ctx.StringSlice("feature")
232335
configs := ctx.StringSlice("feature-config")
233336
if len(configs) > 0 && len(features) != len(configs) {
234337
return fmt.Errorf("number of features (%v) and configurations "+
235338
"(%v) must match", len(features), len(configs))
236339
}
237340

238-
featureMap := make(map[string]*litrpc.FeatureConfig)
239-
for i, feature := range ctx.StringSlice("feature") {
341+
// Parse the configs and store them in the configsMap.
342+
configsMap := make(map[string][]byte)
343+
for i, configFlag := range configs {
240344
var config []byte
241345

242346
// We allow empty configs, to signal the usage of the default
243347
// configuration when the session is registered.
244-
if len(configs) > 0 && configs[i] != "{}" {
348+
if configFlag != "{}" {
245349
// We expect the config to be a JSON dictionary, so we
246350
// unmarshal it into a map to do a first validation.
247351
var configMap map[string]interface{}
248352
err := json.Unmarshal([]byte(configs[i]), &configMap)
249353
if err != nil {
250354
return fmt.Errorf("could not parse "+
251355
"configuration for feature %v: %v",
252-
feature, err)
356+
features[i], err)
253357
}
254358

255359
config = []byte(configs[i])
256360
}
257361

362+
configsMap[features[i]] = config
363+
}
364+
365+
featureMap := make(map[string]*litrpc.FeatureConfig)
366+
for _, feature := range features {
367+
// Map access for unknown features will return their zero value
368+
// if not set, which is what we want to signal default usage.
258369
featureMap[feature] = &litrpc.FeatureConfig{
259-
Rules: ruleMap,
260-
Config: config,
370+
Rules: rulesMap[feature],
371+
Config: configsMap[feature],
261372
}
262373
}
263374

0 commit comments

Comments
 (0)