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  }