channeldb+routing: add in-memory graph

Adds an in-memory channel graph cache for faster pathfinding.

Original PoC by: Joost Jager
Co-Authored by: Oliver Gugger
This commit is contained in:
Joost Jager authored and Oliver Gugger committed 2021-09-29 17:00:04 +02:00
1 parent d6fa912188
commit 369c09be61
11 files changed
+652 -133

No files matched your search

+4 -40
View File
@@ -2,7 +2,6 @@ package routing
import (
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
@@ -12,8 +11,7 @@ import (
type routingGraph interface {
// forEachNodeChannel calls the callback for every channel of the given node.
forEachNodeChannel(nodePub route.Vertex,
cb func(*channeldb.ChannelEdgeInfo, *channeldb.ChannelEdgePolicy,
*channeldb.ChannelEdgePolicy) error) error
cb func(channel *channeldb.DirectedChannel) error) error
// sourceNode returns the source node of the graph.
sourceNode() route.Vertex
@@ -26,7 +24,6 @@ type routingGraph interface {
// database.
type dbRoutingTx struct {
graph *channeldb.ChannelGraph
tx kvdb.RTx
source route.Vertex
}
@@ -38,37 +35,19 @@ func newDbRoutingTx(graph *channeldb.ChannelGraph) (*dbRoutingTx, error) {
return nil, err
}
tx, err := graph.Database().BeginReadTx()
if err != nil {
return nil, err
}
return &dbRoutingTx{
graph: graph,
tx: tx,
source: sourceNode.PubKeyBytes,
}, nil
}
// close closes the underlying db transaction.
func (g *dbRoutingTx) close() error {
return g.tx.Rollback()
}
// forEachNodeChannel calls the callback for every channel of the given node.
//
// NOTE: Part of the routingGraph interface.
func (g *dbRoutingTx) forEachNodeChannel(nodePub route.Vertex,
cb func(*channeldb.ChannelEdgeInfo, *channeldb.ChannelEdgePolicy,
*channeldb.ChannelEdgePolicy) error) error {
cb func(channel *channeldb.DirectedChannel) error) error {
txCb := func(_ kvdb.RTx, info *channeldb.ChannelEdgeInfo,
p1, p2 *channeldb.ChannelEdgePolicy) error {
return cb(info, p1, p2)
}
return g.graph.ForEachNodeChannel(g.tx, nodePub[:], txCb)
return g.graph.ForEachNodeChannel(nodePub, cb)
}
// sourceNode returns the source node of the graph.
@@ -85,20 +64,5 @@ func (g *dbRoutingTx) sourceNode() route.Vertex {
func (g *dbRoutingTx) fetchNodeFeatures(nodePub route.Vertex) (
*lnwire.FeatureVector, error) {
targetNode, err := g.graph.FetchLightningNode(nodePub)
switch err {
// If the node exists and has features, return them directly.
case nil:
return targetNode.Features, nil
// If we couldn't find a node announcement, populate a blank feature
// vector.
case channeldb.ErrGraphNodeNotFound:
return lnwire.EmptyFeatureVector(), nil
// Otherwise bubble the error up.
default:
return nil, err
}
return g.graph.FetchNodeFeatures(nodePub)
}
+22 -21
View File
@@ -159,8 +159,7 @@ func (m *mockGraph) addChannel(id uint64, node1id, node2id byte,
//
// NOTE: Part of the routingGraph interface.
func (m *mockGraph) forEachNodeChannel(nodePub route.Vertex,
cb func(*channeldb.ChannelEdgeInfo, *channeldb.ChannelEdgePolicy,
*channeldb.ChannelEdgePolicy) error) error {
cb func(channel *channeldb.DirectedChannel) error) error {
// Look up the mock node.
node, ok := m.nodes[nodePub]
@@ -171,36 +170,38 @@ func (m *mockGraph) forEachNodeChannel(nodePub route.Vertex,
// Iterate over all of its channels.
for peer, channel := range node.channels {
// Lexicographically sort the pubkeys.
var node1, node2 route.Vertex
var node1 route.Vertex
if bytes.Compare(nodePub[:], peer[:]) == -1 {
node1, node2 = peer, nodePub
node1 = peer
} else {
node1, node2 = nodePub, peer
node1 = nodePub
}
peerNode := m.nodes[peer]
// Call the per channel callback.
err := cb(
&channeldb.ChannelEdgeInfo{
NodeKey1Bytes: node1,
NodeKey2Bytes: node2,
},
&channeldb.ChannelEdgePolicy{
&channeldb.DirectedChannel{
ChannelID: channel.id,
Node: &channeldb.LightningNode{
PubKeyBytes: peer,
Features: lnwire.EmptyFeatureVector(),
IsNode1: nodePub == node1,
OtherNode: peer,
Capacity: channel.capacity,
OutPolicy: &channeldb.ChannelEdgePolicy{
ChannelID: channel.id,
Node: &channeldb.LightningNode{
PubKeyBytes: peer,
Features: lnwire.EmptyFeatureVector(),
},
FeeBaseMSat: node.baseFee,
},
FeeBaseMSat: node.baseFee,
},
&channeldb.ChannelEdgePolicy{
ChannelID: channel.id,
Node: &channeldb.LightningNode{
PubKeyBytes: nodePub,
Features: lnwire.EmptyFeatureVector(),
InPolicy: &channeldb.ChannelEdgePolicy{
ChannelID: channel.id,
Node: &channeldb.LightningNode{
PubKeyBytes: nodePub,
Features: lnwire.EmptyFeatureVector(),
},
FeeBaseMSat: peerNode.baseFee,
},
FeeBaseMSat: peerNode.baseFee,
},
)
if err != nil {
+6 -9
View File
@@ -359,14 +359,12 @@ func getOutgoingBalance(node route.Vertex, outgoingChans map[uint64]struct{},
g routingGraph) (lnwire.MilliSatoshi, lnwire.MilliSatoshi, error) {
var max, total lnwire.MilliSatoshi
cb := func(edgeInfo *channeldb.ChannelEdgeInfo, outEdge,
_ *channeldb.ChannelEdgePolicy) error {
if outEdge == nil {
cb := func(channel *channeldb.DirectedChannel) error {
if channel.OutPolicy == nil {
return nil
}
chanID := outEdge.ChannelID
chanID := channel.ChannelID
// Enforce outgoing channel restriction.
if outgoingChans != nil {
@@ -381,9 +379,7 @@ func getOutgoingBalance(node route.Vertex, outgoingChans map[uint64]struct{},
// This can happen when a channel is added to the graph after
// we've already queried the bandwidth hints.
if !ok {
bandwidth = lnwire.NewMSatFromSatoshis(
edgeInfo.Capacity,
)
bandwidth = lnwire.NewMSatFromSatoshis(channel.Capacity)
}
if bandwidth > max {
@@ -889,7 +885,8 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
// Determine the next hop forward using the next map.
currentNodeWithDist, ok := distance[currentNode]
if !ok {
// If the node doesnt have a next hop it means we didn't find a path.
// If the node doesn't have a next hop it means we
// didn't find a path.
return nil, errNoPathFound
}
+42 -7
View File
@@ -304,6 +304,16 @@ func parseTestGraph(path string) (*testGraphInstance, error) {
}
}
aliasForNode := func(node route.Vertex) string {
for alias, pubKey := range aliasMap {
if pubKey == node {
return alias
}
}
return ""
}
// With all the vertexes inserted, we can now insert the edges into the
// test graph.
for _, edge := range g.Edges {
@@ -353,10 +363,17 @@ func parseTestGraph(path string) (*testGraphInstance, error) {
return nil, err
}
channelFlags := lnwire.ChanUpdateChanFlags(edge.ChannelFlags)
isUpdate1 := channelFlags&lnwire.ChanUpdateDirection == 0
targetNode := edgeInfo.NodeKey1Bytes
if isUpdate1 {
targetNode = edgeInfo.NodeKey2Bytes
}
edgePolicy := &channeldb.ChannelEdgePolicy{
SigBytes: testSig.Serialize(),
MessageFlags: lnwire.ChanUpdateMsgFlags(edge.MessageFlags),
ChannelFlags: lnwire.ChanUpdateChanFlags(edge.ChannelFlags),
ChannelFlags: channelFlags,
ChannelID: edge.ChannelID,
LastUpdate: testTime,
TimeLockDelta: edge.Expiry,
@@ -364,6 +381,10 @@ func parseTestGraph(path string) (*testGraphInstance, error) {
MaxHTLC: lnwire.MilliSatoshi(edge.MaxHTLC),
FeeBaseMSat: lnwire.MilliSatoshi(edge.FeeBaseMsat),
FeeProportionalMillionths: lnwire.MilliSatoshi(edge.FeeRate),
Node: &channeldb.LightningNode{
Alias: aliasForNode(targetNode),
PubKeyBytes: targetNode,
},
}
if err := graph.UpdateEdgePolicy(edgePolicy); err != nil {
return nil, err
@@ -635,6 +656,11 @@ func createTestGraphFromChannels(testChannels []*testChannel, source string) (
channelFlags |= lnwire.ChanUpdateDisabled
}
node2Features := lnwire.EmptyFeatureVector()
if node2.testChannelPolicy != nil {
node2Features = node2.Features
}
edgePolicy := &channeldb.ChannelEdgePolicy{
SigBytes: testSig.Serialize(),
MessageFlags: msgFlags,
@@ -646,6 +672,11 @@ func createTestGraphFromChannels(testChannels []*testChannel, source string) (
MaxHTLC: node1.MaxHTLC,
FeeBaseMSat: node1.FeeBaseMsat,
FeeProportionalMillionths: node1.FeeRate,
Node: &channeldb.LightningNode{
Alias: node2.Alias,
PubKeyBytes: node2Vertex,
Features: node2Features,
},
}
if err := graph.UpdateEdgePolicy(edgePolicy); err != nil {
return nil, err
@@ -663,6 +694,11 @@ func createTestGraphFromChannels(testChannels []*testChannel, source string) (
}
channelFlags |= lnwire.ChanUpdateDirection
node1Features := lnwire.EmptyFeatureVector()
if node1.testChannelPolicy != nil {
node1Features = node1.Features
}
edgePolicy := &channeldb.ChannelEdgePolicy{
SigBytes: testSig.Serialize(),
MessageFlags: msgFlags,
@@ -674,6 +710,11 @@ func createTestGraphFromChannels(testChannels []*testChannel, source string) (
MaxHTLC: node2.MaxHTLC,
FeeBaseMSat: node2.FeeBaseMsat,
FeeProportionalMillionths: node2.FeeRate,
Node: &channeldb.LightningNode{
Alias: node1.Alias,
PubKeyBytes: node1Vertex,
Features: node1Features,
},
}
if err := graph.UpdateEdgePolicy(edgePolicy); err != nil {
return nil, err
@@ -2980,12 +3021,6 @@ func dbFindPath(graph *channeldb.ChannelGraph,
if err != nil {
return nil, err
}
defer func() {
err := routingTx.close()
if err != nil {
log.Errorf("Error closing db tx: %v", err)
}
}()
return findPath(
&graphParams{
+1 -6
View File
@@ -47,12 +47,7 @@ func (m *SessionSource) getRoutingGraph() (routingGraph, func(), error) {
if err != nil {
return nil, nil, err
}
return routingTx, func() {
err := routingTx.close()
if err != nil {
log.Errorf("Error closing db tx: %v", err)
}
}, nil
return routingTx, func() {}, nil
}
// NewPaymentSession creates a new payment session backed by the latest prune
-12
View File
@@ -1756,12 +1756,6 @@ func (r *ChannelRouter) FindRoute(source, target route.Vertex,
if err != nil {
return nil, err
}
defer func() {
err := routingTx.close()
if err != nil {
log.Errorf("Error closing db tx: %v", err)
}
}()
path, err := findPath(
&graphParams{
@@ -2763,12 +2757,6 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
if err != nil {
return nil, err
}
defer func() {
err := routingTx.close()
if err != nil {
log.Errorf("Error closing db tx: %v", err)
}
}()
// Traverse hops backwards to accumulate fees in the running amounts.
source := r.selfNode.PubKeyBytes
+12
View File
@@ -1393,6 +1393,9 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
MinHTLC: 1,
FeeBaseMSat: 10,
FeeProportionalMillionths: 10000,
Node: &channeldb.LightningNode{
PubKeyBytes: edge.NodeKey2Bytes,
},
}
edgePolicy.ChannelFlags = 0
@@ -1409,6 +1412,9 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
MinHTLC: 1,
FeeBaseMSat: 10,
FeeProportionalMillionths: 10000,
Node: &channeldb.LightningNode{
PubKeyBytes: edge.NodeKey1Bytes,
},
}
edgePolicy.ChannelFlags = 1
@@ -1490,6 +1496,9 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
MinHTLC: 1,
FeeBaseMSat: 10,
FeeProportionalMillionths: 10000,
Node: &channeldb.LightningNode{
PubKeyBytes: edge.NodeKey2Bytes,
},
}
edgePolicy.ChannelFlags = 0
@@ -1505,6 +1514,9 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
MinHTLC: 1,
FeeBaseMSat: 10,
FeeProportionalMillionths: 10000,
Node: &channeldb.LightningNode{
PubKeyBytes: edge.NodeKey1Bytes,
},
}
edgePolicy.ChannelFlags = 1
+5 -11
View File
@@ -69,24 +69,18 @@ func (u *unifiedPolicies) addPolicy(fromNode route.Vertex,
// addGraphPolicies adds all policies that are known for the toNode in the
// graph.
func (u *unifiedPolicies) addGraphPolicies(g routingGraph) error {
cb := func(edgeInfo *channeldb.ChannelEdgeInfo, _,
inEdge *channeldb.ChannelEdgePolicy) error {
cb := func(channel *channeldb.DirectedChannel) error {
// If there is no edge policy for this candidate node, skip.
// Note that we are searching backwards so this node would have
// come prior to the pivot node in the route.
if inEdge == nil {
if channel.InPolicy == nil {
return nil
}
// The node on the other end of this channel is the from node.
fromNode, err := edgeInfo.OtherNodeKeyBytes(u.toNode[:])
if err != nil {
return err
}
// Add this policy to the unified policies map.
u.addPolicy(fromNode, inEdge, edgeInfo.Capacity)
u.addPolicy(
channel.OtherNode, channel.InPolicy, channel.Capacity,
)
return nil
}