Merge pull request #3686 from joostjager/local-apriori
routing: use distinct probability estimation for local channels
This commit is contained in:
commit
92fb5ac363
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -116,13 +116,43 @@ func (p *probabilityEstimator) getPairProbability(
|
|||||||
now time.Time, results NodeResults,
|
now time.Time, results NodeResults,
|
||||||
toNode route.Vertex, amt lnwire.MilliSatoshi) float64 {
|
toNode route.Vertex, amt lnwire.MilliSatoshi) float64 {
|
||||||
|
|
||||||
|
nodeProbability := p.getNodeProbability(now, results, amt)
|
||||||
|
|
||||||
|
return p.calculateProbability(
|
||||||
|
now, results, nodeProbability, toNode, amt,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
now time.Time, results NodeResults,
|
||||||
|
nodeProbability float64, toNode route.Vertex,
|
||||||
|
amt lnwire.MilliSatoshi) float64 {
|
||||||
|
|
||||||
// Retrieve the last pair outcome.
|
// Retrieve the last pair outcome.
|
||||||
lastPairResult, ok := results[toNode]
|
lastPairResult, ok := results[toNode]
|
||||||
|
|
||||||
// If there is no history for this pair, return the node probability
|
// If there is no history for this pair, return the node probability
|
||||||
// that is a probability estimate for untried channel.
|
// that is a probability estimate for untried channel.
|
||||||
if !ok {
|
if !ok {
|
||||||
return p.getNodeProbability(now, results, amt)
|
return nodeProbability
|
||||||
}
|
}
|
||||||
|
|
||||||
// For successes, we have a fixed (high) probability. Those pairs
|
// For successes, we have a fixed (high) probability. Those pairs
|
||||||
@ -131,8 +161,6 @@ func (p *probabilityEstimator) getPairProbability(
|
|||||||
return p.prevSuccessProbability
|
return p.prevSuccessProbability
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeProbability := p.getNodeProbability(now, results, amt)
|
|
||||||
|
|
||||||
// Take into account a minimum penalize amount. For balance errors, a
|
// Take into account a minimum penalize amount. For balance errors, a
|
||||||
// failure may be reported with such a minimum to prevent too aggressive
|
// failure may be reported with such a minimum to prevent too aggressive
|
||||||
// penalization. If the current amount is smaller than the amount that
|
// penalization. If the current amount is smaller than the amount that
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user