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

lightningnetwork / lnd / 13236757158

10 Feb 2025 08:39AM UTC coverage: 57.649% (-1.2%) from 58.815%
13236757158

Pull #9493

github

ziggie1984
lncli: for some cmds we don't replace the data of the response.

For some cmds it is not very practical to replace the json output
because we might pipe it into other commands. For example when
creating the route we want to pipe it into sendtoRoute.
Pull Request #9493: For some lncli cmds we should not replace the content with other data

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

19535 existing lines in 252 files now uncovered.

103517 of 179563 relevant lines covered (57.65%)

24878.49 hits per line

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

76.44
/routing/missioncontrol_state.go
1
package routing
2

3
import (
4
        "time"
5

6
        "github.com/lightningnetwork/lnd/routing/route"
7
)
8

9
// missionControlState is an object that manages the internal mission control
10
// state. Note that it isn't thread safe and synchronization needs to be
11
// enforced externally.
12
type missionControlState struct {
13
        // lastPairResult tracks the last payment result (on a pair basis) for
14
        // each transited node. This is a multi-layer map that allows us to look
15
        // up the failure history of all connected channels (node pairs) for a
16
        // particular node.
17
        lastPairResult map[route.Vertex]NodeResults
18

19
        // lastSecondChance tracks the last time a second chance was granted for
20
        // a directed node pair.
21
        lastSecondChance map[DirectedNodePair]time.Time
22

23
        // minFailureRelaxInterval is the minimum time that must have passed
24
        // since the previously recorded failure before the failure amount may
25
        // be raised.
26
        minFailureRelaxInterval time.Duration
27
}
28

29
// newMissionControlState instantiates a new mission control state object.
30
func newMissionControlState(
31
        minFailureRelaxInterval time.Duration) *missionControlState {
34✔
32

34✔
33
        return &missionControlState{
34✔
34
                lastPairResult:          make(map[route.Vertex]NodeResults),
34✔
35
                lastSecondChance:        make(map[DirectedNodePair]time.Time),
34✔
36
                minFailureRelaxInterval: minFailureRelaxInterval,
34✔
37
        }
34✔
38
}
34✔
39

40
// getLastPairResult returns the current state for connections to the given
41
// node.
42
func (m *missionControlState) getLastPairResult(node route.Vertex) (NodeResults,
43
        bool) {
668✔
44

668✔
45
        result, ok := m.lastPairResult[node]
668✔
46
        return result, ok
668✔
47
}
668✔
48

49
// ResetHistory resets the history of missionControlState returning it to a
50
// state as if no payment attempts have been made.
51
func (m *missionControlState) resetHistory() {
3✔
52
        m.lastPairResult = make(map[route.Vertex]NodeResults)
3✔
53
        m.lastSecondChance = make(map[DirectedNodePair]time.Time)
3✔
54
}
3✔
55

56
// setLastPairResult stores a result for a node pair.
57
func (m *missionControlState) setLastPairResult(fromNode, toNode route.Vertex,
58
        timestamp time.Time, result *pairResult, force bool) {
152✔
59

152✔
60
        nodePairs, ok := m.lastPairResult[fromNode]
152✔
61
        if !ok {
224✔
62
                nodePairs = make(NodeResults)
72✔
63
                m.lastPairResult[fromNode] = nodePairs
72✔
64
        }
72✔
65

66
        current := nodePairs[toNode]
152✔
67

152✔
68
        // Apply the new result to the existing data for this pair. If there is
152✔
69
        // no existing data, apply it to the default values for TimedPairResult.
152✔
70
        if result.success {
235✔
71
                successAmt := result.amt
83✔
72
                current.SuccessTime = timestamp
83✔
73

83✔
74
                // Only update the success amount if this amount is higher. This
83✔
75
                // prevents the success range from shrinking when there is no
83✔
76
                // reason to do so. For example: small amount probes shouldn't
83✔
77
                // affect a previous success for a much larger amount.
83✔
78
                if force || successAmt > current.SuccessAmt {
128✔
79
                        current.SuccessAmt = successAmt
45✔
80
                }
45✔
81

82
                // If the success amount goes into the failure range, move the
83
                // failure range up. Future attempts up to the success amount
84
                // are likely to succeed. We don't want to clear the failure
85
                // completely, because we haven't learnt much for amounts above
86
                // the current success amount.
87
                if force || (!current.FailTime.IsZero() &&
83✔
88
                        successAmt >= current.FailAmt) {
87✔
89

4✔
90
                        current.FailAmt = successAmt + 1
4✔
91
                }
4✔
92
        } else {
69✔
93
                // For failures we always want to update both the amount and the
69✔
94
                // time. Those need to relate to the same result, because the
69✔
95
                // time is used to gradually diminish the penalty for that
69✔
96
                // specific result. Updating the timestamp but not the amount
69✔
97
                // could cause a failure for a lower amount (a more severe
69✔
98
                // condition) to be revived as if it just happened.
69✔
99
                failAmt := result.amt
69✔
100

69✔
101
                // Drop result if it would increase the failure amount too soon
69✔
102
                // after a previous failure. This can happen if htlc results
69✔
103
                // come in out of order. This check makes it easier for payment
69✔
104
                // processes to converge to a final state.
69✔
105
                failInterval := timestamp.Sub(current.FailTime)
69✔
106
                if !force && failAmt > current.FailAmt &&
69✔
107
                        failInterval < m.minFailureRelaxInterval {
70✔
108

1✔
109
                        log.Debugf("Ignoring higher amount failure within min "+
1✔
110
                                "failure relaxation interval: prev_fail_amt=%v, "+
1✔
111
                                "fail_amt=%v, interval=%v",
1✔
112
                                current.FailAmt, failAmt, failInterval)
1✔
113

1✔
114
                        return
1✔
115
                }
1✔
116

117
                current.FailTime = timestamp
68✔
118
                current.FailAmt = failAmt
68✔
119

68✔
120
                switch {
68✔
121
                // The failure amount is set to zero when the failure is
122
                // amount-independent, meaning that the attempt would have
123
                // failed regardless of the amount. This should also reset the
124
                // success amount to zero.
125
                case failAmt == 0:
34✔
126
                        current.SuccessAmt = 0
34✔
127

128
                // If the failure range goes into the success range, move the
129
                // success range down.
130
                case failAmt <= current.SuccessAmt:
5✔
131
                        current.SuccessAmt = failAmt - 1
5✔
132
                }
133
        }
134

135
        log.Debugf("Setting %v->%v range to [%v-%v]",
151✔
136
                fromNode, toNode, current.SuccessAmt, current.FailAmt)
151✔
137

151✔
138
        nodePairs[toNode] = current
151✔
139
}
140

141
// setAllFail stores a fail result for all known connections to and from the
142
// given node.
143
func (m *missionControlState) setAllFail(node route.Vertex,
144
        timestamp time.Time) {
6✔
145

6✔
146
        for fromNode, nodePairs := range m.lastPairResult {
12✔
147
                for toNode := range nodePairs {
14✔
148
                        if fromNode == node || toNode == node {
12✔
149
                                nodePairs[toNode] = TimedPairResult{
4✔
150
                                        FailTime: timestamp,
4✔
151
                                }
4✔
152
                        }
4✔
153
                }
154
        }
155
}
156

157
// requestSecondChance checks whether the node fromNode can have a second chance
158
// at providing a channel update for its channel with toNode.
159
func (m *missionControlState) requestSecondChance(timestamp time.Time,
160
        fromNode, toNode route.Vertex) bool {
9✔
161

9✔
162
        // Look up previous second chance time.
9✔
163
        pair := DirectedNodePair{
9✔
164
                From: fromNode,
9✔
165
                To:   toNode,
9✔
166
        }
9✔
167
        lastSecondChance, ok := m.lastSecondChance[pair]
9✔
168

9✔
169
        // If the channel hasn't already be given a second chance or its last
9✔
170
        // second chance was long ago, we give it another chance.
9✔
171
        if !ok || timestamp.Sub(lastSecondChance) > minSecondChanceInterval {
15✔
172
                m.lastSecondChance[pair] = timestamp
6✔
173

6✔
174
                log.Debugf("Second chance granted for %v->%v", fromNode, toNode)
6✔
175

6✔
176
                return true
6✔
177
        }
6✔
178

179
        // Otherwise penalize the channel, because we don't allow channel
180
        // updates that are that frequent. This is to prevent nodes from keeping
181
        // us busy by continuously sending new channel updates.
182
        log.Debugf("Second chance denied for %v->%v, remaining interval: %v",
3✔
183
                fromNode, toNode, timestamp.Sub(lastSecondChance))
3✔
184

3✔
185
        return false
3✔
186
}
187

188
// GetHistorySnapshot takes a snapshot from the current mission control state
189
// and actual probability estimates.
190
func (m *missionControlState) getSnapshot() *MissionControlSnapshot {
1✔
191
        log.Debugf("Requesting history snapshot from mission control: "+
1✔
192
                "pair_result_count=%v", len(m.lastPairResult))
1✔
193

1✔
194
        pairs := make([]MissionControlPairSnapshot, 0, len(m.lastPairResult))
1✔
195

1✔
196
        for fromNode, fromPairs := range m.lastPairResult {
4✔
197
                for toNode, result := range fromPairs {
7✔
198
                        pair := NewDirectedNodePair(fromNode, toNode)
4✔
199

4✔
200
                        pairSnapshot := MissionControlPairSnapshot{
4✔
201
                                Pair:            pair,
4✔
202
                                TimedPairResult: result,
4✔
203
                        }
4✔
204

4✔
205
                        pairs = append(pairs, pairSnapshot)
4✔
206
                }
4✔
207
        }
208

209
        snapshot := MissionControlSnapshot{
1✔
210
                Pairs: pairs,
1✔
211
        }
1✔
212

1✔
213
        return &snapshot
1✔
214
}
215

216
// importSnapshot takes an existing snapshot and merges it with our current
217
// state if the result provided are fresher than our current results. It returns
218
// the number of pairs that were used.
219
func (m *missionControlState) importSnapshot(snapshot *MissionControlSnapshot,
UNCOV
220
        force bool) int {
×
UNCOV
221

×
UNCOV
222
        var imported int
×
UNCOV
223

×
UNCOV
224
        for _, pair := range snapshot.Pairs {
×
UNCOV
225
                fromNode := pair.Pair.From
×
UNCOV
226
                toNode := pair.Pair.To
×
UNCOV
227

×
UNCOV
228
                results, found := m.getLastPairResult(fromNode)
×
UNCOV
229
                if !found {
×
UNCOV
230
                        results = make(map[route.Vertex]TimedPairResult)
×
UNCOV
231
                }
×
232

UNCOV
233
                lastResult := results[toNode]
×
UNCOV
234

×
UNCOV
235
                failResult := failPairResult(pair.FailAmt)
×
UNCOV
236
                imported += m.importResult(
×
UNCOV
237
                        lastResult.FailTime, pair.FailTime, failResult,
×
UNCOV
238
                        fromNode, toNode, force,
×
UNCOV
239
                )
×
UNCOV
240

×
UNCOV
241
                successResult := successPairResult(pair.SuccessAmt)
×
UNCOV
242
                imported += m.importResult(
×
UNCOV
243
                        lastResult.SuccessTime, pair.SuccessTime, successResult,
×
UNCOV
244
                        fromNode, toNode, force,
×
UNCOV
245
                )
×
246
        }
247

UNCOV
248
        return imported
×
249
}
250

251
func (m *missionControlState) importResult(currentTs, importedTs time.Time,
252
        importedResult pairResult, fromNode, toNode route.Vertex,
UNCOV
253
        force bool) int {
×
UNCOV
254

×
UNCOV
255
        if !force && currentTs.After(importedTs) {
×
256
                log.Debugf("Not setting pair result for %v->%v (%v) "+
×
257
                        "success=%v, timestamp %v older than last result %v",
×
258
                        fromNode, toNode, importedResult.amt,
×
259
                        importedResult.success, importedTs, currentTs)
×
260

×
261
                return 0
×
262
        }
×
263

UNCOV
264
        m.setLastPairResult(
×
UNCOV
265
                fromNode, toNode, importedTs, &importedResult, force,
×
UNCOV
266
        )
×
UNCOV
267

×
UNCOV
268
        return 1
×
269
}
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