Skip to content

Commit

Permalink
Merge f494433 into c96d07c
Browse files Browse the repository at this point in the history
  • Loading branch information
Roasbeef committed May 14, 2018
2 parents c96d07c + f494433 commit 0753868
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 27 deletions.
66 changes: 62 additions & 4 deletions routing/missioncontrol.go
Expand Up @@ -4,6 +4,7 @@ import (
"sync"
"time"

"github.com/coreos/bbolt"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/btcec"
Expand Down Expand Up @@ -57,6 +58,8 @@ type missionControl struct {

selfNode *channeldb.LightningNode

queryBandwidth func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi

sync.Mutex

// TODO(roasbeef): further counters, if vertex continually unavailable,
Expand All @@ -68,13 +71,15 @@ type missionControl struct {
// newMissionControl returns a new instance of missionControl.
//
// TODO(roasbeef): persist memory
func newMissionControl(g *channeldb.ChannelGraph,
selfNode *channeldb.LightningNode) *missionControl {
func newMissionControl(
g *channeldb.ChannelGraph, selfNode *channeldb.LightningNode,
qb func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi) *missionControl {

return &missionControl{
failedEdges: make(map[uint64]time.Time),
failedVertexes: make(map[Vertex]time.Time),
selfNode: selfNode,
queryBandwidth: qb,
graph: g,
}
}
Expand Down Expand Up @@ -157,6 +162,8 @@ type paymentSession struct {

additionalEdges map[Vertex][]*channeldb.ChannelEdgePolicy

bandwidthHints map[uint64]lnwire.MilliSatoshi

mc *missionControl
}

Expand All @@ -165,7 +172,7 @@ type paymentSession struct {
// in order to populate additional edges to explore when finding a path to the
// payment's destination.
func (m *missionControl) NewPaymentSession(routeHints [][]HopHint,
target *btcec.PublicKey) *paymentSession {
target *btcec.PublicKey) (*paymentSession, error) {

viewSnapshot := m.GraphPruneView()

Expand Down Expand Up @@ -210,11 +217,62 @@ func (m *missionControl) NewPaymentSession(routeHints [][]HopHint,
}
}

// 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{
pruneViewSnapshot: viewSnapshot,
additionalEdges: edges,
bandwidthHints: bandwidthHints,
mc: m,
}, nil
}

// 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
// bandwidth hints for the edges we directly have open ourselves. Obtaining
// these hints allows us to reduce the number of extraneous attempts as we can
// skip channels that are inactive, or just don't have enough bandwidth to
// carry the payment.
func generateBandwidthHints(sourceNode *channeldb.LightningNode,
queryBandwidth func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi) (map[uint64]lnwire.MilliSatoshi, error) {

// First, we'll collect the set of outbound edges from the target
// source node.
var localChans []*channeldb.ChannelEdgeInfo
err := sourceNode.ForEachChannel(nil, func(tx *bolt.Tx,
edgeInfo *channeldb.ChannelEdgeInfo,
_, _ *channeldb.ChannelEdgePolicy) error {

localChans = append(localChans, edgeInfo)
return nil
})
if err != nil {
return nil, err
}

// Now that we have all of our outbound edges, we'll populate the set
// of bandwidth hints, querying the lower switch layer for the most up
// to date values.
bandwidthHints := make(map[uint64]lnwire.MilliSatoshi)
for _, localChan := range localChans {
bandwidthHints[localChan.ChannelID] = queryBandwidth(localChan)
}

return bandwidthHints, nil
}

// ReportVertexFailure adds a vertex to the graph prune view after a client
Expand Down Expand Up @@ -285,7 +343,7 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
path, err := findPath(
nil, p.mc.graph, p.additionalEdges, p.mc.selfNode,
payment.Target, pruneView.vertexes, pruneView.edges,
payment.Amount,
payment.Amount, p.bandwidthHints,
)
if err != nil {
return nil, err
Expand Down
32 changes: 24 additions & 8 deletions routing/pathfind.go
Expand Up @@ -458,7 +458,8 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
additionalEdges map[Vertex][]*channeldb.ChannelEdgePolicy,
sourceNode *channeldb.LightningNode, target *btcec.PublicKey,
ignoredNodes map[Vertex]struct{}, ignoredEdges map[uint64]struct{},
amt lnwire.MilliSatoshi) ([]*ChannelHop, error) {
amt lnwire.MilliSatoshi,
bandwidthHints map[uint64]lnwire.MilliSatoshi) ([]*ChannelHop, error) {

var err error
if tx == nil {
Expand Down Expand Up @@ -516,7 +517,7 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// processEdge is a helper closure that will be used to make sure edges
// satisfy our specific requirements.
processEdge := func(edge *channeldb.ChannelEdgePolicy,
capacity btcutil.Amount, pivot Vertex) {
bandwidth lnwire.MilliSatoshi, pivot Vertex) {

v := Vertex(edge.Node.PubKeyBytes)

Expand Down Expand Up @@ -547,7 +548,7 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// this edge. We'll also shave off irrelevant edges by adding
// the sufficient capacity of an edge and clearing their
// min-htlc amount to our relaxation condition.
if tempDist < distance[v].dist && capacity >= amt.ToSatoshis() &&
if tempDist < distance[v].dist && bandwidth >= amt &&
amt >= edge.MinHTLC && edge.TimeLockDelta != 0 {

distance[v] = nodeWithDist{
Expand All @@ -558,7 +559,7 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
prev[v] = edgeWithPrev{
edge: &ChannelHop{
ChannelEdgePolicy: edge,
Capacity: capacity,
Capacity: bandwidth.ToSatoshis(),
},
prevNode: pivot,
}
Expand Down Expand Up @@ -606,7 +607,20 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
edgeInfo *channeldb.ChannelEdgeInfo,
outEdge, _ *channeldb.ChannelEdgePolicy) error {

processEdge(outEdge, edgeInfo.Capacity, pivot)
// We'll query the lower layer to see if we can obtain
// any more up to date information concerning the
// bandwidth of this edge.
edgeBandwidth, ok := bandwidthHints[edgeInfo.ChannelID]
if !ok {
// If we don't have a hint for this edge, then
// we'll just use the known Capacity as the
// available bandwidth.
edgeBandwidth = lnwire.NewMSatFromSatoshis(
edgeInfo.Capacity,
)
}

processEdge(outEdge, edgeBandwidth, pivot)

// TODO(roasbeef): return min HTLC as error in end?

Expand All @@ -622,7 +636,7 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// routing hint due to having enough capacity for the payment
// and use the payment amount as its capacity.
for _, edge := range additionalEdges[bestNode.PubKeyBytes] {
processEdge(edge, amt.ToSatoshis(), pivot)
processEdge(edge, amt, pivot)
}
}

Expand Down Expand Up @@ -681,7 +695,8 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// algorithm in a block box manner.
func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
source *channeldb.LightningNode, target *btcec.PublicKey,
amt lnwire.MilliSatoshi, numPaths uint32) ([][]*ChannelHop, error) {
amt lnwire.MilliSatoshi, numPaths uint32,
bandwidthHints map[uint64]lnwire.MilliSatoshi) ([][]*ChannelHop, error) {

ignoredEdges := make(map[uint64]struct{})
ignoredVertexes := make(map[Vertex]struct{})
Expand All @@ -698,7 +713,7 @@ func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// satoshis along the path before fees are calculated.
startingPath, err := findPath(
tx, graph, nil, source, target, ignoredVertexes, ignoredEdges,
amt,
amt, bandwidthHints,
)
if err != nil {
log.Errorf("Unable to find path: %v", err)
Expand Down Expand Up @@ -773,6 +788,7 @@ func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
spurPath, err := findPath(
tx, graph, nil, spurNode, target,
ignoredVertexes, ignoredEdges, amt,
bandwidthHints,
)

// If we weren't able to find a path, we'll continue to
Expand Down
21 changes: 11 additions & 10 deletions routing/pathfind_test.go
Expand Up @@ -313,7 +313,7 @@ func TestBasicGraphPathFinding(t *testing.T) {
target := aliases["sophon"]
path, err := findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, paymentAmt,
ignoredEdges, paymentAmt, nil,
)
if err != nil {
t.Fatalf("unable to find path: %v", err)
Expand Down Expand Up @@ -455,7 +455,7 @@ func TestBasicGraphPathFinding(t *testing.T) {
target = aliases["luoji"]
path, err = findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, paymentAmt,
ignoredEdges, paymentAmt, nil,
)
if err != nil {
t.Fatalf("unable to find route: %v", err)
Expand Down Expand Up @@ -541,7 +541,7 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
// We should now be able to find a path from roasbeef to doge.
path, err := findPath(
nil, graph, additionalEdges, sourceNode, dogePubKey, nil, nil,
paymentAmt,
paymentAmt, nil,
)
if err != nil {
t.Fatalf("unable to find private path to doge: %v", err)
Expand Down Expand Up @@ -578,6 +578,7 @@ func TestKShortestPathFinding(t *testing.T) {
target := aliases["luoji"]
paths, err := findPaths(
nil, graph, sourceNode, target, paymentAmt, 100,
nil,
)
if err != nil {
t.Fatalf("unable to find paths between roasbeef and "+
Expand Down Expand Up @@ -629,7 +630,7 @@ func TestNewRoutePathTooLong(t *testing.T) {
target := aliases["ursula"]
_, err = findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, paymentAmt,
ignoredEdges, paymentAmt, nil,
)
if err != nil {
t.Fatalf("path should have been found")
Expand All @@ -640,7 +641,7 @@ func TestNewRoutePathTooLong(t *testing.T) {
target = aliases["vincent"]
path, err := findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, paymentAmt,
ignoredEdges, paymentAmt, nil,
)
if err == nil {
t.Fatalf("should not have been able to find path, supposed to be "+
Expand Down Expand Up @@ -682,7 +683,7 @@ func TestPathNotAvailable(t *testing.T) {

_, err = findPath(
nil, graph, nil, sourceNode, unknownNode, ignoredVertexes,
ignoredEdges, 100,
ignoredEdges, 100, nil,
)
if !IsError(err, ErrNoPathFound) {
t.Fatalf("path shouldn't have been found: %v", err)
Expand Down Expand Up @@ -718,7 +719,7 @@ func TestPathInsufficientCapacity(t *testing.T) {
payAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
_, err = findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, payAmt,
ignoredEdges, payAmt, nil,
)
if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err)
Expand Down Expand Up @@ -748,7 +749,7 @@ func TestRouteFailMinHTLC(t *testing.T) {
payAmt := lnwire.MilliSatoshi(10)
_, err = findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, payAmt,
ignoredEdges, payAmt, nil,
)
if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err)
Expand Down Expand Up @@ -778,7 +779,7 @@ func TestRouteFailDisabledEdge(t *testing.T) {
payAmt := lnwire.NewMSatFromSatoshis(10000)
_, err = findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, payAmt,
ignoredEdges, payAmt, nil,
)
if err != nil {
t.Fatalf("unable to find path: %v", err)
Expand All @@ -799,7 +800,7 @@ func TestRouteFailDisabledEdge(t *testing.T) {
// failure as it is no longer eligible.
_, err = findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, payAmt,
ignoredEdges, payAmt, nil,
)
if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err)
Expand Down
36 changes: 32 additions & 4 deletions routing/router.go
Expand Up @@ -163,6 +163,14 @@ type Config struct {
// GraphPruneInterval is used as an interval to determine how often we
// should examine the channel graph to garbage collect zombie channels.
GraphPruneInterval time.Duration

// QueryBandwidth is a method that allows the router to query 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(edge *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi
}

// routeTuple is an entry within the ChannelRouter's route cache. We cache
Expand Down Expand Up @@ -283,18 +291,23 @@ func New(cfg Config) (*ChannelRouter, error) {
return nil, err
}

return &ChannelRouter{
r := &ChannelRouter{
cfg: &cfg,
networkUpdates: make(chan *routingMsg),
topologyClients: make(map[uint64]*topologyClient),
ntfnClientUpdates: make(chan *topologyClientUpdate),
missionControl: newMissionControl(cfg.Graph, selfNode),
channelEdgeMtx: multimutex.NewMutex(),
selfNode: selfNode,
routeCache: make(map[routeTuple][]*Route),
rejectCache: make(map[uint64]struct{}),
quit: make(chan struct{}),
}, nil
}

r.missionControl = newMissionControl(
cfg.Graph, selfNode, cfg.QueryBandwidth,
)

return r, nil
}

// Start launches all the goroutines the ChannelRouter requires to carry out
Expand Down Expand Up @@ -1352,6 +1365,16 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
return nil, err
}

// Before we open the db transaction below, we'll attempt to obtain a
// set of bandwidth hints that can help us eliminate certain routes
// early on in the path finding process.
bandwidthHints, err := generateBandwidthHints(
r.selfNode, r.cfg.QueryBandwidth,
)
if err != nil {
return nil, err
}

tx, err := r.cfg.Graph.Database().Begin(false)
if err != nil {
tx.Rollback()
Expand All @@ -1363,6 +1386,7 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
// our source to the destination.
shortestPaths, err := findPaths(
tx, r.cfg.Graph, r.selfNode, target, amt, numPaths,
bandwidthHints,
)
if err != nil {
tx.Rollback()
Expand Down Expand Up @@ -1567,9 +1591,13 @@ func (r *ChannelRouter) SendPayment(payment *LightningPayment) ([32]byte, *Route
// Before starting the HTLC routing attempt, we'll create a fresh
// payment session which will report our errors back to mission
// control.
paySession := r.missionControl.NewPaymentSession(
paySession, err := r.missionControl.NewPaymentSession(
payment.RouteHints, payment.Target,
)
if err != nil {
return preImage, nil, fmt.Errorf("unable to create payment "+
"session: %v", err)
}

// We'll continue until either our payment succeeds, or we encounter a
// critical error during path finding.
Expand Down

0 comments on commit 0753868

Please sign in to comment.