github.com/decred/dcrlnd@v0.7.6/routing/missioncontrol_state.go (about) 1 package routing 2 3 import ( 4 "time" 5 6 "github.com/decred/dcrlnd/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 { 32 33 return &missionControlState{ 34 lastPairResult: make(map[route.Vertex]NodeResults), 35 lastSecondChance: make(map[DirectedNodePair]time.Time), 36 minFailureRelaxInterval: minFailureRelaxInterval, 37 } 38 } 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 44 result, ok := m.lastPairResult[node] 45 return result, ok 46 } 47 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) 52 m.lastSecondChance = make(map[DirectedNodePair]time.Time) 53 } 54 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 59 nodePairs, ok := m.lastPairResult[fromNode] 60 if !ok { 61 nodePairs = make(NodeResults) 62 m.lastPairResult[fromNode] = nodePairs 63 } 64 65 current := nodePairs[toNode] 66 67 // Apply the new result to the existing data for this pair. If there is 68 // no existing data, apply it to the default values for TimedPairResult. 69 if result.success { 70 successAmt := result.amt 71 current.SuccessTime = timestamp 72 73 // Only update the success amount if this amount is higher. This 74 // prevents the success range from shrinking when there is no 75 // reason to do so. For example: small amount probes shouldn't 76 // affect a previous success for a much larger amount. 77 if force || successAmt > current.SuccessAmt { 78 current.SuccessAmt = successAmt 79 } 80 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) { 88 89 current.FailAmt = successAmt + 1 90 } 91 } else { 92 // For failures we always want to update both the amount and the 93 // time. Those need to relate to the same result, because the 94 // time is used to gradually diminish the penality for that 95 // specific result. Updating the timestamp but not the amount 96 // could cause a failure for a lower amount (a more severe 97 // condition) to be revived as if it just happened. 98 failAmt := result.amt 99 100 // Drop result if it would increase the failure amount too soon 101 // after a previous failure. This can happen if htlc results 102 // come in out of order. This check makes it easier for payment 103 // processes to converge to a final state. 104 failInterval := timestamp.Sub(current.FailTime) 105 if !force && failAmt > current.FailAmt && 106 failInterval < m.minFailureRelaxInterval { 107 108 log.Debugf("Ignoring higher amount failure within min "+ 109 "failure relaxation interval: prev_fail_amt=%v, "+ 110 "fail_amt=%v, interval=%v", 111 current.FailAmt, failAmt, failInterval) 112 113 return 114 } 115 116 current.FailTime = timestamp 117 current.FailAmt = failAmt 118 119 switch { 120 // The failure amount is set to zero when the failure is 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 126 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 131 } 132 } 133 134 log.Debugf("Setting %v->%v range to [%v-%v]", 135 fromNode, toNode, current.SuccessAmt, current.FailAmt) 136 137 nodePairs[toNode] = current 138 } 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 145 for fromNode, nodePairs := range m.lastPairResult { 146 for toNode := range nodePairs { 147 if fromNode == node || toNode == node { 148 nodePairs[toNode] = TimedPairResult{ 149 FailTime: timestamp, 150 } 151 } 152 } 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 161 // Look up previous second chance time. 162 pair := DirectedNodePair{ 163 From: fromNode, 164 To: toNode, 165 } 166 lastSecondChance, ok := m.lastSecondChance[pair] 167 168 // If the channel hasn't already be given a second chance or its last 169 // second chance was long ago, we give it another chance. 170 if !ok || timestamp.Sub(lastSecondChance) > minSecondChanceInterval { 171 m.lastSecondChance[pair] = timestamp 172 173 log.Debugf("Second chance granted for %v->%v", fromNode, toNode) 174 175 return true 176 } 177 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)) 183 184 return false 185 } 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: "+ 191 "pair_result_count=%v", len(m.lastPairResult)) 192 193 pairs := make([]MissionControlPairSnapshot, 0, len(m.lastPairResult)) 194 195 for fromNode, fromPairs := range m.lastPairResult { 196 for toNode, result := range fromPairs { 197 pair := NewDirectedNodePair(fromNode, toNode) 198 199 pairSnapshot := MissionControlPairSnapshot{ 200 Pair: pair, 201 TimedPairResult: result, 202 } 203 204 pairs = append(pairs, pairSnapshot) 205 } 206 } 207 208 snapshot := MissionControlSnapshot{ 209 Pairs: pairs, 210 } 211 212 return &snapshot 213 } 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 221 var imported int 222 223 for _, pair := range snapshot.Pairs { 224 fromNode := pair.Pair.From 225 toNode := pair.Pair.To 226 227 results, found := m.getLastPairResult(fromNode) 228 if !found { 229 results = make(map[route.Vertex]TimedPairResult) 230 } 231 232 lastResult := results[toNode] 233 234 failResult := failPairResult(pair.FailAmt) 235 imported += m.importResult( 236 lastResult.FailTime, pair.FailTime, failResult, 237 fromNode, toNode, force, 238 ) 239 240 successResult := successPairResult(pair.SuccessAmt) 241 imported += m.importResult( 242 lastResult.SuccessTime, pair.SuccessTime, successResult, 243 fromNode, toNode, force, 244 ) 245 } 246 247 return imported 248 } 249 250 func (m *missionControlState) importResult(currentTs, importedTs time.Time, 251 importedResult pairResult, fromNode, toNode route.Vertex, 252 force bool) int { 253 254 if !force && currentTs.After(importedTs) { 255 log.Debugf("Not setting pair result for %v->%v (%v) "+ 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, 265 ) 266 267 return 1 268 }