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

lightningnetwork / lnd / 11219354629

07 Oct 2024 03:56PM UTC coverage: 58.585% (-0.2%) from 58.814%
11219354629

Pull #9147

github

ziggie1984
fixup! sqlc: migration up script for payments.
Pull Request #9147: [Part 1|3] Introduce SQL Payment schema into LND

130227 of 222287 relevant lines covered (58.59%)

29106.19 hits per line

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

95.98
/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 {
36✔
32

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

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

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

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

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

154✔
59
        nodePairs, ok := m.lastPairResult[fromNode]
154✔
60
        if !ok {
154✔
61
                nodePairs = make(NodeResults)
228✔
62
                m.lastPairResult[fromNode] = nodePairs
74✔
63
        }
74✔
64

74✔
65
        current := nodePairs[toNode]
66

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

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

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

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

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

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

1✔
113
                        return
1✔
114
                }
1✔
115

1✔
116
                current.FailTime = timestamp
117
                current.FailAmt = failAmt
70✔
118

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

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

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

153✔
137
        nodePairs[toNode] = current
153✔
138
}
153✔
139

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

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

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

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

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

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

8✔
175
                return true
8✔
176
        }
8✔
177

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

5✔
184
        return false
5✔
185
}
5✔
186

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

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

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

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

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

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

1✔
212
        return &snapshot
1✔
213
}
1✔
214

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

2✔
221
        var imported int
2✔
222

2✔
223
        for _, pair := range snapshot.Pairs {
2✔
224
                fromNode := pair.Pair.From
4✔
225
                toNode := pair.Pair.To
2✔
226

2✔
227
                results, found := m.getLastPairResult(fromNode)
2✔
228
                if !found {
2✔
229
                        results = make(map[route.Vertex]TimedPairResult)
4✔
230
                }
2✔
231

2✔
232
                lastResult := results[toNode]
233

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

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

247
        return imported
248
}
2✔
249

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

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

×
260
                return 0
×
261
        }
×
262

×
263
        m.setLastPairResult(
264
                fromNode, toNode, importedTs, &importedResult, force,
2✔
265
        )
2✔
266

2✔
267
        return 1
2✔
268
}
2✔
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