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

lightningnetwork / lnd / 14858949564

06 May 2025 11:52AM UTC coverage: 69.018% (+0.006%) from 69.012%
14858949564

push

github

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

lncli: establish connection after parsing of mc data for loadmc

0 of 9 new or added lines in 1 file covered. (0.0%)

89 existing lines in 27 files now uncovered.

133948 of 194077 relevant lines covered (69.02%)

22052.1 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.StringFlag{
119
                        Name: "timeoffset",
120
                        Usage: "Time offset to add to all timestamps. " +
121
                                "Follows a format like 72h3m0.5s. " +
122
                                "This can be used to make mission control " +
123
                                "data appear more recent, to trick " +
124
                                "pathfinding's in-built information decay " +
125
                                "mechanism. Additionally, " +
126
                                "by setting 0s, this will report the most " +
127
                                "recent result timestamp, which can be used " +
128
                                "to find out how old this data is.",
129
                },
130
                cli.BoolFlag{
131
                        Name: "force",
132
                        Usage: "Whether to force overiding more recent " +
133
                                "results in the database with older results " +
134
                                "from the file.",
135
                },
136
                cli.BoolFlag{
137
                        Name: "skip_confirmation",
138
                        Usage: "Skip the confirmation prompt and import " +
139
                                "immediately",
140
                },
141
        },
142
}
143

144
// loadMissionControl loads mission control data into an LND instance.
145
func loadMissionControl(ctx *cli.Context) error {
×
146
        rpcCtx := context.Background()
×
147

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

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

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

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

NEW
170
        conn := getClientConn(ctx, false)
×
NEW
171
        defer conn.Close()
×
172

×
NEW
173
        client := routerrpc.NewRouterClient(conn)
×
174

×
UNCOV
175
        // Add a time offset to all timestamps if requested.
×
176
        timeOffset := ctx.String("timeoffset")
×
177
        if timeOffset != "" {
×
178
                offset, err := time.ParseDuration(timeOffset)
×
179
                if err != nil {
×
180
                        return fmt.Errorf("could not parse time offset: %w",
×
181
                                err)
×
182
                }
×
183

184
                var maxTimestamp time.Time
×
185

×
186
                for _, pair := range mc.Pairs {
×
187
                        if pair.History.SuccessTime != 0 {
×
188
                                unix := time.Unix(pair.History.SuccessTime, 0)
×
189
                                unix = unix.Add(offset)
×
190

×
191
                                if unix.After(maxTimestamp) {
×
192
                                        maxTimestamp = unix
×
193
                                }
×
194

195
                                pair.History.SuccessTime = unix.Unix()
×
196
                        }
197

198
                        if pair.History.FailTime != 0 {
×
199
                                unix := time.Unix(pair.History.FailTime, 0)
×
200
                                unix = unix.Add(offset)
×
201

×
202
                                if unix.After(maxTimestamp) {
×
203
                                        maxTimestamp = unix
×
204
                                }
×
205

206
                                pair.History.FailTime = unix.Unix()
×
207
                        }
208
                }
209

NEW
210
                fmt.Printf("Added a time offset %v to all timestamps. "+
×
211
                        "New max timestamp: %v\n", offset, maxTimestamp)
×
212
        }
213

214
        sanitizeMCData(mc.Pairs)
×
215

×
216
        fmt.Printf("Mission control file contains %v pairs.\n", len(mc.Pairs))
×
NEW
217
        if !ctx.Bool("skip_confirmation") &&
×
NEW
218
                !promptForConfirmation(
×
NEW
219
                        "Import mission control data (yes/no): ",
×
NEW
220
                ) {
×
NEW
221

×
222
                return nil
×
223
        }
×
224

225
        _, err = client.XImportMissionControl(
×
226
                rpcCtx,
×
227
                &routerrpc.XImportMissionControlRequest{
×
228
                        Pairs: mc.Pairs, Force: ctx.Bool("force"),
×
229
                },
×
230
        )
×
231
        if err != nil {
×
232
                return fmt.Errorf("could not import mission control data: %w",
×
233
                        err)
×
234
        }
×
235

236
        return nil
×
237
}
238

239
// sanitizeMCData removes invalid data from the exported mission control data.
240
func sanitizeMCData(mc []*routerrpc.PairHistory) {
×
241
        for _, pair := range mc {
×
242
                // It is not allowed to import a zero-amount success to mission
×
243
                // control if a timestamp is set. We unset it in this case.
×
244
                if pair.History.SuccessTime != 0 &&
×
245
                        pair.History.SuccessAmtMsat == 0 &&
×
246
                        pair.History.SuccessAmtSat == 0 {
×
247

×
248
                        pair.History.SuccessTime = 0
×
249
                }
×
250

251
                // If we only deal with a failure, we need to set the failure
252
                // amount to a tiny value due to a limitation in the RPC. This
253
                // will lead to a similar penalization in pathfinding.
254
                if pair.History.SuccessTime == 0 &&
×
255
                        pair.History.FailTime != 0 &&
×
256
                        pair.History.FailAmtMsat == 0 &&
×
257
                        pair.History.FailAmtSat == 0 {
×
258

×
259
                        pair.History.FailAmtMsat = 1
×
260
                }
×
261
        }
262
}
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