routing: track amt ranges in mc

This commit is contained in:
Joost Jager committed 2019-11-22 11:17:28 +01:00
1 parent 62f8cca75b
commit cdf1aa5bc1
8 files changed
+320 -229

No files matched your search

+75 -28
View File
@@ -125,26 +125,22 @@ type MissionControlConfig struct {
// TimedPairResult describes a timestamped pair result.
type TimedPairResult struct {
// timestamp is the time when this result was obtained.
Timestamp time.Time
// FailTime is the time of the last failure.
FailTime time.Time
// Amt is the amount that was forwarded in the last payment attempt.
Amt lnwire.MilliSatoshi
// FailAmt is the amount of the last failure. This amount may be pushed
// up if a later success is higher than the last failed amount.
FailAmt lnwire.MilliSatoshi
// Success indicates whether the payment attempt was successful through
// this pair.
Success bool
}
// SuccessTime is the time of the last success.
SuccessTime time.Time
// newTimedPairResult wraps a pair result with a timestamp.
func newTimedPairResult(timestamp time.Time,
result pairResult) TimedPairResult {
return TimedPairResult{
Timestamp: timestamp,
Amt: result.amt,
Success: result.success,
}
// SuccessAmt is the highest amount that successfully forwarded. This
// isn't necessarily the last success amount. The value of this field
// may also be pushed down if a later failure is lower than the highest
// success amount. Because of this, SuccessAmt may not match
// SuccessTime.
SuccessAmt lnwire.MilliSatoshi
}
// MissionControlSnapshot contains a snapshot of the current state of mission
@@ -272,8 +268,8 @@ func (m *MissionControl) GetProbability(fromNode, toNode route.Vertex,
}
// setLastPairResult stores a result for a node pair.
func (m *MissionControl) setLastPairResult(fromNode,
toNode route.Vertex, result TimedPairResult) {
func (m *MissionControl) setLastPairResult(fromNode, toNode route.Vertex,
timestamp time.Time, result *pairResult) {
nodePairs, ok := m.lastPairResult[fromNode]
if !ok {
@@ -281,7 +277,60 @@ func (m *MissionControl) setLastPairResult(fromNode,
m.lastPairResult[fromNode] = nodePairs
}
nodePairs[toNode] = result
current := nodePairs[toNode]
// Apply the new result to the existing data for this pair. If there is
// no existing data, apply it to the default values for TimedPairResult.
if result.success {
successAmt := result.amt
current.SuccessTime = timestamp
// Only update the success amount if this amount is higher. This
// prevents the success range from shrinking when there is no
// reason to do so. For example: small amount probes shouldn't
// affect a previous success for a much larger amount.
if successAmt > current.SuccessAmt {
current.SuccessAmt = successAmt
}
// If the success amount goes into the failure range, move the
// failure range up. Future attempts up to the success amount
// are likely to succeed. We don't want to clear the failure
// completely, because we haven't learnt much for amounts above
// the current success amount.
if !current.FailTime.IsZero() && successAmt >= current.FailAmt {
current.FailAmt = successAmt + 1
}
} else {
// For failures we always want to update both the amount and the
// time. Those need to relate to the same result, because the
// time is used to gradually diminish the penality for that
// specific result. Updating the timestamp but not the amount
// could cause a failure for a lower amount (a more severe
// condition) to be revived as if it just happened.
failAmt := result.amt
current.FailTime = timestamp
current.FailAmt = failAmt
switch {
// The failure amount is set to zero when the failure is
// amount-independent, meaning that the attempt would have
// failed regardless of the amount. This should also reset the
// success amount to zero.
case failAmt == 0:
current.SuccessAmt = 0
// If the failure range goes into the success range, move the
// success range down.
case failAmt <= current.SuccessAmt:
current.SuccessAmt = failAmt - 1
}
}
log.Debugf("Setting %v->%v range to [%v-%v]",
fromNode, toNode, current.SuccessAmt, current.FailAmt)
nodePairs[toNode] = current
}
// setAllFail stores a fail result for all known connection of the given node.
@@ -294,9 +343,9 @@ func (m *MissionControl) setAllFail(fromNode route.Vertex,
}
for connection := range nodePairs {
nodePairs[connection] = newTimedPairResult(
timestamp, failPairResult(0),
)
nodePairs[connection] = TimedPairResult{
FailTime: timestamp,
}
}
}
@@ -491,6 +540,8 @@ func (m *MissionControl) applyPaymentResult(
}
for pair, pairResult := range i.pairResults {
pairResult := pairResult
if pairResult.success {
log.Debugf("Reporting pair success to Mission "+
"Control: pair=%v", pair)
@@ -501,11 +552,7 @@ func (m *MissionControl) applyPaymentResult(
}
m.setLastPairResult(
pair.From, pair.To,
newTimedPairResult(
result.timeReply,
pairResult,
),
pair.From, pair.To, result.timeReply, &pairResult,
)
}
+2 -2
View File
@@ -204,12 +204,12 @@ func TestMissionControlChannelUpdate(t *testing.T) {
ctx.reportFailure(
0, lnwire.NewFeeInsufficient(0, lnwire.ChannelUpdate{}),
)
ctx.expectP(0, testAprioriHopProbability)
ctx.expectP(100, testAprioriHopProbability)
// Report another failure for the same channel. We expect it to be
// pruned.
ctx.reportFailure(
0, lnwire.NewFeeInsufficient(0, lnwire.ChannelUpdate{}),
)
ctx.expectP(0, 0)
ctx.expectP(100, 0)
}
+12 -10
View File
@@ -80,19 +80,20 @@ func (p *probabilityEstimator) getNodeProbability(now time.Time,
totalWeight := aprioriFactor
for _, result := range results {
age := now.Sub(result.Timestamp)
switch {
// Weigh success with a constant high weight of 1. There is no
// decay.
case result.Success:
// decay. Amt is never zero, so this clause is never executed
// when result.SuccessAmt is zero.
case amt <= result.SuccessAmt:
totalWeight++
probabilitiesTotal += p.prevSuccessProbability
// Weigh failures in accordance with their age. The base
// probability of a failure is considered zero, so nothing needs
// to be added to probabilitiesTotal.
case amt >= result.Amt:
case !result.FailTime.IsZero() && amt >= result.FailAmt:
age := now.Sub(result.FailTime)
totalWeight += p.getWeight(age)
}
}
@@ -155,9 +156,10 @@ func (p *probabilityEstimator) calculateProbability(
return nodeProbability
}
// For successes, we have a fixed (high) probability. Those pairs
// will be assumed good until proven otherwise.
if lastPairResult.Success {
// For successes, we have a fixed (high) probability. Those pairs will
// be assumed good until proven otherwise. Amt is never zero, so this
// clause is never executed when lastPairResult.SuccessAmt is zero.
if amt <= lastPairResult.SuccessAmt {
return p.prevSuccessProbability
}
@@ -166,11 +168,11 @@ func (p *probabilityEstimator) calculateProbability(
// penalization. If the current amount is smaller than the amount that
// previously triggered a failure, we act as if this is an untried
// channel.
if amt < lastPairResult.Amt {
if lastPairResult.FailTime.IsZero() || amt < lastPairResult.FailAmt {
return nodeProbability
}
timeSinceLastFailure := now.Sub(lastPairResult.Timestamp)
timeSinceLastFailure := now.Sub(lastPairResult.FailTime)
// Calculate success probability based on the weight of the last
// failure. When the failure is fresh, its weight is 1 and we'll return
+18 -20
View File
@@ -84,10 +84,9 @@ func TestProbabilityEstimatorOneSuccess(t *testing.T) {
ctx := newEstimatorTestContext(t)
ctx.results = map[int]TimedPairResult{
node1: newTimedPairResult(
testTime.Add(-time.Hour),
successPairResult(100),
),
node1: {
SuccessAmt: lnwire.MilliSatoshi(1000),
},
}
// Because of the previous success, this channel keep reporting a high
@@ -108,10 +107,10 @@ func TestProbabilityEstimatorOneFailure(t *testing.T) {
ctx := newEstimatorTestContext(t)
ctx.results = map[int]TimedPairResult{
node1: newTimedPairResult(
testTime.Add(-time.Hour),
failPairResult(0),
),
node1: {
FailTime: testTime.Add(-time.Hour),
FailAmt: lnwire.MilliSatoshi(50),
},
}
// For an untried node, we expected the node probability. The weight for
@@ -131,18 +130,17 @@ func TestProbabilityEstimatorMix(t *testing.T) {
ctx := newEstimatorTestContext(t)
ctx.results = map[int]TimedPairResult{
node1: newTimedPairResult(
testTime.Add(-time.Hour),
successPairResult(100),
),
node2: newTimedPairResult(
testTime.Add(-2*time.Hour),
failPairResult(0),
),
node3: newTimedPairResult(
testTime.Add(-3*time.Hour),
failPairResult(0),
),
node1: {
SuccessAmt: lnwire.MilliSatoshi(1000),
},
node2: {
FailTime: testTime.Add(-2 * time.Hour),
FailAmt: lnwire.MilliSatoshi(50),
},
node3: {
FailTime: testTime.Add(-3 * time.Hour),
FailAmt: lnwire.MilliSatoshi(50),
},
}
// We expect the probability for a previously successful channel to