routing: process successes in mission control

This commit modifies paymentLifecycle so that it not only feeds
failures into mission control, but successes as well.
This allows for more accurate probability estimates. Previously,
the success probability for a successful pair and a pair with
no history was equal. There was no force that pushed towards
previously successful routes.
This commit is contained in:
Joost Jager committed 2019-08-23 09:15:41 +02:00
1 parent 2594abfba1
commit ff0c5a0d5e
12 files changed
+415 -190

No files matched your search

+72 -28
View File
@@ -43,6 +43,10 @@ const (
// DefaultMaxMcHistory is the default maximum history size.
DefaultMaxMcHistory = 1000
// prevSuccessProbability is the assumed probability for node pairs that
// successfully relayed the previous attempt.
prevSuccessProbability = 0.95
)
// MissionControl contains state which summarizes the past attempts of HTLC
@@ -55,8 +59,8 @@ const (
// since the last failure is used to estimate a success probability that is fed
// into the path finding process for subsequent payment attempts.
type MissionControl struct {
// lastPairFailure tracks the last payment failure per node pair.
lastPairFailure map[DirectedNodePair]pairFailure
// lastPairResult tracks the last payment result per node pair.
lastPairResult map[DirectedNodePair]timedPairResult
// lastNodeFailure tracks the last node level failure per node.
lastNodeFailure map[route.Vertex]time.Time
@@ -97,14 +101,12 @@ type MissionControlConfig struct {
MaxMcHistory int
}
// pairFailure describes a payment failure for a node pair.
type pairFailure struct {
// timestamp is the time when this failure result was obtained.
// timedPairResult describes a timestamped pair result.
type timedPairResult struct {
// timestamp is the time when this result was obtained.
timestamp time.Time
// minPenalizeAmt is the minimum amount for which to take this failure
// into account.
minPenalizeAmt lnwire.MilliSatoshi
pairResult
}
// MissionControlSnapshot contains a snapshot of the current state of mission
@@ -138,8 +140,8 @@ type MissionControlPairSnapshot struct {
// Pair is the node pair of which the state is described.
Pair DirectedNodePair
// LastFail is the time of last failure.
LastFail time.Time
// Timestamp is the time of last result.
Timestamp time.Time
// MinPenalizeAmt is the minimum amount for which the channel will be
// penalized.
@@ -147,6 +149,10 @@ type MissionControlPairSnapshot struct {
// SuccessProb is the success probability estimation for this channel.
SuccessProb float64
// LastAttemptSuccessful indicates whether the last payment attempt
// through this pair was successful.
LastAttemptSuccessful bool
}
// paymentResult is the information that becomes available when a payment
@@ -174,7 +180,7 @@ func NewMissionControl(db *bbolt.DB, cfg *MissionControlConfig) (
}
mc := &MissionControl{
lastPairFailure: make(map[DirectedNodePair]pairFailure),
lastPairResult: make(map[DirectedNodePair]timedPairResult),
lastNodeFailure: make(map[route.Vertex]time.Time),
lastSecondChance: make(map[DirectedNodePair]time.Time),
now: time.Now,
@@ -220,7 +226,7 @@ func (m *MissionControl) ResetHistory() error {
return err
}
m.lastPairFailure = make(map[DirectedNodePair]pairFailure)
m.lastPairResult = make(map[DirectedNodePair]timedPairResult)
m.lastNodeFailure = make(map[route.Vertex]time.Time)
m.lastSecondChance = make(map[DirectedNodePair]time.Time)
@@ -271,12 +277,16 @@ func (m *MissionControl) getPairProbability(fromNode,
// Retrieve the last pair outcome.
pair := NewDirectedNodePair(fromNode, toNode)
lastPairResult, ok := m.lastPairFailure[pair]
lastPairResult, ok := m.lastPairResult[pair]
// Only look at the last pair outcome if it happened after the last node
// level failure. Otherwise the node level failure is the most recent
// and used as the basis for calculation of the probability.
if ok && lastPairResult.timestamp.After(lastFail) {
if lastPairResult.success {
return prevSuccessProbability
}
// Take into account a minimum penalize amount. For balance
// errors, a failure may be reported with such a minimum to
// prevent too aggresive penalization. We only take into account
@@ -330,7 +340,7 @@ func (m *MissionControl) GetHistorySnapshot() *MissionControlSnapshot {
log.Debugf("Requesting history snapshot from mission control: "+
"node_failure_count=%v, pair_result_count=%v",
len(m.lastNodeFailure), len(m.lastPairFailure))
len(m.lastNodeFailure), len(m.lastPairResult))
nodes := make([]MissionControlNodeSnapshot, 0, len(m.lastNodeFailure))
for v, h := range m.lastNodeFailure {
@@ -343,18 +353,19 @@ func (m *MissionControl) GetHistorySnapshot() *MissionControlSnapshot {
})
}
pairs := make([]MissionControlPairSnapshot, 0, len(m.lastPairFailure))
pairs := make([]MissionControlPairSnapshot, 0, len(m.lastPairResult))
for v, h := range m.lastPairFailure {
for v, h := range m.lastPairResult {
// Show probability assuming amount meets min
// penalization amount.
prob := m.getPairProbability(v.From, v.To, h.minPenalizeAmt)
pair := MissionControlPairSnapshot{
Pair: v,
MinPenalizeAmt: h.minPenalizeAmt,
LastFail: h.timestamp,
SuccessProb: prob,
Pair: v,
MinPenalizeAmt: h.minPenalizeAmt,
Timestamp: h.timestamp,
SuccessProb: prob,
LastAttemptSuccessful: h.success,
}
pairs = append(pairs, pair)
@@ -379,7 +390,6 @@ func (m *MissionControl) ReportPaymentFail(paymentID uint64, rt *route.Route,
timestamp := m.now()
// TODO(joostjager): Use actual payment initiation time for timeFwd.
result := &paymentResult{
success: false,
timeFwd: timestamp,
@@ -390,6 +400,33 @@ func (m *MissionControl) ReportPaymentFail(paymentID uint64, rt *route.Route,
route: rt,
}
return m.processPaymentResult(result)
}
// ReportPaymentSuccess reports a successful payment to mission control as input
// for future probability estimates.
func (m *MissionControl) ReportPaymentSuccess(paymentID uint64,
rt *route.Route) error {
timestamp := m.now()
result := &paymentResult{
timeFwd: timestamp,
timeReply: timestamp,
id: paymentID,
success: true,
route: rt,
}
_, err := m.processPaymentResult(result)
return err
}
// processPaymentResult stores a payment result in the mission control store and
// updates mission control's in-memory state.
func (m *MissionControl) processPaymentResult(result *paymentResult) (
*channeldb.FailureReason, error) {
// Store complete result in database.
if err := m.store.AddResult(result); err != nil {
return nil, err
@@ -409,7 +446,8 @@ func (m *MissionControl) applyPaymentResult(
// Interpret result.
i := interpretResult(
result.route, result.failureSourceIdx, result.failure,
result.route, result.success, result.failureSourceIdx,
result.failure,
)
// Update mission control state using the interpretation.
@@ -432,13 +470,19 @@ func (m *MissionControl) applyPaymentResult(
m.lastNodeFailure[*i.nodeFailure] = result.timeReply
}
for pair, minPenalizeAmt := range i.pairResults {
log.Debugf("Reporting pair failure to Mission Control: "+
"pair=%v, minPenalizeAmt=%v", pair, minPenalizeAmt)
for pair, pairResult := range i.pairResults {
if pairResult.success {
log.Debugf("Reporting pair success to Mission "+
"Control: pair=%v", pair)
} else {
log.Debugf("Reporting pair failure to Mission "+
"Control: pair=%v, minPenalizeAmt=%v",
pair, pairResult.minPenalizeAmt)
}
m.lastPairFailure[pair] = pairFailure{
minPenalizeAmt: minPenalizeAmt,
timestamp: result.timeReply,
m.lastPairResult[pair] = timedPairResult{
timestamp: result.timeReply,
pairResult: pairResult,
}
}