• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 14844045539

05 May 2025 07:00PM UTC coverage: 69.012% (-0.03%) from 69.037%
14844045539

push

github

web-flow
Merge pull request #9781 from bitromortac/2505-loadmc

lncli: add loadmc command

0 of 101 new or added lines in 2 files covered. (0.0%)

78 existing lines in 18 files now uncovered.

133941 of 194085 relevant lines covered (69.01%)

21967.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/cmd/commands/cmd_import_mission_control.go
1
package commands
2

3
import (
4
        "context"
5
        "errors"
6
        "fmt"
7
        "os"
8
        "strconv"
9
        "time"
10

11
        "github.com/lightningnetwork/lnd/lnrpc"
12
        "github.com/lightningnetwork/lnd/lnrpc/routerrpc"
13
        "github.com/lightningnetwork/lnd/routing/route"
14
        "github.com/urfave/cli"
15
)
16

17
const argsStr = "[source node] [dest node] [unix ts seconds] [amount in msat]"
18

19
var importMissionControlCommand = cli.Command{
20
        Name:      "importmc",
21
        Category:  "Mission Control",
22
        Usage:     "Import a result to the internal mission control state.",
23
        ArgsUsage: fmt.Sprintf("importmc %v", argsStr),
24
        Action:    actionDecorator(importMissionControl),
25
        Flags: []cli.Flag{
26
                cli.BoolFlag{
27
                        Name:  "failure",
28
                        Usage: "whether the routing history entry was a failure",
29
                },
30
                cli.BoolFlag{
31
                        Name:  "force",
32
                        Usage: "whether to force the history entry import",
33
                },
34
        },
35
}
36

37
func importMissionControl(ctx *cli.Context) error {
×
38
        conn := getClientConn(ctx, false)
×
39
        defer conn.Close()
×
40

×
41
        if ctx.NArg() != 4 {
×
42
                return fmt.Errorf("please provide args: %v", argsStr)
×
43
        }
×
44

45
        args := ctx.Args()
×
46

×
47
        sourceNode, err := route.NewVertexFromStr(args[0])
×
48
        if err != nil {
×
49
                return fmt.Errorf("please provide valid source node: %w", err)
×
50
        }
×
51

52
        destNode, err := route.NewVertexFromStr(args[1])
×
53
        if err != nil {
×
54
                return fmt.Errorf("please provide valid dest node: %w", err)
×
55
        }
×
56

57
        ts, err := strconv.ParseInt(args[2], 10, 64)
×
58
        if err != nil {
×
59
                return fmt.Errorf("please provide unix timestamp "+
×
60
                        "in seconds: %v", err)
×
61
        }
×
62

63
        if ts <= 0 {
×
64
                return errors.New("please provide positive timestamp")
×
65
        }
×
66

67
        amt, err := strconv.ParseInt(args[3], 10, 64)
×
68
        if err != nil {
×
69
                return fmt.Errorf("please provide amount in msat: %w", err)
×
70
        }
×
71

72
        // Allow 0 value as failure amount.
73
        if !ctx.IsSet("failure") && amt <= 0 {
×
74
                return errors.New("success amount must be >0")
×
75
        }
×
76

77
        client := routerrpc.NewRouterClient(conn)
×
78

×
79
        importResult := &routerrpc.PairHistory{
×
80
                NodeFrom: sourceNode[:],
×
81
                NodeTo:   destNode[:],
×
82
                History:  &routerrpc.PairData{},
×
83
        }
×
84

×
85
        if ctx.IsSet("failure") {
×
86
                importResult.History.FailAmtMsat = amt
×
87
                importResult.History.FailTime = ts
×
88
        } else {
×
89
                importResult.History.SuccessAmtMsat = amt
×
90
                importResult.History.SuccessTime = ts
×
91
        }
×
92

93
        req := &routerrpc.XImportMissionControlRequest{
×
94
                Pairs: []*routerrpc.PairHistory{
×
95
                        importResult,
×
96
                },
×
97
                Force: ctx.IsSet("force"),
×
98
        }
×
99

×
100
        rpcCtx := context.Background()
×
101
        _, err = client.XImportMissionControl(rpcCtx, req)
×
102
        return err
×
103
}
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.
NEW
144
func loadMissionControl(ctx *cli.Context) error {
×
NEW
145
        rpcCtx := context.Background()
×
NEW
146

×
NEW
147
        mcDataPath := ctx.String("mcdatapath")
×
NEW
148
        if mcDataPath == "" {
×
NEW
149
                return fmt.Errorf("mcdatapath must be set")
×
NEW
150
        }
×
151

NEW
152
        if _, err := os.Stat(mcDataPath); os.IsNotExist(err) {
×
NEW
153
                return fmt.Errorf("%v does not exist", mcDataPath)
×
NEW
154
        }
×
155

NEW
156
        conn := getClientConn(ctx, false)
×
NEW
157
        defer conn.Close()
×
NEW
158

×
NEW
159
        client := routerrpc.NewRouterClient(conn)
×
NEW
160

×
NEW
161
        // Load and unmarshal the querymc output file.
×
NEW
162
        mcRaw, err := os.ReadFile(mcDataPath)
×
NEW
163
        if err != nil {
×
NEW
164
                return fmt.Errorf("could not read querymc output file: %w", err)
×
NEW
165
        }
×
166

NEW
167
        mc := &routerrpc.QueryMissionControlResponse{}
×
NEW
168
        err = lnrpc.ProtoJSONUnmarshalOpts.Unmarshal(mcRaw, mc)
×
NEW
169
        if err != nil {
×
NEW
170
                return fmt.Errorf("could not unmarshal querymc output file: %w",
×
NEW
171
                        err)
×
NEW
172
        }
×
173

174
        // We discard mission control data if requested.
NEW
175
        if ctx.Bool("discard") {
×
NEW
176
                if !promptForConfirmation("This will discard all current " +
×
NEW
177
                        "mission control data in the database (yes/no): ") {
×
NEW
178

×
NEW
179
                        return nil
×
NEW
180
                }
×
181

NEW
182
                _, err = client.ResetMissionControl(
×
NEW
183
                        rpcCtx, &routerrpc.ResetMissionControlRequest{},
×
NEW
184
                )
×
NEW
185
                if err != nil {
×
NEW
186
                        return err
×
NEW
187
                }
×
188
        }
189

190
        // Add a time offset to all timestamps if requested.
NEW
191
        timeOffset := ctx.String("timeoffset")
×
NEW
192
        if timeOffset != "" {
×
NEW
193
                offset, err := time.ParseDuration(timeOffset)
×
NEW
194
                if err != nil {
×
NEW
195
                        return fmt.Errorf("could not parse time offset: %w",
×
NEW
196
                                err)
×
NEW
197
                }
×
198

NEW
199
                var maxTimestamp time.Time
×
NEW
200

×
NEW
201
                for _, pair := range mc.Pairs {
×
NEW
202
                        if pair.History.SuccessTime != 0 {
×
NEW
203
                                unix := time.Unix(pair.History.SuccessTime, 0)
×
NEW
204
                                unix = unix.Add(offset)
×
NEW
205

×
NEW
206
                                if unix.After(maxTimestamp) {
×
NEW
207
                                        maxTimestamp = unix
×
NEW
208
                                }
×
209

NEW
210
                                pair.History.SuccessTime = unix.Unix()
×
211
                        }
212

NEW
213
                        if pair.History.FailTime != 0 {
×
NEW
214
                                unix := time.Unix(pair.History.FailTime, 0)
×
NEW
215
                                unix = unix.Add(offset)
×
NEW
216

×
NEW
217
                                if unix.After(maxTimestamp) {
×
NEW
218
                                        maxTimestamp = unix
×
NEW
219
                                }
×
220

NEW
221
                                pair.History.FailTime = unix.Unix()
×
222
                        }
223
                }
224

NEW
225
                fmt.Printf("Adding time offset %v to all timestamps. "+
×
NEW
226
                        "New max timestamp: %v\n", offset, maxTimestamp)
×
227
        }
228

NEW
229
        sanitizeMCData(mc.Pairs)
×
NEW
230

×
NEW
231
        fmt.Printf("Mission control file contains %v pairs.\n", len(mc.Pairs))
×
NEW
232
        if !promptForConfirmation("Import mission control data (yes/no): ") {
×
NEW
233
                return nil
×
NEW
234
        }
×
235

NEW
236
        _, err = client.XImportMissionControl(
×
NEW
237
                rpcCtx,
×
NEW
238
                &routerrpc.XImportMissionControlRequest{
×
NEW
239
                        Pairs: mc.Pairs, Force: ctx.Bool("force"),
×
NEW
240
                },
×
NEW
241
        )
×
NEW
242
        if err != nil {
×
NEW
243
                return fmt.Errorf("could not import mission control data: %w",
×
NEW
244
                        err)
×
NEW
245
        }
×
246

NEW
247
        return nil
×
248
}
249

250
// sanitizeMCData removes invalid data from the exported mission control data.
NEW
251
func sanitizeMCData(mc []*routerrpc.PairHistory) {
×
NEW
252
        for _, pair := range mc {
×
NEW
253
                // It is not allowed to import a zero-amount success to mission
×
NEW
254
                // control if a timestamp is set. We unset it in this case.
×
NEW
255
                if pair.History.SuccessTime != 0 &&
×
NEW
256
                        pair.History.SuccessAmtMsat == 0 &&
×
NEW
257
                        pair.History.SuccessAmtSat == 0 {
×
NEW
258

×
NEW
259
                        pair.History.SuccessTime = 0
×
NEW
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.
NEW
265
                if pair.History.SuccessTime == 0 &&
×
NEW
266
                        pair.History.FailTime != 0 &&
×
NEW
267
                        pair.History.FailAmtMsat == 0 &&
×
NEW
268
                        pair.History.FailAmtSat == 0 {
×
NEW
269

×
NEW
270
                        pair.History.FailAmtMsat = 1
×
NEW
271
                }
×
272
        }
273
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc