routing+routerrpc: isolate payment session source from mission control
This commit is contained in:
parent
cf3dd3fb94
commit
37e2751695
28
lnrpc/routerrpc/config.go
Normal file
28
lnrpc/routerrpc/config.go
Normal file
@ -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
|
||||
}
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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,129 +121,18 @@ 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,
|
||||
}
|
||||
}
|
||||
|
||||
// generateBandwidthHints is a helper function that's utilized the main
|
||||
// findPath function in order to obtain hints from the lower layer w.r.t to the
|
||||
// available bandwidth of edges on the network. Currently, we'll only obtain
|
||||
@ -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",
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
path, err := p.pathFinder(
|
||||
&graphParams{
|
||||
graph: p.mc.graph,
|
||||
additionalEdges: p.additionalEdges,
|
||||
bandwidthHints: p.bandwidthHints,
|
||||
},
|
||||
&RestrictParams{
|
||||
ProbabilitySource: p.mc.getEdgeProbability,
|
||||
ss := p.sessionSource
|
||||
|
||||
restrictions := &RestrictParams{
|
||||
ProbabilitySource: ss.MissionControl.GetEdgeProbability,
|
||||
FeeLimit: payment.FeeLimit,
|
||||
OutgoingChannelID: payment.OutgoingChannelID,
|
||||
CltvLimit: cltvLimit,
|
||||
PaymentAttemptPenalty: p.mc.cfg.PaymentAttemptPenalty,
|
||||
MinProbability: p.mc.cfg.MinRouteProbability,
|
||||
PaymentAttemptPenalty: ss.PaymentAttemptPenalty,
|
||||
MinProbability: ss.MinRouteProbability,
|
||||
}
|
||||
|
||||
path, err := p.pathFinder(
|
||||
&graphParams{
|
||||
graph: ss.Graph,
|
||||
additionalEdges: p.additionalEdges,
|
||||
bandwidthHints: p.bandwidthHints,
|
||||
},
|
||||
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,
|
||||
)
|
||||
|
150
routing/payment_session_source.go
Normal file
150
routing/payment_session_source.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
@ -32,11 +32,15 @@ func TestRequestRoute(t *testing.T) {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
session := &paymentSession{
|
||||
mc: &MissionControl{
|
||||
selfNode: &channeldb.LightningNode{},
|
||||
sessionSource := &SessionSource{
|
||||
SelfNode: &channeldb.LightningNode{},
|
||||
MissionControl: &MissionControl{
|
||||
cfg: &MissionControlConfig{},
|
||||
},
|
||||
}
|
||||
|
||||
session := &paymentSession{
|
||||
sessionSource: sessionSource,
|
||||
pathFinder: findPath,
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
23
server.go
23
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,
|
||||
|
Loading…
Reference in New Issue
Block a user