routing: track amt ranges in mc
This commit is contained in:
8 files changed
+320
-229
No files matched your search
+75
-28
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user