@@ -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. 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='{}'` ,
4685 Action : initAutopilotSession ,
4786 Flags : []cli.Flag {
4887 labelFlag ,
@@ -53,19 +92,21 @@ var addAutopilotSessionCmd = cli.Command{
5392 Name : "feature" ,
5493 Required : true ,
5594 },
56- cli.StringFlag {
95+ cli.StringSliceFlag {
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 },
63- cli.StringFlag {
103+ cli.StringSliceFlag {
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,160 @@ 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 ),
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 {}{}
195251 }
196252
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" )
207282 }
208283
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 ,
213313 },
214- },
314+ }
215315 }
216- }
217316
218- peerRestrictList := ctx .String ("peer-restrict-list" )
219- if peerRestrictList != "" {
220- peerIDs := strings .Split (peerRestrictList , "," )
317+ if peerRestrictList != "" {
318+ peerIDs := strings .Split (peerRestrictList , "," )
221319
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+ },
226325 },
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
228355 }
229356 }
230357
231- features := ctx .StringSlice ("feature" )
232358 configs := ctx .StringSlice ("feature-config" )
233359 if len (configs ) > 0 && len (features ) != len (configs ) {
234360 return fmt .Errorf ("number of features (%v) and configurations " +
235361 "(%v) must match" , len (features ), len (configs ))
236362 }
237363
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 {
240367 var config []byte
241368
242369 // We allow empty configs, to signal the usage of the default
243370 // configuration when the session is registered.
244- if len ( configs ) > 0 && configs [ i ] != "{}" {
371+ if configFlag != "{}" {
245372 // We expect the config to be a JSON dictionary, so we
246373 // unmarshal it into a map to do a first validation.
247374 var configMap map [string ]interface {}
248375 err := json .Unmarshal ([]byte (configs [i ]), & configMap )
249376 if err != nil {
250377 return fmt .Errorf ("could not parse " +
251378 "configuration for feature %v: %v" ,
252- feature , err )
379+ features [ i ] , err )
253380 }
254381
255382 config = []byte (configs [i ])
256383 }
257384
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.
258392 featureMap [feature ] = & litrpc.FeatureConfig {
259- Rules : ruleMap ,
260- Config : config ,
393+ Rules : rulesMap [ feature ] ,
394+ Config : configsMap [ feature ] ,
261395 }
262396 }
263397
0 commit comments