lnd/cmd/lncli/cmd_mission_control.go
2023-03-23 11:12:10 +01:00

371 lines
8.9 KiB
Go

package main
import (
"fmt"
"strconv"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/urfave/cli"
)
var getCfgCommand = cli.Command{
Name: "getmccfg",
Category: "Mission Control",
Usage: "Display mission control's config.",
Description: `
Returns the config currently being used by mission control.
`,
Action: actionDecorator(getCfg),
}
func getCfg(ctx *cli.Context) error {
ctxc := getContext()
conn := getClientConn(ctx, false)
defer conn.Close()
client := routerrpc.NewRouterClient(conn)
resp, err := client.GetMissionControlConfig(
ctxc, &routerrpc.GetMissionControlConfigRequest{},
)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var setCfgCommand = cli.Command{
Name: "setmccfg",
Category: "Mission Control",
Usage: "Set mission control's config.",
Description: `
Update the config values being used by mission control to calculate the
probability that payment routes will succeed. The estimator type must be
provided to set estimator-related parameters.`,
Flags: []cli.Flag{
// General settings.
cli.UintFlag{
Name: "pmtnr",
Usage: "the number of payments mission control " +
"should store",
},
cli.DurationFlag{
Name: "failrelax",
Usage: "the amount of time to wait after a failure " +
"before raising failure amount",
},
// Probability estimator.
cli.StringFlag{
Name: "estimator",
Usage: "the probability estimator to use, choose " +
"between 'apriori' or 'bimodal' (bimodal is " +
"experimental)",
},
// Apriori config.
cli.DurationFlag{
Name: "apriorihalflife",
Usage: "the amount of time taken to restore a node " +
"or channel to 50% probability of success.",
},
cli.Float64Flag{
Name: "apriorihopprob",
Usage: "the probability of success assigned " +
"to hops that we have no information about",
},
cli.Float64Flag{
Name: "aprioriweight",
Usage: "the degree to which mission control should " +
"rely on historical results, expressed as " +
"value in [0, 1]",
},
cli.Float64Flag{
Name: "aprioricapacityfraction",
Usage: "the fraction of channels' capacities that is " +
"considered liquid in pathfinding, a value " +
"between [0.75-1.0]. a value of 1.0 disables " +
"this feature.",
},
// Bimodal config.
cli.DurationFlag{
Name: "bimodaldecaytime",
Usage: "the time span after which we phase out " +
"learnings from previous payment attempts",
},
cli.Uint64Flag{
Name: "bimodalscale",
Usage: "controls the assumed channel liquidity " +
"imbalance in the network, measured in msat. " +
"a low value (compared to typical channel " +
"capacity) anticipates unbalanced channels.",
},
cli.Float64Flag{
Name: "bimodalweight",
Usage: "controls the degree to which the probability " +
"estimator takes into account other channels " +
"of a router",
},
},
Action: actionDecorator(setCfg),
}
func setCfg(ctx *cli.Context) error {
ctxc := getContext()
conn := getClientConn(ctx, false)
defer conn.Close()
client := routerrpc.NewRouterClient(conn)
// Fetch current mission control config which we update to create our
// response.
mcCfg, err := client.GetMissionControlConfig(
ctxc, &routerrpc.GetMissionControlConfigRequest{},
)
if err != nil {
return err
}
// haveValue is a helper variable to determine if a flag has been set or
// the help should be displayed.
var haveValue bool
// Handle general mission control settings.
if ctx.IsSet("pmtnr") {
haveValue = true
mcCfg.Config.MaximumPaymentResults = uint32(ctx.Int("pmtnr"))
}
if ctx.IsSet("failrelax") {
haveValue = true
mcCfg.Config.MinimumFailureRelaxInterval = uint64(ctx.Duration(
"failrelax",
).Seconds())
}
// We switch between estimators and set corresponding configs. If
// estimator is not set, we ignore the values.
if ctx.IsSet("estimator") {
switch ctx.String("estimator") {
case routing.AprioriEstimatorName:
haveValue = true
// If we switch from another estimator, initialize with
// default values.
if mcCfg.Config.Model !=
routerrpc.MissionControlConfig_APRIORI {
dCfg := routing.DefaultAprioriConfig()
aParams := &routerrpc.AprioriParameters{
HalfLifeSeconds: uint64(
dCfg.PenaltyHalfLife.Seconds(),
),
HopProbability: dCfg.
AprioriHopProbability,
Weight: dCfg.AprioriWeight,
CapacityFraction: dCfg.CapacityFraction,
}
// We make sure the correct config is set.
mcCfg.Config.EstimatorConfig =
&routerrpc.MissionControlConfig_Apriori{
Apriori: aParams,
}
}
// We update all values for the apriori estimator.
mcCfg.Config.Model = routerrpc.
MissionControlConfig_APRIORI
aCfg := mcCfg.Config.GetApriori()
if ctx.IsSet("apriorihalflife") {
aCfg.HalfLifeSeconds = uint64(ctx.Duration(
"apriorihalflife",
).Seconds())
}
if ctx.IsSet("apriorihopprob") {
aCfg.HopProbability = ctx.Float64(
"apriorihopprob",
)
}
if ctx.IsSet("aprioriweight") {
aCfg.Weight = ctx.Float64("aprioriweight")
}
if ctx.IsSet("aprioricapacityfraction") {
aCfg.CapacityFraction =
ctx.Float64("aprioricapacityfraction")
}
case routing.BimodalEstimatorName:
haveValue = true
// If we switch from another estimator, initialize with
// default values.
if mcCfg.Config.Model !=
routerrpc.MissionControlConfig_BIMODAL {
dCfg := routing.DefaultBimodalConfig()
bParams := &routerrpc.BimodalParameters{
DecayTime: uint64(
dCfg.BimodalDecayTime.Seconds(),
),
ScaleMsat: uint64(
dCfg.BimodalScaleMsat,
),
NodeWeight: dCfg.BimodalNodeWeight,
}
// We make sure the correct config is set.
mcCfg.Config.EstimatorConfig =
&routerrpc.MissionControlConfig_Bimodal{
Bimodal: bParams,
}
}
// We update all values for the bimodal estimator.
mcCfg.Config.Model = routerrpc.
MissionControlConfig_BIMODAL
bCfg := mcCfg.Config.GetBimodal()
if ctx.IsSet("bimodaldecaytime") {
bCfg.DecayTime = uint64(ctx.Duration(
"bimodaldecaytime",
).Seconds())
}
if ctx.IsSet("bimodalscale") {
bCfg.ScaleMsat = ctx.Uint64("bimodalscale")
}
if ctx.IsSet("bimodalweight") {
bCfg.NodeWeight = ctx.Float64(
"bimodalweight",
)
}
default:
return fmt.Errorf("unknown estimator %v",
ctx.String("estimator"))
}
}
if !haveValue {
return cli.ShowCommandHelp(ctx, "setmccfg")
}
_, err = client.SetMissionControlConfig(
ctxc, &routerrpc.SetMissionControlConfigRequest{
Config: mcCfg.Config,
},
)
return err
}
var queryMissionControlCommand = cli.Command{
Name: "querymc",
Category: "Mission Control",
Usage: "Query the internal mission control state.",
Action: actionDecorator(queryMissionControl),
}
func queryMissionControl(ctx *cli.Context) error {
ctxc := getContext()
conn := getClientConn(ctx, false)
defer conn.Close()
client := routerrpc.NewRouterClient(conn)
req := &routerrpc.QueryMissionControlRequest{}
snapshot, err := client.QueryMissionControl(ctxc, req)
if err != nil {
return err
}
printRespJSON(snapshot)
return nil
}
var queryProbCommand = cli.Command{
Name: "queryprob",
Category: "Mission Control",
Usage: "Deprecated. Estimate a success probability.",
ArgsUsage: "from-node to-node amt",
Action: actionDecorator(queryProb),
Hidden: true,
}
func queryProb(ctx *cli.Context) error {
ctxc := getContext()
args := ctx.Args()
if len(args) != 3 {
return cli.ShowCommandHelp(ctx, "queryprob")
}
fromNode, err := route.NewVertexFromStr(args.Get(0))
if err != nil {
return fmt.Errorf("invalid from node key: %v", err)
}
toNode, err := route.NewVertexFromStr(args.Get(1))
if err != nil {
return fmt.Errorf("invalid to node key: %v", err)
}
amtSat, err := strconv.ParseUint(args.Get(2), 10, 64)
if err != nil {
return fmt.Errorf("invalid amt: %v", err)
}
amtMsat := lnwire.NewMSatFromSatoshis(
btcutil.Amount(amtSat),
)
conn := getClientConn(ctx, false)
defer conn.Close()
client := routerrpc.NewRouterClient(conn)
req := &routerrpc.QueryProbabilityRequest{
FromNode: fromNode[:],
ToNode: toNode[:],
AmtMsat: int64(amtMsat),
}
response, err := client.QueryProbability(ctxc, req)
if err != nil {
return err
}
printRespJSON(response)
return nil
}
var resetMissionControlCommand = cli.Command{
Name: "resetmc",
Category: "Mission Control",
Usage: "Reset internal mission control state.",
Action: actionDecorator(resetMissionControl),
}
func resetMissionControl(ctx *cli.Context) error {
ctxc := getContext()
conn := getClientConn(ctx, false)
defer conn.Close()
client := routerrpc.NewRouterClient(conn)
req := &routerrpc.ResetMissionControlRequest{}
_, err := client.ResetMissionControl(ctxc, req)
return err
}