routing: use distinct probability estimation for local channels

Previously we used the a priori probability also for our own untried
channels. This led to local channels that had seen a success already
being prioritized over untried local channels. In some cases, depending
on the configured payment attempt cost, this could lead to the payment
taking a two hop route while a direct payment was also possible.
This commit is contained in:
Joost Jager 2019-11-07 11:24:38 +01:00
parent 5a80c3459f
commit dc0399af51
No known key found for this signature in database
GPG Key ID: A61B9D4C393C59C7
4 changed files with 35 additions and 2 deletions

View File

@ -118,6 +118,9 @@ type MissionControlConfig struct {
// probability completely and only base the probability on historical // probability completely and only base the probability on historical
// results, unless there are none available. // results, unless there are none available.
AprioriWeight float64 AprioriWeight float64
// SelfNode is our own pubkey.
SelfNode route.Vertex
} }
// TimedPairResult describes a timestamped pair result. // TimedPairResult describes a timestamped pair result.
@ -261,6 +264,11 @@ func (m *MissionControl) GetProbability(fromNode, toNode route.Vertex,
now := m.now() now := m.now()
results := m.lastPairResult[fromNode] results := m.lastPairResult[fromNode]
// Use a distinct probability estimation function for local channels.
if fromNode == m.cfg.SelfNode {
return m.estimator.getLocalPairProbability(now, results, toNode)
}
return m.estimator.getPairProbability(now, results, toNode, amt) return m.estimator.getPairProbability(now, results, toNode, amt)
} }

View File

@ -13,7 +13,7 @@ import (
var ( var (
mcTestRoute = &route.Route{ mcTestRoute = &route.Route{
SourcePubKey: route.Vertex{10}, SourcePubKey: mcTestSelf,
Hops: []*route.Hop{ Hops: []*route.Hop{
{ {
ChannelID: 1, ChannelID: 1,
@ -30,6 +30,7 @@ var (
} }
mcTestTime = time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC) mcTestTime = time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC)
mcTestSelf = route.Vertex{10}
mcTestNode1 = mcTestRoute.Hops[0].PubKeyBytes mcTestNode1 = mcTestRoute.Hops[0].PubKeyBytes
mcTestNode2 = mcTestRoute.Hops[1].PubKeyBytes mcTestNode2 = mcTestRoute.Hops[1].PubKeyBytes
@ -80,6 +81,7 @@ func (ctx *mcTestContext) restartMc() {
PenaltyHalfLife: testPenaltyHalfLife, PenaltyHalfLife: testPenaltyHalfLife,
AprioriHopProbability: testAprioriHopProbability, AprioriHopProbability: testAprioriHopProbability,
AprioriWeight: testAprioriWeight, AprioriWeight: testAprioriWeight,
SelfNode: mcTestSelf,
}, },
) )
if err != nil { if err != nil {
@ -98,7 +100,6 @@ func (ctx *mcTestContext) cleanup() {
// Assert that mission control returns a probability for an edge. // Assert that mission control returns a probability for an edge.
func (ctx *mcTestContext) expectP(amt lnwire.MilliSatoshi, expected float64) { func (ctx *mcTestContext) expectP(amt lnwire.MilliSatoshi, expected float64) {
ctx.t.Helper() ctx.t.Helper()
p := ctx.mc.GetProbability(mcTestNode1, mcTestNode2, amt) p := ctx.mc.GetProbability(mcTestNode1, mcTestNode2, amt)
@ -138,6 +139,13 @@ func TestMissionControl(t *testing.T) {
testTime := time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC) testTime := time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC)
// For local channels, we expect a higher probability than our a prior
// test probability.
selfP := ctx.mc.GetProbability(mcTestSelf, mcTestNode1, 100)
if selfP != prevSuccessProbability {
t.Fatalf("expected prev success prob for untried local chans")
}
// Initial probability is expected to be the a priori. // Initial probability is expected to be the a priori.
ctx.expectP(1000, testAprioriHopProbability) ctx.expectP(1000, testAprioriHopProbability)

View File

@ -123,6 +123,22 @@ func (p *probabilityEstimator) getPairProbability(
) )
} }
// getLocalPairProbability estimates the probability of successfully traversing
// our own local channels to toNode.
func (p *probabilityEstimator) getLocalPairProbability(
now time.Time, results NodeResults, toNode route.Vertex) float64 {
// For local channels that have never been tried before, we assume them
// to be successful. We have accurate balance and online status
// information on our own channels, so when we select them in a route it
// is close to certain that those channels will work.
nodeProbability := p.prevSuccessProbability
return p.calculateProbability(
now, results, nodeProbability, toNode, lnwire.MaxMilliSatoshi,
)
}
// calculateProbability estimates the probability of successfully traversing to // calculateProbability estimates the probability of successfully traversing to
// toNode based on historical payment outcomes and a fall-back node probability. // toNode based on historical payment outcomes and a fall-back node probability.
func (p *probabilityEstimator) calculateProbability( func (p *probabilityEstimator) calculateProbability(

View File

@ -672,6 +672,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
PenaltyHalfLife: routingConfig.PenaltyHalfLife, PenaltyHalfLife: routingConfig.PenaltyHalfLife,
MaxMcHistory: routingConfig.MaxMcHistory, MaxMcHistory: routingConfig.MaxMcHistory,
AprioriWeight: routingConfig.AprioriWeight, AprioriWeight: routingConfig.AprioriWeight,
SelfNode: selfNode.PubKeyBytes,
}, },
) )
if err != nil { if err != nil {