Skip to content

Commit 8351018

Browse files
committed
lncli: add loadmc command
This command lets one import data exported by `lncli querymc > mc.json` via `lncli loadmc --mcdatapath mc.json`.
1 parent 180f61b commit 8351018

File tree

2 files changed

+174
-0
lines changed

2 files changed

+174
-0
lines changed

cmd/commands/cmd_import_mission_control.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"os"
78
"strconv"
9+
"time"
810

11+
"github.com/lightningnetwork/lnd/lnrpc"
912
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
1013
"github.com/lightningnetwork/lnd/routing/route"
1114
"github.com/urfave/cli"
@@ -98,3 +101,173 @@ func importMissionControl(ctx *cli.Context) error {
98101
_, err = client.XImportMissionControl(rpcCtx, req)
99102
return err
100103
}
104+
105+
var loadMissionControlCommand = cli.Command{
106+
Name: "loadmc",
107+
Category: "Mission Control",
108+
Usage: "Load mission control results to the internal mission " +
109+
"control state from a file produced by querymc with the " +
110+
"option to shift timestamps. Note that this data is not " +
111+
"persisted across restarts.",
112+
Action: actionDecorator(loadMissionControl),
113+
Flags: []cli.Flag{
114+
cli.StringFlag{
115+
Name: "mcdatapath",
116+
Usage: "The path to the querymc output file (json).",
117+
},
118+
cli.BoolFlag{
119+
Name: "discard",
120+
Usage: "Discards current mission control data.",
121+
},
122+
cli.StringFlag{
123+
Name: "timeoffset",
124+
Usage: "Time offset to add to all timestamps. " +
125+
"Format: 1m for a minute, 1h for an hour, 1d " +
126+
"for one day. This can be used to let " +
127+
"mission control data appear to be more " +
128+
"recent, to trick pathfinding's in-built " +
129+
"information decay mechanism. Additionally " +
130+
"by setting 0m, this will report the most " +
131+
"recent result timestamp, which can be used " +
132+
"to find out how old this data is.",
133+
},
134+
cli.BoolFlag{
135+
Name: "force",
136+
Usage: "Whether to force overiding more recent " +
137+
"results in the database with older results " +
138+
"from the file.",
139+
},
140+
},
141+
}
142+
143+
// loadMissionControl loads mission control data into an LND instance.
144+
func loadMissionControl(ctx *cli.Context) error {
145+
rpcCtx := context.Background()
146+
147+
mcDataPath := ctx.String("mcdatapath")
148+
if mcDataPath == "" {
149+
return fmt.Errorf("mcdatapath must be set")
150+
}
151+
152+
if _, err := os.Stat(mcDataPath); os.IsNotExist(err) {
153+
return fmt.Errorf("%v does not exist", mcDataPath)
154+
}
155+
156+
conn := getClientConn(ctx, false)
157+
defer conn.Close()
158+
159+
client := routerrpc.NewRouterClient(conn)
160+
161+
// Load and unmarshal the querymc output file.
162+
mcRaw, err := os.ReadFile(mcDataPath)
163+
if err != nil {
164+
return fmt.Errorf("could not read querymc output file: %w", err)
165+
}
166+
167+
mc := &routerrpc.QueryMissionControlResponse{}
168+
err = lnrpc.ProtoJSONUnmarshalOpts.Unmarshal(mcRaw, mc)
169+
if err != nil {
170+
return fmt.Errorf("could not unmarshal querymc output file: %w",
171+
err)
172+
}
173+
174+
// We discard mission control data if requested.
175+
if ctx.Bool("discard") {
176+
if !promptForConfirmation("This will discard all current " +
177+
"mission control data in the database (yes/no): ") {
178+
179+
return nil
180+
}
181+
182+
_, err = client.ResetMissionControl(
183+
rpcCtx, &routerrpc.ResetMissionControlRequest{},
184+
)
185+
if err != nil {
186+
return err
187+
}
188+
}
189+
190+
// Add a time offset to all timestamps if requested.
191+
timeOffset := ctx.String("timeoffset")
192+
if timeOffset != "" {
193+
offset, err := time.ParseDuration(timeOffset)
194+
if err != nil {
195+
return fmt.Errorf("could not parse time offset: %w",
196+
err)
197+
}
198+
199+
var maxTimestamp time.Time
200+
201+
for _, pair := range mc.Pairs {
202+
if pair.History.SuccessTime != 0 {
203+
unix := time.Unix(pair.History.SuccessTime, 0)
204+
unix = unix.Add(offset)
205+
206+
if unix.After(maxTimestamp) {
207+
maxTimestamp = unix
208+
}
209+
210+
pair.History.SuccessTime = unix.Unix()
211+
}
212+
213+
if pair.History.FailTime != 0 {
214+
unix := time.Unix(pair.History.FailTime, 0)
215+
unix = unix.Add(offset)
216+
217+
if unix.After(maxTimestamp) {
218+
maxTimestamp = unix
219+
}
220+
221+
pair.History.FailTime = unix.Unix()
222+
}
223+
}
224+
225+
fmt.Printf("Adding time offset %v to all timestamps. "+
226+
"New max timestamp: %v\n", offset, maxTimestamp)
227+
}
228+
229+
sanitizeMCData(mc.Pairs)
230+
231+
fmt.Printf("Mission control file contains %v pairs.\n", len(mc.Pairs))
232+
if !promptForConfirmation("Import mission control data (yes/no): ") {
233+
return nil
234+
}
235+
236+
_, err = client.XImportMissionControl(
237+
rpcCtx,
238+
&routerrpc.XImportMissionControlRequest{
239+
Pairs: mc.Pairs, Force: ctx.Bool("force"),
240+
},
241+
)
242+
if err != nil {
243+
return fmt.Errorf("could not import mission control data: %w",
244+
err)
245+
}
246+
247+
return nil
248+
}
249+
250+
// sanitizeMCData removes invalid data from the exported mission control data.
251+
func sanitizeMCData(mc []*routerrpc.PairHistory) {
252+
for _, pair := range mc {
253+
// It is not allowed to import a zero-amount success to mission
254+
// control if a timestamp is set. We unset it in this case.
255+
if pair.History.SuccessTime != 0 &&
256+
pair.History.SuccessAmtMsat == 0 &&
257+
pair.History.SuccessAmtSat == 0 {
258+
259+
pair.History.SuccessTime = 0
260+
}
261+
262+
// If we only deal with a failure, we need to set the failure
263+
// amount to a tiny value due to a limitation in the RPC. This
264+
// will lead to a similar penalization in pathfinding.
265+
if pair.History.SuccessTime == 0 &&
266+
pair.History.FailTime != 0 &&
267+
pair.History.FailAmtMsat == 0 &&
268+
pair.History.FailAmtSat == 0 {
269+
270+
pair.History.FailAmtMsat = 1
271+
}
272+
}
273+
}

cmd/commands/routerrpc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ func routerCommands() []cli.Command {
77
return []cli.Command{
88
queryMissionControlCommand,
99
importMissionControlCommand,
10+
loadMissionControlCommand,
1011
queryProbCommand,
1112
resetMissionControlCommand,
1213
buildRouteCommand,

0 commit comments

Comments
 (0)