From 37e275169537d48094fd3051d46b6dcc40545050 Mon Sep 17 00:00:00 2001
From: Joost Jager <joost.jager@gmail.com>
Date: Tue, 18 Jun 2019 18:30:56 +0200
Subject: [PATCH] routing+routerrpc: isolate payment session source from
 mission control

---
 lnrpc/routerrpc/config.go         |  28 ++++++
 lnrpc/routerrpc/config_active.go  |   7 +-
 lnrpc/routerrpc/config_default.go |   7 +-
 routing/missioncontrol.go         | 157 +++---------------------------
 routing/missioncontrol_test.go    |  10 +-
 routing/mock_test.go              |  17 ++++
 routing/payment_session.go        |  33 ++++---
 routing/payment_session_source.go | 150 ++++++++++++++++++++++++++++
 routing/payment_session_test.go   |  14 ++-
 routing/router.go                 |  28 +++++-
 routing/router_test.go            |  25 +++--
 server.go                         |  23 ++++-
 12 files changed, 308 insertions(+), 191 deletions(-)
 create mode 100644 lnrpc/routerrpc/config.go
 create mode 100644 routing/payment_session_source.go

diff --git a/lnrpc/routerrpc/config.go b/lnrpc/routerrpc/config.go
new file mode 100644
index 000000000..15dd6c40d
--- /dev/null
+++ b/lnrpc/routerrpc/config.go
@@ -0,0 +1,28 @@
+package routerrpc
+
+import (
+	"time"
+
+	"github.com/lightningnetwork/lnd/lnwire"
+)
+
+// RoutingConfig contains the configurable parameters that control routing.
+type RoutingConfig struct {
+	// PenaltyHalfLife defines after how much time a penalized node or
+	// channel is back at 50% probability.
+	PenaltyHalfLife time.Duration
+
+	// PaymentAttemptPenalty is the virtual cost in path finding weight
+	// units of executing a payment attempt that fails. It is used to trade
+	// off potentially better routes against their probability of
+	// succeeding.
+	PaymentAttemptPenalty lnwire.MilliSatoshi
+
+	// MinProbability defines the minimum success probability of the
+	// returned route.
+	MinRouteProbability float64
+
+	// AprioriHopProbability is the assumed success probability of a hop in
+	// a route when no other information is available.
+	AprioriHopProbability float64
+}
diff --git a/lnrpc/routerrpc/config_active.go b/lnrpc/routerrpc/config_active.go
index 36877c365..b919364ea 100644
--- a/lnrpc/routerrpc/config_active.go
+++ b/lnrpc/routerrpc/config_active.go
@@ -72,10 +72,9 @@ func DefaultConfig() *Config {
 	}
 }
 
-// GetMissionControlConfig returns the mission control config based on this sub
-// server config.
-func GetMissionControlConfig(cfg *Config) *routing.MissionControlConfig {
-	return &routing.MissionControlConfig{
+// GetRoutingConfig returns the routing config based on this sub server config.
+func GetRoutingConfig(cfg *Config) *RoutingConfig {
+	return &RoutingConfig{
 		AprioriHopProbability: cfg.AprioriHopProbability,
 		MinRouteProbability:   cfg.MinRouteProbability,
 		PaymentAttemptPenalty: lnwire.NewMSatFromSatoshis(
diff --git a/lnrpc/routerrpc/config_default.go b/lnrpc/routerrpc/config_default.go
index 81c4a577e..a3a95021c 100644
--- a/lnrpc/routerrpc/config_default.go
+++ b/lnrpc/routerrpc/config_default.go
@@ -14,10 +14,9 @@ func DefaultConfig() *Config {
 	return &Config{}
 }
 
-// GetMissionControlConfig returns the mission control config based on this sub
-// server config.
-func GetMissionControlConfig(cfg *Config) *routing.MissionControlConfig {
-	return &routing.MissionControlConfig{
+// GetRoutingConfig returns the routing config based on this sub server config.
+func GetRoutingConfig(cfg *Config) *RoutingConfig {
+	return &RoutingConfig{
 		AprioriHopProbability: routing.DefaultAprioriHopProbability,
 		MinRouteProbability:   routing.DefaultMinRouteProbability,
 		PaymentAttemptPenalty: routing.DefaultPaymentAttemptPenalty,
diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go
index 17575a721..69a34b658 100644
--- a/routing/missioncontrol.go
+++ b/routing/missioncontrol.go
@@ -5,12 +5,10 @@ import (
 	"sync"
 	"time"
 
-	"github.com/btcsuite/btcd/btcec"
 	"github.com/coreos/bbolt"
 	"github.com/lightningnetwork/lnd/channeldb"
 	"github.com/lightningnetwork/lnd/lnwire"
 	"github.com/lightningnetwork/lnd/routing/route"
-	"github.com/lightningnetwork/lnd/zpay32"
 )
 
 const (
@@ -32,12 +30,6 @@ const (
 type MissionControl struct {
 	history map[route.Vertex]*nodeHistory
 
-	graph *channeldb.ChannelGraph
-
-	selfNode *channeldb.LightningNode
-
-	queryBandwidth func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi
-
 	// now is expected to return the current time. It is supplied as an
 	// external function to enable deterministic unit tests.
 	now func() time.Time
@@ -52,10 +44,6 @@ type MissionControl struct {
 	// TODO(roasbeef): also add favorable metrics for nodes
 }
 
-// A compile time assertion to ensure MissionControl meets the
-// PaymentSessionSource interface.
-var _ PaymentSessionSource = (*MissionControl)(nil)
-
 // MissionControlConfig defines parameters that control mission control
 // behaviour.
 type MissionControlConfig struct {
@@ -63,16 +51,6 @@ type MissionControlConfig struct {
 	// channel is back at 50% probability.
 	PenaltyHalfLife time.Duration
 
-	// PaymentAttemptPenalty is the virtual cost in path finding weight
-	// units of executing a payment attempt that fails. It is used to trade
-	// off potentially better routes against their probability of
-	// succeeding.
-	PaymentAttemptPenalty lnwire.MilliSatoshi
-
-	// MinProbability defines the minimum success probability of the
-	// returned route.
-	MinRouteProbability float64
-
 	// AprioriHopProbability is the assumed success probability of a hop in
 	// a route when no other information is available.
 	AprioriHopProbability float64
@@ -143,126 +121,15 @@ type MissionControlChannelSnapshot struct {
 }
 
 // NewMissionControl returns a new instance of missionControl.
-//
-// TODO(roasbeef): persist memory
-func NewMissionControl(g *channeldb.ChannelGraph, selfNode *channeldb.LightningNode,
-	qb func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi,
-	cfg *MissionControlConfig) *MissionControl {
-
+func NewMissionControl(cfg *MissionControlConfig) *MissionControl {
 	log.Debugf("Instantiating mission control with config: "+
-		"PenaltyHalfLife=%v, PaymentAttemptPenalty=%v, "+
-		"MinRouteProbability=%v, AprioriHopProbability=%v",
-		cfg.PenaltyHalfLife,
-		int64(cfg.PaymentAttemptPenalty.ToSatoshis()),
-		cfg.MinRouteProbability, cfg.AprioriHopProbability)
+		"PenaltyHalfLife=%v, AprioriHopProbability=%v",
+		cfg.PenaltyHalfLife, cfg.AprioriHopProbability)
 
 	return &MissionControl{
-		history:        make(map[route.Vertex]*nodeHistory),
-		selfNode:       selfNode,
-		queryBandwidth: qb,
-		graph:          g,
-		now:            time.Now,
-		cfg:            cfg,
-	}
-}
-
-// NewPaymentSession creates a new payment session backed by the latest prune
-// view from Mission Control. An optional set of routing hints can be provided
-// in order to populate additional edges to explore when finding a path to the
-// payment's destination.
-func (m *MissionControl) NewPaymentSession(routeHints [][]zpay32.HopHint,
-	target route.Vertex) (PaymentSession, error) {
-
-	edges := make(map[route.Vertex][]*channeldb.ChannelEdgePolicy)
-
-	// Traverse through all of the available hop hints and include them in
-	// our edges map, indexed by the public key of the channel's starting
-	// node.
-	for _, routeHint := range routeHints {
-		// If multiple hop hints are provided within a single route
-		// hint, we'll assume they must be chained together and sorted
-		// in forward order in order to reach the target successfully.
-		for i, hopHint := range routeHint {
-			// In order to determine the end node of this hint,
-			// we'll need to look at the next hint's start node. If
-			// we've reached the end of the hints list, we can
-			// assume we've reached the destination.
-			endNode := &channeldb.LightningNode{}
-			if i != len(routeHint)-1 {
-				endNode.AddPubKey(routeHint[i+1].NodeID)
-			} else {
-				targetPubKey, err := btcec.ParsePubKey(
-					target[:], btcec.S256(),
-				)
-				if err != nil {
-					return nil, err
-				}
-				endNode.AddPubKey(targetPubKey)
-			}
-
-			// Finally, create the channel edge from the hop hint
-			// and add it to list of edges corresponding to the node
-			// at the start of the channel.
-			edge := &channeldb.ChannelEdgePolicy{
-				Node:      endNode,
-				ChannelID: hopHint.ChannelID,
-				FeeBaseMSat: lnwire.MilliSatoshi(
-					hopHint.FeeBaseMSat,
-				),
-				FeeProportionalMillionths: lnwire.MilliSatoshi(
-					hopHint.FeeProportionalMillionths,
-				),
-				TimeLockDelta: hopHint.CLTVExpiryDelta,
-			}
-
-			v := route.NewVertex(hopHint.NodeID)
-			edges[v] = append(edges[v], edge)
-		}
-	}
-
-	// We'll also obtain a set of bandwidthHints from the lower layer for
-	// each of our outbound channels. This will allow the path finding to
-	// skip any links that aren't active or just don't have enough
-	// bandwidth to carry the payment.
-	sourceNode, err := m.graph.SourceNode()
-	if err != nil {
-		return nil, err
-	}
-	bandwidthHints, err := generateBandwidthHints(
-		sourceNode, m.queryBandwidth,
-	)
-	if err != nil {
-		return nil, err
-	}
-
-	return &paymentSession{
-		additionalEdges:      edges,
-		bandwidthHints:       bandwidthHints,
-		errFailedPolicyChans: make(map[nodeChannel]struct{}),
-		mc:                   m,
-		pathFinder:           findPath,
-	}, nil
-}
-
-// NewPaymentSessionForRoute creates a new paymentSession instance that is just
-// used for failure reporting to missioncontrol.
-func (m *MissionControl) NewPaymentSessionForRoute(preBuiltRoute *route.Route) PaymentSession {
-	return &paymentSession{
-		errFailedPolicyChans: make(map[nodeChannel]struct{}),
-		mc:                   m,
-		preBuiltRoute:        preBuiltRoute,
-	}
-}
-
-// NewPaymentSessionEmpty creates a new paymentSession instance that is empty,
-// and will be exhausted immediately. Used for failure reporting to
-// missioncontrol for resumed payment we don't want to make more attempts for.
-func (m *MissionControl) NewPaymentSessionEmpty() PaymentSession {
-	return &paymentSession{
-		errFailedPolicyChans: make(map[nodeChannel]struct{}),
-		mc:                   m,
-		preBuiltRoute:        &route.Route{},
-		preBuiltRouteTried:   true,
+		history: make(map[route.Vertex]*nodeHistory),
+		now:     time.Now,
+		cfg:     cfg,
 	}
 }
 
@@ -312,9 +179,9 @@ func (m *MissionControl) ResetHistory() {
 	log.Debugf("Mission control history cleared")
 }
 
-// getEdgeProbability is expected to return the success probability of a payment
+// GetEdgeProbability is expected to return the success probability of a payment
 // from fromNode along edge.
-func (m *MissionControl) getEdgeProbability(fromNode route.Vertex,
+func (m *MissionControl) GetEdgeProbability(fromNode route.Vertex,
 	edge EdgeLocator, amt lnwire.MilliSatoshi) float64 {
 
 	m.Lock()
@@ -391,8 +258,8 @@ func (m *MissionControl) createHistoryIfNotExists(vertex route.Vertex) *nodeHist
 	return node
 }
 
-// reportVertexFailure reports a node level failure.
-func (m *MissionControl) reportVertexFailure(v route.Vertex) {
+// ReportVertexFailure reports a node level failure.
+func (m *MissionControl) ReportVertexFailure(v route.Vertex) {
 	log.Debugf("Reporting vertex %v failure to Mission Control", v)
 
 	now := m.now()
@@ -404,10 +271,10 @@ func (m *MissionControl) reportVertexFailure(v route.Vertex) {
 	history.lastFail = &now
 }
 
-// reportEdgeFailure reports a channel level failure.
+// ReportEdgeFailure reports a channel level failure.
 //
 // TODO(roasbeef): also add value attempted to send and capacity of channel
-func (m *MissionControl) reportEdgeFailure(failedEdge edge,
+func (m *MissionControl) ReportEdgeFailure(failedEdge edge,
 	minPenalizeAmt lnwire.MilliSatoshi) {
 
 	log.Debugf("Reporting channel %v failure to Mission Control",
diff --git a/routing/missioncontrol_test.go b/routing/missioncontrol_test.go
index 72c3b07a1..f6f658b8e 100644
--- a/routing/missioncontrol_test.go
+++ b/routing/missioncontrol_test.go
@@ -29,7 +29,7 @@ func createMcTestContext(t *testing.T) *mcTestContext {
 	}
 
 	mc := NewMissionControl(
-		nil, nil, nil, &MissionControlConfig{
+		&MissionControlConfig{
 			PenaltyHalfLife:       30 * time.Minute,
 			AprioriHopProbability: 0.8,
 		},
@@ -47,7 +47,7 @@ func (ctx *mcTestContext) expectP(amt lnwire.MilliSatoshi,
 
 	ctx.t.Helper()
 
-	p := ctx.mc.getEdgeProbability(mcTestNode, mcTestEdge, amt)
+	p := ctx.mc.GetEdgeProbability(mcTestNode, mcTestEdge, amt)
 	if p != expected {
 		ctx.t.Fatalf("unexpected probability %v", p)
 	}
@@ -70,7 +70,7 @@ func TestMissionControl(t *testing.T) {
 	ctx.expectP(1000, 0.8)
 
 	// Expect probability to be zero after reporting the edge as failed.
-	ctx.mc.reportEdgeFailure(testEdge, 1000)
+	ctx.mc.ReportEdgeFailure(testEdge, 1000)
 	ctx.expectP(1000, 0)
 
 	// As we reported with a min penalization amt, a lower amt than reported
@@ -83,7 +83,7 @@ func TestMissionControl(t *testing.T) {
 
 	// Edge fails again, this time without a min penalization amt. The edge
 	// should be penalized regardless of amount.
-	ctx.mc.reportEdgeFailure(testEdge, 0)
+	ctx.mc.ReportEdgeFailure(testEdge, 0)
 	ctx.expectP(1000, 0)
 	ctx.expectP(500, 0)
 
@@ -93,7 +93,7 @@ func TestMissionControl(t *testing.T) {
 
 	// A node level failure should bring probability of every channel back
 	// to zero.
-	ctx.mc.reportVertexFailure(testNode)
+	ctx.mc.ReportVertexFailure(testNode)
 	ctx.expectP(1000, 0)
 
 	// Check whether history snapshot looks sane.
diff --git a/routing/mock_test.go b/routing/mock_test.go
index f2f7de780..cf0a3f3be 100644
--- a/routing/mock_test.go
+++ b/routing/mock_test.go
@@ -93,6 +93,23 @@ func (m *mockPaymentSessionSource) NewPaymentSessionEmpty() PaymentSession {
 	return &mockPaymentSession{}
 }
 
+type mockMissionControl struct {
+}
+
+var _ MissionController = (*mockMissionControl)(nil)
+
+func (m *mockMissionControl) ReportEdgeFailure(failedEdge edge,
+	minPenalizeAmt lnwire.MilliSatoshi) {
+}
+
+func (m *mockMissionControl) ReportVertexFailure(v route.Vertex) {}
+
+func (m *mockMissionControl) GetEdgeProbability(fromNode route.Vertex, edge EdgeLocator,
+	amt lnwire.MilliSatoshi) float64 {
+
+	return 0
+}
+
 type mockPaymentSession struct {
 	routes []*route.Route
 }
diff --git a/routing/payment_session.go b/routing/payment_session.go
index 7508e30a3..4a2c0a5bf 100644
--- a/routing/payment_session.go
+++ b/routing/payment_session.go
@@ -58,7 +58,7 @@ type paymentSession struct {
 	// require pruning, but any subsequent ones do.
 	errFailedPolicyChans map[nodeChannel]struct{}
 
-	mc *MissionControl
+	sessionSource *SessionSource
 
 	preBuiltRoute      *route.Route
 	preBuiltRouteTried bool
@@ -78,7 +78,7 @@ var _ PaymentSession = (*paymentSession)(nil)
 //
 // NOTE: Part of the PaymentSession interface.
 func (p *paymentSession) ReportVertexFailure(v route.Vertex) {
-	p.mc.reportVertexFailure(v)
+	p.sessionSource.MissionControl.ReportVertexFailure(v)
 }
 
 // ReportEdgeFailure adds a channel to the graph prune view. The time the
@@ -93,7 +93,9 @@ func (p *paymentSession) ReportVertexFailure(v route.Vertex) {
 func (p *paymentSession) ReportEdgeFailure(failedEdge edge,
 	minPenalizeAmt lnwire.MilliSatoshi) {
 
-	p.mc.reportEdgeFailure(failedEdge, minPenalizeAmt)
+	p.sessionSource.MissionControl.ReportEdgeFailure(
+		failedEdge, minPenalizeAmt,
+	)
 }
 
 // ReportEdgePolicyFailure handles a failure message that relates to a
@@ -169,21 +171,24 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
 	// Taking into account this prune view, we'll attempt to locate a path
 	// to our destination, respecting the recommendations from
 	// MissionControl.
+	ss := p.sessionSource
+
+	restrictions := &RestrictParams{
+		ProbabilitySource:     ss.MissionControl.GetEdgeProbability,
+		FeeLimit:              payment.FeeLimit,
+		OutgoingChannelID:     payment.OutgoingChannelID,
+		CltvLimit:             cltvLimit,
+		PaymentAttemptPenalty: ss.PaymentAttemptPenalty,
+		MinProbability:        ss.MinRouteProbability,
+	}
+
 	path, err := p.pathFinder(
 		&graphParams{
-			graph:           p.mc.graph,
+			graph:           ss.Graph,
 			additionalEdges: p.additionalEdges,
 			bandwidthHints:  p.bandwidthHints,
 		},
-		&RestrictParams{
-			ProbabilitySource:     p.mc.getEdgeProbability,
-			FeeLimit:              payment.FeeLimit,
-			OutgoingChannelID:     payment.OutgoingChannelID,
-			CltvLimit:             cltvLimit,
-			PaymentAttemptPenalty: p.mc.cfg.PaymentAttemptPenalty,
-			MinProbability:        p.mc.cfg.MinRouteProbability,
-		},
-		p.mc.selfNode.PubKeyBytes, payment.Target,
+		restrictions, ss.SelfNode.PubKeyBytes, payment.Target,
 		payment.Amount,
 	)
 	if err != nil {
@@ -192,7 +197,7 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
 
 	// With the next candidate path found, we'll attempt to turn this into
 	// a route by applying the time-lock and fee requirements.
-	sourceVertex := route.Vertex(p.mc.selfNode.PubKeyBytes)
+	sourceVertex := route.Vertex(ss.SelfNode.PubKeyBytes)
 	route, err := newRoute(
 		payment.Amount, sourceVertex, path, height, finalCltvDelta,
 	)
diff --git a/routing/payment_session_source.go b/routing/payment_session_source.go
new file mode 100644
index 000000000..697380267
--- /dev/null
+++ b/routing/payment_session_source.go
@@ -0,0 +1,150 @@
+package routing
+
+import (
+	"github.com/btcsuite/btcd/btcec"
+	"github.com/lightningnetwork/lnd/channeldb"
+	"github.com/lightningnetwork/lnd/lnwire"
+	"github.com/lightningnetwork/lnd/routing/route"
+	"github.com/lightningnetwork/lnd/zpay32"
+)
+
+// A compile time assertion to ensure MissionControl meets the
+// PaymentSessionSource interface.
+var _ PaymentSessionSource = (*SessionSource)(nil)
+
+// SessionSource defines a source for the router to retrieve new payment
+// sessions.
+type SessionSource struct {
+	// Graph is the channel graph that will be used to gather metrics from
+	// and also to carry out path finding queries.
+	Graph *channeldb.ChannelGraph
+
+	// QueryBandwidth is a method that allows querying the lower link layer
+	// to determine the up to date available bandwidth at a prospective link
+	// to be traversed. If the link isn't available, then a value of zero
+	// should be returned. Otherwise, the current up to date knowledge of
+	// the available bandwidth of the link should be returned.
+	QueryBandwidth func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi
+
+	// SelfNode is our own node.
+	SelfNode *channeldb.LightningNode
+
+	// MissionControl is a shared memory of sorts that executions of payment
+	// path finding use in order to remember which vertexes/edges were
+	// pruned from prior attempts. During payment execution, errors sent by
+	// nodes are mapped into a vertex or edge to be pruned. Each run will
+	// then take into account this set of pruned vertexes/edges to reduce
+	// route failure and pass on graph information gained to the next
+	// execution.
+	MissionControl MissionController
+
+	// PaymentAttemptPenalty is the virtual cost in path finding weight
+	// units of executing a payment attempt that fails. It is used to trade
+	// off potentially better routes against their probability of
+	// succeeding.
+	PaymentAttemptPenalty lnwire.MilliSatoshi
+
+	// MinProbability defines the minimum success probability of the
+	// returned route.
+	MinRouteProbability float64
+}
+
+// NewPaymentSession creates a new payment session backed by the latest prune
+// view from Mission Control. An optional set of routing hints can be provided
+// in order to populate additional edges to explore when finding a path to the
+// payment's destination.
+func (m *SessionSource) NewPaymentSession(routeHints [][]zpay32.HopHint,
+	target route.Vertex) (PaymentSession, error) {
+
+	edges := make(map[route.Vertex][]*channeldb.ChannelEdgePolicy)
+
+	// Traverse through all of the available hop hints and include them in
+	// our edges map, indexed by the public key of the channel's starting
+	// node.
+	for _, routeHint := range routeHints {
+		// If multiple hop hints are provided within a single route
+		// hint, we'll assume they must be chained together and sorted
+		// in forward order in order to reach the target successfully.
+		for i, hopHint := range routeHint {
+			// In order to determine the end node of this hint,
+			// we'll need to look at the next hint's start node. If
+			// we've reached the end of the hints list, we can
+			// assume we've reached the destination.
+			endNode := &channeldb.LightningNode{}
+			if i != len(routeHint)-1 {
+				endNode.AddPubKey(routeHint[i+1].NodeID)
+			} else {
+				targetPubKey, err := btcec.ParsePubKey(
+					target[:], btcec.S256(),
+				)
+				if err != nil {
+					return nil, err
+				}
+				endNode.AddPubKey(targetPubKey)
+			}
+
+			// Finally, create the channel edge from the hop hint
+			// and add it to list of edges corresponding to the node
+			// at the start of the channel.
+			edge := &channeldb.ChannelEdgePolicy{
+				Node:      endNode,
+				ChannelID: hopHint.ChannelID,
+				FeeBaseMSat: lnwire.MilliSatoshi(
+					hopHint.FeeBaseMSat,
+				),
+				FeeProportionalMillionths: lnwire.MilliSatoshi(
+					hopHint.FeeProportionalMillionths,
+				),
+				TimeLockDelta: hopHint.CLTVExpiryDelta,
+			}
+
+			v := route.NewVertex(hopHint.NodeID)
+			edges[v] = append(edges[v], edge)
+		}
+	}
+
+	// We'll also obtain a set of bandwidthHints from the lower layer for
+	// each of our outbound channels. This will allow the path finding to
+	// skip any links that aren't active or just don't have enough
+	// bandwidth to carry the payment.
+	sourceNode, err := m.Graph.SourceNode()
+	if err != nil {
+		return nil, err
+	}
+	bandwidthHints, err := generateBandwidthHints(
+		sourceNode, m.QueryBandwidth,
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	return &paymentSession{
+		additionalEdges:      edges,
+		bandwidthHints:       bandwidthHints,
+		errFailedPolicyChans: make(map[nodeChannel]struct{}),
+		sessionSource:        m,
+		pathFinder:           findPath,
+	}, nil
+}
+
+// NewPaymentSessionForRoute creates a new paymentSession instance that is just
+// used for failure reporting to missioncontrol.
+func (m *SessionSource) NewPaymentSessionForRoute(preBuiltRoute *route.Route) PaymentSession {
+	return &paymentSession{
+		errFailedPolicyChans: make(map[nodeChannel]struct{}),
+		sessionSource:        m,
+		preBuiltRoute:        preBuiltRoute,
+	}
+}
+
+// NewPaymentSessionEmpty creates a new paymentSession instance that is empty,
+// and will be exhausted immediately. Used for failure reporting to
+// missioncontrol for resumed payment we don't want to make more attempts for.
+func (m *SessionSource) NewPaymentSessionEmpty() PaymentSession {
+	return &paymentSession{
+		errFailedPolicyChans: make(map[nodeChannel]struct{}),
+		sessionSource:        m,
+		preBuiltRoute:        &route.Route{},
+		preBuiltRouteTried:   true,
+	}
+}
diff --git a/routing/payment_session_test.go b/routing/payment_session_test.go
index a5ee7889c..33070fbde 100644
--- a/routing/payment_session_test.go
+++ b/routing/payment_session_test.go
@@ -32,12 +32,16 @@ func TestRequestRoute(t *testing.T) {
 		return path, nil
 	}
 
-	session := &paymentSession{
-		mc: &MissionControl{
-			selfNode: &channeldb.LightningNode{},
-			cfg:      &MissionControlConfig{},
+	sessionSource := &SessionSource{
+		SelfNode: &channeldb.LightningNode{},
+		MissionControl: &MissionControl{
+			cfg: &MissionControlConfig{},
 		},
-		pathFinder: findPath,
+	}
+
+	session := &paymentSession{
+		sessionSource: sessionSource,
+		pathFinder:    findPath,
 	}
 
 	cltvLimit := uint32(30)
diff --git a/routing/router.go b/routing/router.go
index 87bb5c753..80cf4753e 100644
--- a/routing/router.go
+++ b/routing/router.go
@@ -171,6 +171,22 @@ type PaymentSessionSource interface {
 	NewPaymentSessionEmpty() PaymentSession
 }
 
+// MissionController is an interface that exposes failure reporting and
+// probability estimation.
+type MissionController interface {
+	// ReportEdgeFailure reports a channel level failure.
+	ReportEdgeFailure(failedEdge edge,
+		minPenalizeAmt lnwire.MilliSatoshi)
+
+	// ReportVertexFailure reports a node level failure.
+	ReportVertexFailure(v route.Vertex)
+
+	// GetEdgeProbability is expected to return the success probability of a
+	// payment from fromNode along edge.
+	GetEdgeProbability(fromNode route.Vertex, edge EdgeLocator,
+		amt lnwire.MilliSatoshi) float64
+}
+
 // FeeSchema is the set fee configuration for a Lightning Node on the network.
 // Using the coefficients described within the schema, the required fee to
 // forward outgoing payments can be derived.
@@ -234,7 +250,11 @@ type Config struct {
 	// Each run will then take into account this set of pruned
 	// vertexes/edges to reduce route failure and pass on graph information
 	// gained to the next execution.
-	MissionControl PaymentSessionSource
+	MissionControl MissionController
+
+	// SessionSource defines a source for the router to retrieve new payment
+	// sessions.
+	SessionSource PaymentSessionSource
 
 	// ChannelPruneExpiry is the duration used to determine if a channel
 	// should be pruned or not. If the delta between now and when the
@@ -544,7 +564,7 @@ func (r *ChannelRouter) Start() error {
 			//
 			// PayAttemptTime doesn't need to be set, as there is
 			// only a single attempt.
-			paySession := r.cfg.MissionControl.NewPaymentSessionEmpty()
+			paySession := r.cfg.SessionSource.NewPaymentSessionEmpty()
 
 			lPayment := &LightningPayment{
 				PaymentHash: payment.Info.PaymentHash,
@@ -1651,7 +1671,7 @@ func (r *ChannelRouter) preparePayment(payment *LightningPayment) (
 	// Before starting the HTLC routing attempt, we'll create a fresh
 	// payment session which will report our errors back to mission
 	// control.
-	paySession, err := r.cfg.MissionControl.NewPaymentSession(
+	paySession, err := r.cfg.SessionSource.NewPaymentSession(
 		payment.RouteHints, payment.Target,
 	)
 	if err != nil {
@@ -1682,7 +1702,7 @@ func (r *ChannelRouter) SendToRoute(hash lntypes.Hash, route *route.Route) (
 	lntypes.Preimage, error) {
 
 	// Create a payment session for just this route.
-	paySession := r.cfg.MissionControl.NewPaymentSessionForRoute(route)
+	paySession := r.cfg.SessionSource.NewPaymentSessionForRoute(route)
 
 	// Calculate amount paid to receiver.
 	amt := route.TotalAmount - route.TotalFees()
diff --git a/routing/router_test.go b/routing/router_test.go
index c50dba1c4..bc71cfc94 100644
--- a/routing/router_test.go
+++ b/routing/router_test.go
@@ -91,17 +91,23 @@ func createTestCtxFromGraphInstance(startingHeight uint32, graphInstance *testGr
 	}
 
 	mc := NewMissionControl(
-		graphInstance.graph, selfNode,
-		func(e *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi {
-			return lnwire.NewMSatFromSatoshis(e.Capacity)
-		},
 		&MissionControlConfig{
-			MinRouteProbability:   0.01,
-			PaymentAttemptPenalty: 100,
 			PenaltyHalfLife:       time.Hour,
 			AprioriHopProbability: 0.9,
 		},
 	)
+
+	sessionSource := &SessionSource{
+		Graph:    graphInstance.graph,
+		SelfNode: selfNode,
+		QueryBandwidth: func(e *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi {
+			return lnwire.NewMSatFromSatoshis(e.Capacity)
+		},
+		MinRouteProbability:   0.01,
+		PaymentAttemptPenalty: 100,
+		MissionControl:        mc,
+	}
+
 	router, err := New(Config{
 		Graph:              graphInstance.graph,
 		Chain:              chain,
@@ -109,6 +115,7 @@ func createTestCtxFromGraphInstance(startingHeight uint32, graphInstance *testGr
 		Payer:              &mockPaymentAttemptDispatcher{},
 		Control:            makeMockControlTower(),
 		MissionControl:     mc,
+		SessionSource:      sessionSource,
 		ChannelPruneExpiry: time.Hour * 24,
 		GraphPruneInterval: time.Hour * 2,
 		QueryBandwidth: func(e *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi {
@@ -2940,7 +2947,7 @@ func TestRouterPaymentStateMachine(t *testing.T) {
 			Chain:              chain,
 			ChainView:          chainView,
 			Control:            control,
-			MissionControl:     &mockPaymentSessionSource{},
+			SessionSource:      &mockPaymentSessionSource{},
 			Payer:              payer,
 			ChannelPruneExpiry: time.Hour * 24,
 			GraphPruneInterval: time.Hour * 2,
@@ -3004,10 +3011,12 @@ func TestRouterPaymentStateMachine(t *testing.T) {
 
 		copy(preImage[:], bytes.Repeat([]byte{9}, 32))
 
-		router.cfg.MissionControl = &mockPaymentSessionSource{
+		router.cfg.SessionSource = &mockPaymentSessionSource{
 			routes: test.routes,
 		}
 
+		router.cfg.MissionControl = &mockMissionControl{}
+
 		// Send the payment. Since this is new payment hash, the
 		// information should be registered with the ControlTower.
 		paymentResult := make(chan error)
diff --git a/server.go b/server.go
index 88204d812..64b376663 100644
--- a/server.go
+++ b/server.go
@@ -652,11 +652,29 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
 	//
 	// TODO(joostjager): When we are further in the process of moving to sub
 	// servers, the mission control instance itself can be moved there too.
+	routingConfig := routerrpc.GetRoutingConfig(cfg.SubRPCServers.RouterRPC)
+
 	s.missionControl = routing.NewMissionControl(
-		chanGraph, selfNode, queryBandwidth,
-		routerrpc.GetMissionControlConfig(cfg.SubRPCServers.RouterRPC),
+		&routing.MissionControlConfig{
+			AprioriHopProbability: routingConfig.AprioriHopProbability,
+			PenaltyHalfLife:       routingConfig.PenaltyHalfLife,
+		},
 	)
 
+	srvrLog.Debugf("Instantiating payment session source with config: "+
+		"PaymentAttemptPenalty=%v, MinRouteProbability=%v",
+		int64(routingConfig.PaymentAttemptPenalty.ToSatoshis()),
+		routingConfig.MinRouteProbability)
+
+	paymentSessionSource := &routing.SessionSource{
+		Graph:                 chanGraph,
+		MissionControl:        s.missionControl,
+		QueryBandwidth:        queryBandwidth,
+		SelfNode:              selfNode,
+		PaymentAttemptPenalty: routingConfig.PaymentAttemptPenalty,
+		MinRouteProbability:   routingConfig.MinRouteProbability,
+	}
+
 	paymentControl := channeldb.NewPaymentControl(chanDB)
 
 	s.controlTower = routing.NewControlTower(paymentControl)
@@ -668,6 +686,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
 		Payer:              s.htlcSwitch,
 		Control:            s.controlTower,
 		MissionControl:     s.missionControl,
+		SessionSource:      paymentSessionSource,
 		ChannelPruneExpiry: routing.DefaultChannelPruneExpiry,
 		GraphPruneInterval: time.Duration(time.Hour),
 		QueryBandwidth:     queryBandwidth,