diff --git a/routing/probability_bimodal.go b/routing/probability_bimodal.go index 06f2646ec..d23b96666 100644 --- a/routing/probability_bimodal.go +++ b/routing/probability_bimodal.go @@ -308,6 +308,32 @@ func (p *BimodalEstimator) calculateProbability(directProbability float64, return directProbability } + // If we have up-to-date information about the channel we want to use, + // i.e. the info stems from results not longer ago than the decay time, + // we will only use the direct probability. This is needed in order to + // avoid that other previous results (on all other channels of the same + // routing node) will distort and pin the calculated probability even if + // we have accurate direct information. This helps to dip the + // probability below the min probability in case of failures, to start + // the splitting process. + directResult, ok := results[toNode] + if ok { + latest := directResult.SuccessTime + if directResult.FailTime.After(latest) { + latest = directResult.FailTime + } + + // We use BimonodalDecayTime to judge the currentness of the + // data. It is the time scale on which we assume to have lost + // information. + if now.Sub(latest) < p.BimodalDecayTime { + log.Tracef("Using direct probability for node %v: %v", + toNode, directResult) + + return directProbability + } + } + // w is a parameter which determines how strongly the other channels of // a node should be incorporated, the higher the stronger. w := p.BimodalNodeWeight diff --git a/routing/probability_bimodal_test.go b/routing/probability_bimodal_test.go index 8e8797d39..676fe348f 100644 --- a/routing/probability_bimodal_test.go +++ b/routing/probability_bimodal_test.go @@ -379,35 +379,44 @@ func TestComputeProbability(t *testing.T) { nodeWeight := 1 / 5. toNode := route.Vertex{10} tolerance := 0.01 + now := time.Unix(0, 0) decayTime := time.Duration(1) * time.Hour * 24 // makeNodeResults prepares forwarding data for the other channels of // the node. - makeNodeResults := func(successes []bool, now time.Time) NodeResults { + makeNodeResults := func(successes []bool, resultsTime time.Time, + directResultTime time.Time) NodeResults { + results := make(NodeResults, len(successes)) for i, s := range successes { vertex := route.Vertex{byte(i)} results[vertex] = TimedPairResult{ - FailTime: now, FailAmt: 1, + FailTime: resultsTime, FailAmt: 1, } if s { results[vertex] = TimedPairResult{ - SuccessTime: now, SuccessAmt: 1, + SuccessTime: resultsTime, SuccessAmt: 1, } } } + // Include a direct result. + results[toNode] = TimedPairResult{ + SuccessTime: directResultTime, SuccessAmt: 1, + } + return results } tests := []struct { name string directProbability float64 + recentDirectResult bool otherResults []bool expectedProbability float64 - delay time.Duration + resultsTimeAgo time.Duration }{ // If no other information is available, use the direct // probability. @@ -526,7 +535,7 @@ func TestComputeProbability(t *testing.T) { "time", directProbability: 1.0, otherResults: []bool{true}, - delay: decayTime, + resultsTimeAgo: decayTime, expectedProbability: 1.00, }, // A failure that was experienced some time ago won't influence @@ -535,7 +544,7 @@ func TestComputeProbability(t *testing.T) { name: "success, single fail, decay time", directProbability: 1.0, otherResults: []bool{false}, - delay: decayTime, + resultsTimeAgo: decayTime, expectedProbability: 0.9314, }, // Information from a long time ago doesn't have any effect. @@ -543,7 +552,7 @@ func TestComputeProbability(t *testing.T) { name: "success, single fail, long ago", directProbability: 1.0, otherResults: []bool{false}, - delay: 10 * decayTime, + resultsTimeAgo: 10 * decayTime, expectedProbability: 1.0, }, { @@ -552,7 +561,7 @@ func TestComputeProbability(t *testing.T) { otherResults: []bool{ true, true, true, true, true, }, - delay: decayTime, + resultsTimeAgo: decayTime, expectedProbability: 0.269, }, // Very recent info approaches the case with no time decay. @@ -562,9 +571,22 @@ func TestComputeProbability(t *testing.T) { otherResults: []bool{ true, true, true, true, true, }, - delay: decayTime / 10, + resultsTimeAgo: decayTime / 10, expectedProbability: 0.741, }, + // If we have recent info on the direct probability, we don't + // include node-wide info. Here we check that a recent failure + // is not pinned to a high probability by many successes on the + // node. + { + name: "recent direct result", + directProbability: 0.1, + recentDirectResult: true, + otherResults: []bool{ + true, true, true, true, true, + }, + expectedProbability: 0.1, + }, } estimator := BimodalEstimator{ @@ -580,9 +602,19 @@ func TestComputeProbability(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - then := time.Unix(0, 0) - results := makeNodeResults(test.otherResults, then) - now := then.Add(test.delay) + var directResultTime time.Time + if test.recentDirectResult { + directResultTime = now.Add(-decayTime / 2) + } else { + directResultTime = now.Add(-2 * decayTime) + } + + resultsTime := now.Add(-test.resultsTimeAgo) + + results := makeNodeResults( + test.otherResults, resultsTime, + directResultTime, + ) p := estimator.calculateProbability( test.directProbability, now, results, toNode,