diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index cebac51c..2d5deb50 100644 --- a/routing/missioncontrol.go +++ b/routing/missioncontrol.go @@ -118,6 +118,9 @@ type MissionControlConfig struct { // probability completely and only base the probability on historical // results, unless there are none available. AprioriWeight float64 + + // SelfNode is our own pubkey. + SelfNode route.Vertex } // TimedPairResult describes a timestamped pair result. @@ -261,6 +264,11 @@ func (m *MissionControl) GetProbability(fromNode, toNode route.Vertex, now := m.now() 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) } diff --git a/routing/missioncontrol_test.go b/routing/missioncontrol_test.go index bbef69c9..f7b2f33a 100644 --- a/routing/missioncontrol_test.go +++ b/routing/missioncontrol_test.go @@ -13,7 +13,7 @@ import ( var ( mcTestRoute = &route.Route{ - SourcePubKey: route.Vertex{10}, + SourcePubKey: mcTestSelf, Hops: []*route.Hop{ { ChannelID: 1, @@ -30,6 +30,7 @@ var ( } mcTestTime = time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC) + mcTestSelf = route.Vertex{10} mcTestNode1 = mcTestRoute.Hops[0].PubKeyBytes mcTestNode2 = mcTestRoute.Hops[1].PubKeyBytes @@ -80,6 +81,7 @@ func (ctx *mcTestContext) restartMc() { PenaltyHalfLife: testPenaltyHalfLife, AprioriHopProbability: testAprioriHopProbability, AprioriWeight: testAprioriWeight, + SelfNode: mcTestSelf, }, ) if err != nil { @@ -98,7 +100,6 @@ func (ctx *mcTestContext) cleanup() { // Assert that mission control returns a probability for an edge. func (ctx *mcTestContext) expectP(amt lnwire.MilliSatoshi, expected float64) { - ctx.t.Helper() 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) + // 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. ctx.expectP(1000, testAprioriHopProbability) diff --git a/routing/probability_estimator.go b/routing/probability_estimator.go index 9d18067e..1e60608c 100644 --- a/routing/probability_estimator.go +++ b/routing/probability_estimator.go @@ -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 // toNode based on historical payment outcomes and a fall-back node probability. func (p *probabilityEstimator) calculateProbability( diff --git a/server.go b/server.go index b8872e9e..7baf1446 100644 --- a/server.go +++ b/server.go @@ -672,6 +672,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, PenaltyHalfLife: routingConfig.PenaltyHalfLife, MaxMcHistory: routingConfig.MaxMcHistory, AprioriWeight: routingConfig.AprioriWeight, + SelfNode: selfNode.PubKeyBytes, }, ) if err != nil {