litcli: session registration with generic rules

This commit adds generic feature rule flags. Legacy flags
`channel-restrict-list` and `peer-restrict-list` are deprecated.
This commit is contained in:
bitromortac 2024-01-26 14:01:16 +01:00
parent c03b040e4d
commit 34eeb2ae05
No known key found for this signature in database
GPG key ID: 1965063FC13BEBE2

View file

@ -11,6 +11,7 @@ import (
"github.com/lightninglabs/lightning-terminal/litrpc"
"github.com/lightninglabs/lightning-terminal/rules"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/urfave/cli"
)
@ -39,10 +40,48 @@ var addAutopilotSessionCmd = cli.Command{
Name: "add",
ShortName: "a",
Usage: "Initialize an Autopilot session.",
Description: "Initialize an Autopilot session.\n\n" +
" If set for any feature, configuration flags need to be " +
"repeated for each feature that is registered, corresponding " +
"to the order of features.",
Description: `
Initialize an Autopilot session.
If one of the 'feature-' flags is set for any 'feature', then that flag
must be provided for each 'feature'.
The rules and configuration options available for each feature can be
seen in the 'autopilot features' output. For a rule, all fields must be
set since the unset ones are interpreteded as zero values. Rule values
must adhere to the limits found in 'autopilot features'. If a rule is
not set, default values are used.
An example call for AutoFees reads:
#!/bin/bash
./litcli autopilot add --label=customRules \
--feature=AutoFees \
--feature-rules='{
"rules": {
"channel-policy-bounds": {
"chan_policy_bounds": {
"min_base_msat": "0",
"max_base_msat": "10000",
"min_rate_ppm": 10,
"max_rate_ppm": 5000,
"min_cltv_delta": 60,
"max_cltv_delta": 120,
"min_htlc_msat": "1",
"max_htlc_msat": "100000000000"
}
},
"peer-restriction": {
"peer_restrict": {
"peer_ids": [
"abcabc",
"defdef"
]
}
}
}
}' \
--feature-config='{}'`,
Action: initAutopilotSession,
Flags: []cli.Flag{
labelFlag,
@ -53,19 +92,21 @@ var addAutopilotSessionCmd = cli.Command{
Name: "feature",
Required: true,
},
cli.StringFlag{
cli.StringSliceFlag{
Name: "channel-restrict-list",
Usage: "List of channel IDs that the " +
Usage: "[deprecated] List of channel IDs that the " +
"Autopilot server should not " +
"perform actions on. In the " +
"form of: chanID1,chanID2,...",
Hidden: true,
},
cli.StringFlag{
cli.StringSliceFlag{
Name: "peer-restrict-list",
Usage: "List of peer IDs that the " +
Usage: "[deprecated] List of peer IDs that the " +
"Autopilot server should not " +
"perform actions on. In the " +
"form of: peerID1,peerID2,...",
Hidden: true,
},
cli.StringFlag{
Name: "group_id",
@ -81,6 +122,13 @@ var addAutopilotSessionCmd = cli.Command{
"configuration is allowed with {} to use the " +
"default configuration.",
},
cli.StringSliceFlag{
Name: "feature-rules",
Usage: `JSON-serialized rule map (see main ` +
`description for a format example).` +
`An empty rule map is allowed with {} to ` +
`use the default rules.`,
},
},
}
@ -190,58 +238,137 @@ func initAutopilotSession(ctx *cli.Context) error {
defer cleanup()
client := litrpc.NewAutopilotClient(clientConn)
ruleMap := &litrpc.RulesMap{
Rules: make(map[string]*litrpc.RuleValue),
}
chanRestrictList := ctx.String("channel-restrict-list")
if chanRestrictList != "" {
var chanIDs []uint64
chans := strings.Split(chanRestrictList, ",")
for _, c := range chans {
i, err := strconv.ParseUint(c, 10, 64)
if err != nil {
return err
}
chanIDs = append(chanIDs, i)
}
ruleMap.Rules[rules.ChannelRestrictName] = &litrpc.RuleValue{
Value: &litrpc.RuleValue_ChannelRestrict{
ChannelRestrict: &litrpc.ChannelRestrict{
ChannelIds: chanIDs,
},
},
}
}
peerRestrictList := ctx.String("peer-restrict-list")
if peerRestrictList != "" {
peerIDs := strings.Split(peerRestrictList, ",")
ruleMap.Rules[rules.PeersRestrictName] = &litrpc.RuleValue{
Value: &litrpc.RuleValue_PeerRestrict{
PeerRestrict: &litrpc.PeerRestrict{
PeerIds: peerIDs,
},
},
}
}
features := ctx.StringSlice("feature")
// Check that the user only sets unique features.
fs := make(map[string]struct{})
for _, feature := range features {
if _, ok := fs[feature]; ok {
return fmt.Errorf("feature %v is set multiple times",
feature)
}
fs[feature] = struct{}{}
}
// Check that the user did not set multiple restrict lists.
var chanRestrictList, peerRestrictList string
channelRestrictSlice := ctx.StringSlice("channel-restrict-list")
if len(channelRestrictSlice) > 1 {
return fmt.Errorf("channel-restrict-list can only be used once")
} else if len(channelRestrictSlice) == 1 {
chanRestrictList = channelRestrictSlice[0]
}
peerRestrictSlice := ctx.StringSlice("peer-restrict-list")
if len(peerRestrictSlice) > 1 {
return fmt.Errorf("peer-restrict-list can only be used once")
} else if len(peerRestrictSlice) == 1 {
peerRestrictList = peerRestrictSlice[0]
}
// rulesMap stores the rules per each feature.
rulesMap := make(map[string]*litrpc.RulesMap)
rulesFlags := ctx.StringSlice("feature-rules")
// For legacy flags, we allow setting the channel and peer restrict
// lists when only a single feature is added.
if chanRestrictList != "" || peerRestrictList != "" {
// Check that the user did not set both the legacy flags and the
// generic rules flags together.
if len(rulesFlags) > 0 {
return fmt.Errorf("either set channel-restrict-list/" +
"peer-restrict-list or feature-rules, not both")
}
if len(features) > 1 {
return fmt.Errorf("cannot set channel-restrict-list/" +
"peer-restrict-list when multiple features " +
"are set")
}
feature := features[0]
// Init the rule map for this feature.
ruleMap := make(map[string]*litrpc.RuleValue)
if chanRestrictList != "" {
var chanIDs []uint64
chans := strings.Split(chanRestrictList, ",")
for _, c := range chans {
i, err := strconv.ParseUint(c, 10, 64)
if err != nil {
return err
}
chanIDs = append(chanIDs, i)
}
channelRestrict := &litrpc.ChannelRestrict{
ChannelIds: chanIDs,
}
ruleMap[rules.ChannelRestrictName] = &litrpc.RuleValue{
Value: &litrpc.RuleValue_ChannelRestrict{
ChannelRestrict: channelRestrict,
},
}
}
if peerRestrictList != "" {
peerIDs := strings.Split(peerRestrictList, ",")
ruleMap[rules.PeersRestrictName] = &litrpc.RuleValue{
Value: &litrpc.RuleValue_PeerRestrict{
PeerRestrict: &litrpc.PeerRestrict{
PeerIds: peerIDs,
},
},
}
}
rulesMap[feature] = &litrpc.RulesMap{Rules: ruleMap}
} else {
// We make sure that if the rules or configs flags are set, they
// are set for all features, to avoid ambiguity.
if len(rulesFlags) > 0 && len(features) != len(rulesFlags) {
return fmt.Errorf("number of features (%v) and rules "+
"(%v) must match", len(features),
len(rulesFlags))
}
// Parse the rules and store them in the rulesMap.
for i, rulesFlag := range rulesFlags {
var ruleMap litrpc.RulesMap
// We allow empty rules, to signal the usage of the
// default rules when the session is registered.
if rulesFlag != "{}" {
err = lnrpc.ProtoJSONUnmarshalOpts.Unmarshal(
[]byte(rulesFlag), &ruleMap,
)
if err != nil {
return err
}
}
rulesMap[features[i]] = &ruleMap
}
}
configs := ctx.StringSlice("feature-config")
if len(configs) > 0 && len(features) != len(configs) {
return fmt.Errorf("number of features (%v) and configurations "+
"(%v) must match", len(features), len(configs))
}
featureMap := make(map[string]*litrpc.FeatureConfig)
for i, feature := range ctx.StringSlice("feature") {
// Parse the configs and store them in the configsMap.
configsMap := make(map[string][]byte)
for i, configFlag := range configs {
var config []byte
// We allow empty configs, to signal the usage of the default
// configuration when the session is registered.
if len(configs) > 0 && configs[i] != "{}" {
if configFlag != "{}" {
// We expect the config to be a JSON dictionary, so we
// unmarshal it into a map to do a first validation.
var configMap map[string]interface{}
@ -249,15 +376,22 @@ func initAutopilotSession(ctx *cli.Context) error {
if err != nil {
return fmt.Errorf("could not parse "+
"configuration for feature %v: %v",
feature, err)
features[i], err)
}
config = []byte(configs[i])
}
configsMap[features[i]] = config
}
featureMap := make(map[string]*litrpc.FeatureConfig)
for _, feature := range features {
// Map access for unknown features will return their zero value
// if not set, which is what we want to signal default usage.
featureMap[feature] = &litrpc.FeatureConfig{
Rules: ruleMap,
Config: config,
Rules: rulesMap[feature],
Config: configsMap[feature],
}
}