-
Notifications
You must be signed in to change notification settings - Fork 2.2k
lncli: add loadmc command #9781
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -4,8 +4,11 @@ import ( | |||||||||||
"context" | ||||||||||||
"errors" | ||||||||||||
"fmt" | ||||||||||||
"os" | ||||||||||||
"strconv" | ||||||||||||
"time" | ||||||||||||
|
||||||||||||
"github.com/lightningnetwork/lnd/lnrpc" | ||||||||||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc" | ||||||||||||
"github.com/lightningnetwork/lnd/routing/route" | ||||||||||||
"github.com/urfave/cli" | ||||||||||||
|
@@ -15,7 +18,7 @@ const argsStr = "[source node] [dest node] [unix ts seconds] [amount in msat]" | |||||||||||
|
||||||||||||
var importMissionControlCommand = cli.Command{ | ||||||||||||
Name: "importmc", | ||||||||||||
Category: "Payments", | ||||||||||||
Category: "Mission Control", | ||||||||||||
Usage: "Import a result to the internal mission control state.", | ||||||||||||
ArgsUsage: fmt.Sprintf("importmc %v", argsStr), | ||||||||||||
Action: actionDecorator(importMissionControl), | ||||||||||||
|
@@ -98,3 +101,173 @@ func importMissionControl(ctx *cli.Context) error { | |||||||||||
_, err = client.XImportMissionControl(rpcCtx, req) | ||||||||||||
return err | ||||||||||||
} | ||||||||||||
|
||||||||||||
var loadMissionControlCommand = cli.Command{ | ||||||||||||
Name: "loadmc", | ||||||||||||
Category: "Mission Control", | ||||||||||||
Usage: "Load mission control results to the internal mission " + | ||||||||||||
"control state from a file produced by querymc with the " + | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Micro-Nit: use backticks for the |
||||||||||||
"option to shift timestamps. Note that this data is not " + | ||||||||||||
"persisted across restarts.", | ||||||||||||
Action: actionDecorator(loadMissionControl), | ||||||||||||
Flags: []cli.Flag{ | ||||||||||||
cli.StringFlag{ | ||||||||||||
Name: "mcdatapath", | ||||||||||||
Usage: "The path to the querymc output file (json).", | ||||||||||||
}, | ||||||||||||
cli.BoolFlag{ | ||||||||||||
Name: "discard", | ||||||||||||
Usage: "Discards current mission control data.", | ||||||||||||
}, | ||||||||||||
cli.StringFlag{ | ||||||||||||
Name: "timeoffset", | ||||||||||||
Usage: "Time offset to add to all timestamps. " + | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we explain what this is useful for? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, added an explanation. |
||||||||||||
"Format: 1m for a minute, 1h for an hour, 1d " + | ||||||||||||
"for one day. This can be used to let " + | ||||||||||||
"mission control data appear to be more " + | ||||||||||||
"recent, to trick pathfinding's in-built " + | ||||||||||||
"information decay mechanism. Additionally " + | ||||||||||||
"by setting 0m, this will report the most " + | ||||||||||||
"recent result timestamp, which can be used " + | ||||||||||||
"to find out how old this data is.", | ||||||||||||
Comment on lines
+129
to
+132
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Q: What do you mean by report here? The import mc data rpc does not return any information or ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, I was referring to the stdout text with the new max timestamp |
||||||||||||
}, | ||||||||||||
cli.BoolFlag{ | ||||||||||||
Name: "force", | ||||||||||||
Usage: "Whether to force overiding more recent " + | ||||||||||||
"results in the database with older results " + | ||||||||||||
"from the file.", | ||||||||||||
}, | ||||||||||||
}, | ||||||||||||
} | ||||||||||||
|
||||||||||||
// loadMissionControl loads mission control data into an LND instance. | ||||||||||||
func loadMissionControl(ctx *cli.Context) error { | ||||||||||||
rpcCtx := context.Background() | ||||||||||||
|
||||||||||||
mcDataPath := ctx.String("mcdatapath") | ||||||||||||
if mcDataPath == "" { | ||||||||||||
return fmt.Errorf("mcdatapath must be set") | ||||||||||||
} | ||||||||||||
|
||||||||||||
if _, err := os.Stat(mcDataPath); os.IsNotExist(err) { | ||||||||||||
return fmt.Errorf("%v does not exist", mcDataPath) | ||||||||||||
} | ||||||||||||
|
||||||||||||
conn := getClientConn(ctx, false) | ||||||||||||
defer conn.Close() | ||||||||||||
|
||||||||||||
client := routerrpc.NewRouterClient(conn) | ||||||||||||
|
||||||||||||
// Load and unmarshal the querymc output file. | ||||||||||||
mcRaw, err := os.ReadFile(mcDataPath) | ||||||||||||
if err != nil { | ||||||||||||
return fmt.Errorf("could not read querymc output file: %w", err) | ||||||||||||
} | ||||||||||||
|
||||||||||||
mc := &routerrpc.QueryMissionControlResponse{} | ||||||||||||
err = lnrpc.ProtoJSONUnmarshalOpts.Unmarshal(mcRaw, mc) | ||||||||||||
if err != nil { | ||||||||||||
return fmt.Errorf("could not unmarshal querymc output file: %w", | ||||||||||||
err) | ||||||||||||
} | ||||||||||||
|
||||||||||||
// We discard mission control data if requested. | ||||||||||||
if ctx.Bool("discard") { | ||||||||||||
if !promptForConfirmation("This will discard all current " + | ||||||||||||
"mission control data in the database (yes/no): ") { | ||||||||||||
|
||||||||||||
return nil | ||||||||||||
} | ||||||||||||
|
||||||||||||
_, err = client.ResetMissionControl( | ||||||||||||
rpcCtx, &routerrpc.ResetMissionControlRequest{}, | ||||||||||||
) | ||||||||||||
if err != nil { | ||||||||||||
return err | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
// Add a time offset to all timestamps if requested. | ||||||||||||
timeOffset := ctx.String("timeoffset") | ||||||||||||
if timeOffset != "" { | ||||||||||||
offset, err := time.ParseDuration(timeOffset) | ||||||||||||
if err != nil { | ||||||||||||
return fmt.Errorf("could not parse time offset: %w", | ||||||||||||
err) | ||||||||||||
} | ||||||||||||
|
||||||||||||
var maxTimestamp time.Time | ||||||||||||
|
||||||||||||
for _, pair := range mc.Pairs { | ||||||||||||
if pair.History.SuccessTime != 0 { | ||||||||||||
unix := time.Unix(pair.History.SuccessTime, 0) | ||||||||||||
unix = unix.Add(offset) | ||||||||||||
|
||||||||||||
if unix.After(maxTimestamp) { | ||||||||||||
maxTimestamp = unix | ||||||||||||
} | ||||||||||||
|
||||||||||||
pair.History.SuccessTime = unix.Unix() | ||||||||||||
} | ||||||||||||
|
||||||||||||
if pair.History.FailTime != 0 { | ||||||||||||
unix := time.Unix(pair.History.FailTime, 0) | ||||||||||||
unix = unix.Add(offset) | ||||||||||||
|
||||||||||||
if unix.After(maxTimestamp) { | ||||||||||||
maxTimestamp = unix | ||||||||||||
} | ||||||||||||
|
||||||||||||
pair.History.FailTime = unix.Unix() | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
fmt.Printf("Adding time offset %v to all timestamps. "+ | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Adding => Added ? |
||||||||||||
"New max timestamp: %v\n", offset, maxTimestamp) | ||||||||||||
} | ||||||||||||
|
||||||||||||
sanitizeMCData(mc.Pairs) | ||||||||||||
|
||||||||||||
fmt.Printf("Mission control file contains %v pairs.\n", len(mc.Pairs)) | ||||||||||||
if !promptForConfirmation("Import mission control data (yes/no): ") { | ||||||||||||
return nil | ||||||||||||
} | ||||||||||||
|
||||||||||||
_, err = client.XImportMissionControl( | ||||||||||||
rpcCtx, | ||||||||||||
&routerrpc.XImportMissionControlRequest{ | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's also the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, good to have API feature completeness |
||||||||||||
Pairs: mc.Pairs, Force: ctx.Bool("force"), | ||||||||||||
}, | ||||||||||||
) | ||||||||||||
if err != nil { | ||||||||||||
return fmt.Errorf("could not import mission control data: %w", | ||||||||||||
err) | ||||||||||||
} | ||||||||||||
|
||||||||||||
return nil | ||||||||||||
} | ||||||||||||
|
||||||||||||
// sanitizeMCData removes invalid data from the exported mission control data. | ||||||||||||
func sanitizeMCData(mc []*routerrpc.PairHistory) { | ||||||||||||
for _, pair := range mc { | ||||||||||||
// It is not allowed to import a zero-amount success to mission | ||||||||||||
// control if a timestamp is set. We unset it in this case. | ||||||||||||
if pair.History.SuccessTime != 0 && | ||||||||||||
pair.History.SuccessAmtMsat == 0 && | ||||||||||||
pair.History.SuccessAmtSat == 0 { | ||||||||||||
|
||||||||||||
pair.History.SuccessTime = 0 | ||||||||||||
} | ||||||||||||
Comment on lines
+253
to
+260
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When testing I've seen entries like that
which is inconsistent with what we allow to import, which is why I've added those clean-ups. See here: lnd/lnrpc/routerrpc/router_server.go Lines 1282 to 1286 in 334a7d1
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Definitely something we need to think about and potentially removing this validation check on the server side. |
||||||||||||
|
||||||||||||
// If we only deal with a failure, we need to set the failure | ||||||||||||
// amount to a tiny value due to a limitation in the RPC. This | ||||||||||||
// will lead to a similar penalization in pathfinding. | ||||||||||||
if pair.History.SuccessTime == 0 && | ||||||||||||
pair.History.FailTime != 0 && | ||||||||||||
pair.History.FailAmtMsat == 0 && | ||||||||||||
pair.History.FailAmtSat == 0 { | ||||||||||||
|
||||||||||||
pair.History.FailAmtMsat = 1 | ||||||||||||
} | ||||||||||||
Comment on lines
+262
to
+271
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an other check we would violate with the above input. lnd/lnrpc/routerrpc/router_server.go Lines 1240 to 1243 in 334a7d1
|
||||||||||||
} | ||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍