Merge pull request #1203 from Roasbeef/router-view-switch

routing+server: add new QueryBandwidth method to reduce outbound fail…
This commit is contained in:
Olaoluwa Osuntokun authored and GitHub committed 2018-05-14 17:13:58 -07:00
commit 9f6af3a2c1
6 files changed
+162 -27

No files matched your search

+62 -4
View File
@@ -4,6 +4,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/coreos/bbolt"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
@@ -57,6 +58,8 @@ type missionControl struct {
selfNode *channeldb.LightningNode selfNode *channeldb.LightningNode
queryBandwidth func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi
sync.Mutex sync.Mutex
// TODO(roasbeef): further counters, if vertex continually unavailable, // TODO(roasbeef): further counters, if vertex continually unavailable,
@@ -68,13 +71,15 @@ type missionControl struct {
// newMissionControl returns a new instance of missionControl. // newMissionControl returns a new instance of missionControl.
// //
// TODO(roasbeef): persist memory // TODO(roasbeef): persist memory
func newMissionControl(g *channeldb.ChannelGraph, func newMissionControl(
selfNode *channeldb.LightningNode) *missionControl { g *channeldb.ChannelGraph, selfNode *channeldb.LightningNode,
qb func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi) *missionControl {
return &missionControl{ return &missionControl{
failedEdges: make(map[uint64]time.Time), failedEdges: make(map[uint64]time.Time),
failedVertexes: make(map[Vertex]time.Time), failedVertexes: make(map[Vertex]time.Time),
selfNode: selfNode, selfNode: selfNode,
queryBandwidth: qb,
graph: g, graph: g,
} }
} }
@@ -157,6 +162,8 @@ type paymentSession struct {
additionalEdges map[Vertex][]*channeldb.ChannelEdgePolicy additionalEdges map[Vertex][]*channeldb.ChannelEdgePolicy
bandwidthHints map[uint64]lnwire.MilliSatoshi
mc *missionControl mc *missionControl
} }
@@ -165,7 +172,7 @@ type paymentSession struct {
// in order to populate additional edges to explore when finding a path to the // in order to populate additional edges to explore when finding a path to the
// payment's destination. // payment's destination.
func (m *missionControl) NewPaymentSession(routeHints [][]HopHint, func (m *missionControl) NewPaymentSession(routeHints [][]HopHint,
target *btcec.PublicKey) *paymentSession { target *btcec.PublicKey) (*paymentSession, error) {
viewSnapshot := m.GraphPruneView() viewSnapshot := m.GraphPruneView()
@@ -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{ return &paymentSession{
pruneViewSnapshot: viewSnapshot, pruneViewSnapshot: viewSnapshot,
additionalEdges: edges, additionalEdges: edges,
bandwidthHints: bandwidthHints,
mc: m, 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 // ReportVertexFailure adds a vertex to the graph prune view after a client
@@ -285,7 +343,7 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
path, err := findPath( path, err := findPath(
nil, p.mc.graph, p.additionalEdges, p.mc.selfNode, nil, p.mc.graph, p.additionalEdges, p.mc.selfNode,
payment.Target, pruneView.vertexes, pruneView.edges, payment.Target, pruneView.vertexes, pruneView.edges,
payment.Amount, payment.Amount, p.bandwidthHints,
) )
if err != nil { if err != nil {
return nil, err return nil, err
+24 -8
View File
@@ -458,7 +458,8 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
additionalEdges map[Vertex][]*channeldb.ChannelEdgePolicy, additionalEdges map[Vertex][]*channeldb.ChannelEdgePolicy,
sourceNode *channeldb.LightningNode, target *btcec.PublicKey, sourceNode *channeldb.LightningNode, target *btcec.PublicKey,
ignoredNodes map[Vertex]struct{}, ignoredEdges map[uint64]struct{}, 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 var err error
if tx == nil { if tx == nil {
@@ -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 // processEdge is a helper closure that will be used to make sure edges
// satisfy our specific requirements. // satisfy our specific requirements.
processEdge := func(edge *channeldb.ChannelEdgePolicy, processEdge := func(edge *channeldb.ChannelEdgePolicy,
capacity btcutil.Amount, pivot Vertex) { bandwidth lnwire.MilliSatoshi, pivot Vertex) {
v := Vertex(edge.Node.PubKeyBytes) v := Vertex(edge.Node.PubKeyBytes)
@@ -547,7 +548,7 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// this edge. We'll also shave off irrelevant edges by adding // this edge. We'll also shave off irrelevant edges by adding
// the sufficient capacity of an edge and clearing their // the sufficient capacity of an edge and clearing their
// min-htlc amount to our relaxation condition. // 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 { amt >= edge.MinHTLC && edge.TimeLockDelta != 0 {
distance[v] = nodeWithDist{ distance[v] = nodeWithDist{
@@ -558,7 +559,7 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
prev[v] = edgeWithPrev{ prev[v] = edgeWithPrev{
edge: &ChannelHop{ edge: &ChannelHop{
ChannelEdgePolicy: edge, ChannelEdgePolicy: edge,
Capacity: capacity, Capacity: bandwidth.ToSatoshis(),
}, },
prevNode: pivot, prevNode: pivot,
} }
@@ -606,7 +607,20 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
edgeInfo *channeldb.ChannelEdgeInfo, edgeInfo *channeldb.ChannelEdgeInfo,
outEdge, _ *channeldb.ChannelEdgePolicy) error { 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? // TODO(roasbeef): return min HTLC as error in end?
@@ -622,7 +636,7 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// routing hint due to having enough capacity for the payment // routing hint due to having enough capacity for the payment
// and use the payment amount as its capacity. // and use the payment amount as its capacity.
for _, edge := range additionalEdges[bestNode.PubKeyBytes] { for _, edge := range additionalEdges[bestNode.PubKeyBytes] {
processEdge(edge, amt.ToSatoshis(), pivot) processEdge(edge, amt, pivot)
} }
} }
@@ -681,7 +695,8 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// algorithm in a block box manner. // algorithm in a block box manner.
func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph, func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
source *channeldb.LightningNode, target *btcec.PublicKey, 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{}) ignoredEdges := make(map[uint64]struct{})
ignoredVertexes := make(map[Vertex]struct{}) ignoredVertexes := make(map[Vertex]struct{})
@@ -698,7 +713,7 @@ func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// satoshis along the path before fees are calculated. // satoshis along the path before fees are calculated.
startingPath, err := findPath( startingPath, err := findPath(
tx, graph, nil, source, target, ignoredVertexes, ignoredEdges, tx, graph, nil, source, target, ignoredVertexes, ignoredEdges,
amt, amt, bandwidthHints,
) )
if err != nil { if err != nil {
log.Errorf("Unable to find path: %v", err) log.Errorf("Unable to find path: %v", err)
@@ -773,6 +788,7 @@ func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
spurPath, err := findPath( spurPath, err := findPath(
tx, graph, nil, spurNode, target, tx, graph, nil, spurNode, target,
ignoredVertexes, ignoredEdges, amt, ignoredVertexes, ignoredEdges, amt,
bandwidthHints,
) )
// If we weren't able to find a path, we'll continue to // If we weren't able to find a path, we'll continue to
+11 -10
View File
@@ -313,7 +313,7 @@ func TestBasicGraphPathFinding(t *testing.T) {
target := aliases["sophon"] target := aliases["sophon"]
path, err := findPath( path, err := findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes, nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, paymentAmt, ignoredEdges, paymentAmt, nil,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find path: %v", err) t.Fatalf("unable to find path: %v", err)
@@ -455,7 +455,7 @@ func TestBasicGraphPathFinding(t *testing.T) {
target = aliases["luoji"] target = aliases["luoji"]
path, err = findPath( path, err = findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes, nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, paymentAmt, ignoredEdges, paymentAmt, nil,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find route: %v", err) t.Fatalf("unable to find route: %v", err)
@@ -541,7 +541,7 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
// We should now be able to find a path from roasbeef to doge. // We should now be able to find a path from roasbeef to doge.
path, err := findPath( path, err := findPath(
nil, graph, additionalEdges, sourceNode, dogePubKey, nil, nil, nil, graph, additionalEdges, sourceNode, dogePubKey, nil, nil,
paymentAmt, paymentAmt, nil,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find private path to doge: %v", err) t.Fatalf("unable to find private path to doge: %v", err)
@@ -578,6 +578,7 @@ func TestKShortestPathFinding(t *testing.T) {
target := aliases["luoji"] target := aliases["luoji"]
paths, err := findPaths( paths, err := findPaths(
nil, graph, sourceNode, target, paymentAmt, 100, nil, graph, sourceNode, target, paymentAmt, 100,
nil,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find paths between roasbeef and "+ t.Fatalf("unable to find paths between roasbeef and "+
@@ -629,7 +630,7 @@ func TestNewRoutePathTooLong(t *testing.T) {
target := aliases["ursula"] target := aliases["ursula"]
_, err = findPath( _, err = findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes, nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, paymentAmt, ignoredEdges, paymentAmt, nil,
) )
if err != nil { if err != nil {
t.Fatalf("path should have been found") t.Fatalf("path should have been found")
@@ -640,7 +641,7 @@ func TestNewRoutePathTooLong(t *testing.T) {
target = aliases["vincent"] target = aliases["vincent"]
path, err := findPath( path, err := findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes, nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, paymentAmt, ignoredEdges, paymentAmt, nil,
) )
if err == nil { if err == nil {
t.Fatalf("should not have been able to find path, supposed to be "+ t.Fatalf("should not have been able to find path, supposed to be "+
@@ -682,7 +683,7 @@ func TestPathNotAvailable(t *testing.T) {
_, err = findPath( _, err = findPath(
nil, graph, nil, sourceNode, unknownNode, ignoredVertexes, nil, graph, nil, sourceNode, unknownNode, ignoredVertexes,
ignoredEdges, 100, ignoredEdges, 100, nil,
) )
if !IsError(err, ErrNoPathFound) { if !IsError(err, ErrNoPathFound) {
t.Fatalf("path shouldn't have been found: %v", err) t.Fatalf("path shouldn't have been found: %v", err)
@@ -718,7 +719,7 @@ func TestPathInsufficientCapacity(t *testing.T) {
payAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) payAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
_, err = findPath( _, err = findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes, nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, payAmt, ignoredEdges, payAmt, nil,
) )
if !IsError(err, ErrNoPathFound) { if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err) t.Fatalf("graph shouldn't be able to support payment: %v", err)
@@ -748,7 +749,7 @@ func TestRouteFailMinHTLC(t *testing.T) {
payAmt := lnwire.MilliSatoshi(10) payAmt := lnwire.MilliSatoshi(10)
_, err = findPath( _, err = findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes, nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, payAmt, ignoredEdges, payAmt, nil,
) )
if !IsError(err, ErrNoPathFound) { if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err) t.Fatalf("graph shouldn't be able to support payment: %v", err)
@@ -778,7 +779,7 @@ func TestRouteFailDisabledEdge(t *testing.T) {
payAmt := lnwire.NewMSatFromSatoshis(10000) payAmt := lnwire.NewMSatFromSatoshis(10000)
_, err = findPath( _, err = findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes, nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, payAmt, ignoredEdges, payAmt, nil,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find path: %v", err) t.Fatalf("unable to find path: %v", err)
@@ -799,7 +800,7 @@ func TestRouteFailDisabledEdge(t *testing.T) {
// failure as it is no longer eligible. // failure as it is no longer eligible.
_, err = findPath( _, err = findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes, nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, payAmt, ignoredEdges, payAmt, nil,
) )
if !IsError(err, ErrNoPathFound) { if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err) t.Fatalf("graph shouldn't be able to support payment: %v", err)
+32 -4
View File
@@ -163,6 +163,14 @@ type Config struct {
// GraphPruneInterval is used as an interval to determine how often we // GraphPruneInterval is used as an interval to determine how often we
// should examine the channel graph to garbage collect zombie channels. // should examine the channel graph to garbage collect zombie channels.
GraphPruneInterval time.Duration 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 // routeTuple is an entry within the ChannelRouter's route cache. We cache
@@ -283,18 +291,23 @@ func New(cfg Config) (*ChannelRouter, error) {
return nil, err return nil, err
} }
return &ChannelRouter{ r := &ChannelRouter{
cfg: &cfg, cfg: &cfg,
networkUpdates: make(chan *routingMsg), networkUpdates: make(chan *routingMsg),
topologyClients: make(map[uint64]*topologyClient), topologyClients: make(map[uint64]*topologyClient),
ntfnClientUpdates: make(chan *topologyClientUpdate), ntfnClientUpdates: make(chan *topologyClientUpdate),
missionControl: newMissionControl(cfg.Graph, selfNode),
channelEdgeMtx: multimutex.NewMutex(), channelEdgeMtx: multimutex.NewMutex(),
selfNode: selfNode, selfNode: selfNode,
routeCache: make(map[routeTuple][]*Route), routeCache: make(map[routeTuple][]*Route),
rejectCache: make(map[uint64]struct{}), rejectCache: make(map[uint64]struct{}),
quit: make(chan 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 // Start launches all the goroutines the ChannelRouter requires to carry out
@@ -1352,6 +1365,16 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
return nil, err 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) tx, err := r.cfg.Graph.Database().Begin(false)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
@@ -1363,6 +1386,7 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
// our source to the destination. // our source to the destination.
shortestPaths, err := findPaths( shortestPaths, err := findPaths(
tx, r.cfg.Graph, r.selfNode, target, amt, numPaths, tx, r.cfg.Graph, r.selfNode, target, amt, numPaths,
bandwidthHints,
) )
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
@@ -1567,9 +1591,13 @@ func (r *ChannelRouter) SendPayment(payment *LightningPayment) ([32]byte, *Route
// Before starting the HTLC routing attempt, we'll create a fresh // Before starting the HTLC routing attempt, we'll create a fresh
// payment session which will report our errors back to mission // payment session which will report our errors back to mission
// control. // control.
paySession := r.missionControl.NewPaymentSession( paySession, err := r.missionControl.NewPaymentSession(
payment.RouteHints, payment.Target, 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 // We'll continue until either our payment succeeds, or we encounter a
// critical error during path finding. // critical error during path finding.
+4 -1
View File
@@ -128,6 +128,9 @@ func createTestCtx(startingHeight uint32, testGraph ...string) (*testCtx, func()
}, },
ChannelPruneExpiry: time.Hour * 24, ChannelPruneExpiry: time.Hour * 24,
GraphPruneInterval: time.Hour * 2, GraphPruneInterval: time.Hour * 2,
QueryBandwidth: func(e *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi {
return lnwire.NewMSatFromSatoshis(e.Capacity)
},
}) })
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("unable to create router %v", err) return nil, nil, fmt.Errorf("unable to create router %v", err)
@@ -1617,7 +1620,7 @@ func TestFindPathFeeWeighting(t *testing.T) {
// path even though the direct path has a higher potential time lock. // path even though the direct path has a higher potential time lock.
path, err := findPath( path, err := findPath(
nil, ctx.graph, nil, sourceNode, target, ignoreVertex, nil, ctx.graph, nil, sourceNode, target, ignoreVertex,
ignoreEdge, amt, ignoreEdge, amt, nil,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find path: %v", err) t.Fatalf("unable to find path: %v", err)
+29
View File
@@ -351,6 +351,35 @@ func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl,
}, },
ChannelPruneExpiry: time.Duration(time.Hour * 24 * 14), ChannelPruneExpiry: time.Duration(time.Hour * 24 * 14),
GraphPruneInterval: time.Duration(time.Hour), GraphPruneInterval: time.Duration(time.Hour),
QueryBandwidth: func(edge *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi {
// If we aren't on either side of this edge, then we'll
// just thread through the capacity of the edge as we
// know it.
if !bytes.Equal(edge.NodeKey1Bytes[:], selfNode.PubKeyBytes[:]) &&
!bytes.Equal(edge.NodeKey2Bytes[:], selfNode.PubKeyBytes[:]) {
return lnwire.NewMSatFromSatoshis(edge.Capacity)
}
cid := lnwire.NewChanIDFromOutPoint(&edge.ChannelPoint)
link, err := s.htlcSwitch.GetLink(cid)
if err != nil {
// If the link isn't online, then we'll report
// that it has zero bandwidth to the router.
return 0
}
// If the link is found within the switch, but it isn't
// yet eligible to forward any HTLCs, then we'll treat
// it as if it isn't online in the first place.
if !link.EligibleToForward() {
return 0
}
// Otherwise, we'll return the current best estimate
// for the available bandwidth for the link.
return link.Bandwidth()
},
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("can't create router: %v", err) return nil, fmt.Errorf("can't create router: %v", err)