Merge pull request #8848 from ellemouton/graphManager

refactor: move graph responsibilities from routing.ChannelRouter to new graph.Builder
This commit is contained in:
Oliver Gugger 2024-07-16 00:54:00 -06:00 committed by GitHub
commit fae7e0c5b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 6094 additions and 4330 deletions

View file

@ -89,7 +89,7 @@ func (d *dbNode) Addrs() []net.Addr {
//
// NOTE: Part of the autopilot.Node interface.
func (d *dbNode) ForEachChannel(cb func(ChannelEdge) error) error {
return d.db.ForEachNodeChannel(d.tx, d.node.PubKeyBytes,
return d.db.ForEachNodeChannelTx(d.tx, d.node.PubKeyBytes,
func(tx kvdb.RTx, ei *models.ChannelEdgeInfo, ep,
_ *models.ChannelEdgePolicy) error {
@ -105,7 +105,9 @@ func (d *dbNode) ForEachChannel(cb func(ChannelEdge) error) error {
return nil
}
node, err := d.db.FetchLightningNode(tx, ep.ToNode)
node, err := d.db.FetchLightningNodeTx(
tx, ep.ToNode,
)
if err != nil {
return err
}
@ -164,7 +166,7 @@ func (d *databaseChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
return nil, err
}
dbNode, err := d.db.FetchLightningNode(nil, vertex)
dbNode, err := d.db.FetchLightningNode(vertex)
switch {
case err == channeldb.ErrGraphNodeNotFound:
fallthrough

View file

@ -6,9 +6,9 @@ import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/graph"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing"
)
// ManagerCfg houses a set of values and methods that is passed to the Manager
@ -36,7 +36,7 @@ type ManagerCfg struct {
// SubscribeTopology is used to get a subscription for topology changes
// on the network.
SubscribeTopology func() (*routing.TopologyClient, error)
SubscribeTopology func() (*graph.TopologyClient, error)
}
// Manager is struct that manages an autopilot agent, making it possible to

View file

@ -1351,7 +1351,7 @@ func (d *DB) AddrsForNode(nodePub *btcec.PublicKey) ([]net.Addr,
if err != nil {
return nil, err
}
graphNode, err := d.graph.FetchLightningNode(nil, pubKey)
graphNode, err := d.graph.FetchLightningNode(pubKey)
if err != nil && err != ErrGraphNodeNotFound {
return nil, err
} else if err == ErrGraphNodeNotFound {

View file

@ -529,7 +529,7 @@ func (c *ChannelGraph) FetchNodeFeatures(
}
// Fallback that uses the database.
targetNode, err := c.FetchLightningNode(nil, node)
targetNode, err := c.FetchLightningNode(node)
switch err {
// If the node exists and has features, return them directly.
case nil:
@ -565,7 +565,7 @@ func (c *ChannelGraph) ForEachNodeCached(cb func(node route.Vertex,
return c.ForEachNode(func(tx kvdb.RTx, node *LightningNode) error {
channels := make(map[uint64]*DirectedChannel)
err := c.ForEachNodeChannel(tx, node.PubKeyBytes,
err := c.ForEachNodeChannelTx(tx, node.PubKeyBytes,
func(tx kvdb.RTx, e *models.ChannelEdgeInfo,
p1 *models.ChannelEdgePolicy,
p2 *models.ChannelEdgePolicy) error {
@ -2374,10 +2374,19 @@ func (c *ChannelGraph) FilterChannelRange(startHeight,
// skipped and the result will contain only those edges that exist at the time
// of the query. This can be used to respond to peer queries that are seeking to
// fill in gaps in their view of the channel graph.
func (c *ChannelGraph) FetchChanInfos(chanIDs []uint64) ([]ChannelEdge, error) {
return c.fetchChanInfos(nil, chanIDs)
}
// fetchChanInfos returns the set of channel edges that correspond to the passed
// channel ID's. If an edge is the query is unknown to the database, it will
// skipped and the result will contain only those edges that exist at the time
// of the query. This can be used to respond to peer queries that are seeking to
// fill in gaps in their view of the channel graph.
//
// NOTE: An optional transaction may be provided. If none is provided, then a
// new one will be created.
func (c *ChannelGraph) FetchChanInfos(tx kvdb.RTx, chanIDs []uint64) (
func (c *ChannelGraph) fetchChanInfos(tx kvdb.RTx, chanIDs []uint64) (
[]ChannelEdge, error) {
// TODO(roasbeef): sort cids?
@ -2922,7 +2931,7 @@ func (c *ChannelGraph) isPublic(tx kvdb.RTx, nodePub route.Vertex,
// used to terminate the check early.
nodeIsPublic := false
errDone := errors.New("done")
err := c.ForEachNodeChannel(tx, nodePub, func(tx kvdb.RTx,
err := c.ForEachNodeChannelTx(tx, nodePub, func(tx kvdb.RTx,
info *models.ChannelEdgeInfo, _ *models.ChannelEdgePolicy,
_ *models.ChannelEdgePolicy) error {
@ -2954,12 +2963,31 @@ func (c *ChannelGraph) isPublic(tx kvdb.RTx, nodePub route.Vertex,
return nodeIsPublic, nil
}
// FetchLightningNodeTx attempts to look up a target node by its identity
// public key. If the node isn't found in the database, then
// ErrGraphNodeNotFound is returned. An optional transaction may be provided.
// If none is provided, then a new one will be created.
func (c *ChannelGraph) FetchLightningNodeTx(tx kvdb.RTx, nodePub route.Vertex) (
*LightningNode, error) {
return c.fetchLightningNode(tx, nodePub)
}
// FetchLightningNode attempts to look up a target node by its identity public
// key. If the node isn't found in the database, then ErrGraphNodeNotFound is
// returned.
func (c *ChannelGraph) FetchLightningNode(nodePub route.Vertex) (*LightningNode,
error) {
return c.fetchLightningNode(nil, nodePub)
}
// fetchLightningNode attempts to look up a target node by its identity public
// key. If the node isn't found in the database, then ErrGraphNodeNotFound is
// returned. An optional transaction may be provided. If none is provided, then
// a new one will be created.
func (c *ChannelGraph) FetchLightningNode(tx kvdb.RTx, nodePub route.Vertex) (
*LightningNode, error) {
func (c *ChannelGraph) fetchLightningNode(tx kvdb.RTx,
nodePub route.Vertex) (*LightningNode, error) {
var node *LightningNode
fetch := func(tx kvdb.RTx) error {
@ -3196,13 +3224,29 @@ func nodeTraversal(tx kvdb.RTx, nodePub []byte, db kvdb.Backend,
// halted with the error propagated back up to the caller.
//
// Unknown policies are passed into the callback as nil values.
func (c *ChannelGraph) ForEachNodeChannel(nodePub route.Vertex,
cb func(kvdb.RTx, *models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
*models.ChannelEdgePolicy) error) error {
return nodeTraversal(nil, nodePub[:], c.db, cb)
}
// ForEachNodeChannelTx iterates through all channels of the given node,
// executing the passed callback with an edge info structure and the policies
// of each end of the channel. The first edge policy is the outgoing edge *to*
// the connecting node, while the second is the incoming edge *from* the
// connecting node. If the callback returns an error, then the iteration is
// halted with the error propagated back up to the caller.
//
// Unknown policies are passed into the callback as nil values.
//
// If the caller wishes to re-use an existing boltdb transaction, then it
// should be passed as the first argument. Otherwise the first argument should
// should be passed as the first argument. Otherwise, the first argument should
// be nil and a fresh transaction will be created to execute the graph
// traversal.
func (c *ChannelGraph) ForEachNodeChannel(tx kvdb.RTx, nodePub route.Vertex,
cb func(kvdb.RTx, *models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
func (c *ChannelGraph) ForEachNodeChannelTx(tx kvdb.RTx,
nodePub route.Vertex, cb func(kvdb.RTx, *models.ChannelEdgeInfo,
*models.ChannelEdgePolicy,
*models.ChannelEdgePolicy) error) error {
return nodeTraversal(tx, nodePub[:], c.db, cb)
@ -3705,7 +3749,7 @@ func (c *ChannelGraph) markEdgeLiveUnsafe(tx kvdb.RwTx, chanID uint64) error {
// We need to add the channel back into our graph cache, otherwise we
// won't use it for path finding.
if c.graphCache != nil {
edgeInfos, err := c.FetchChanInfos(tx, []uint64{chanID})
edgeInfos, err := c.fetchChanInfos(tx, []uint64{chanID})
if err != nil {
return err
}

View file

@ -141,7 +141,7 @@ func TestNodeInsertionAndDeletion(t *testing.T) {
// Next, fetch the node from the database to ensure everything was
// serialized properly.
dbNode, err := graph.FetchLightningNode(nil, testPub)
dbNode, err := graph.FetchLightningNode(testPub)
require.NoError(t, err, "unable to locate node")
if _, exists, err := graph.HasLightningNode(dbNode.PubKeyBytes); err != nil {
@ -164,7 +164,7 @@ func TestNodeInsertionAndDeletion(t *testing.T) {
// Finally, attempt to fetch the node again. This should fail as the
// node should have been deleted from the database.
_, err = graph.FetchLightningNode(nil, testPub)
_, err = graph.FetchLightningNode(testPub)
if err != ErrGraphNodeNotFound {
t.Fatalf("fetch after delete should fail!")
}
@ -192,7 +192,7 @@ func TestPartialNode(t *testing.T) {
// Next, fetch the node from the database to ensure everything was
// serialized properly.
dbNode, err := graph.FetchLightningNode(nil, testPub)
dbNode, err := graph.FetchLightningNode(testPub)
require.NoError(t, err, "unable to locate node")
if _, exists, err := graph.HasLightningNode(dbNode.PubKeyBytes); err != nil {
@ -222,7 +222,7 @@ func TestPartialNode(t *testing.T) {
// Finally, attempt to fetch the node again. This should fail as the
// node should have been deleted from the database.
_, err = graph.FetchLightningNode(nil, testPub)
_, err = graph.FetchLightningNode(testPub)
if err != ErrGraphNodeNotFound {
t.Fatalf("fetch after delete should fail!")
}
@ -1055,7 +1055,7 @@ func TestGraphTraversal(t *testing.T) {
// outgoing channels for a particular node.
numNodeChans := 0
firstNode, secondNode := nodeList[0], nodeList[1]
err = graph.ForEachNodeChannel(nil, firstNode.PubKeyBytes,
err = graph.ForEachNodeChannel(firstNode.PubKeyBytes,
func(_ kvdb.RTx, _ *models.ChannelEdgeInfo, outEdge,
inEdge *models.ChannelEdgePolicy) error {
@ -2685,7 +2685,7 @@ func TestFetchChanInfos(t *testing.T) {
// We'll now attempt to query for the range of channel ID's we just
// inserted into the database. We should get the exact same set of
// edges back.
resp, err := graph.FetchChanInfos(nil, edgeQuery)
resp, err := graph.FetchChanInfos(edgeQuery)
require.NoError(t, err, "unable to fetch chan edges")
if len(resp) != len(edges) {
t.Fatalf("expected %v edges, instead got %v", len(edges),
@ -2737,7 +2737,7 @@ func TestIncompleteChannelPolicies(t *testing.T) {
// Ensure that channel is reported with unknown policies.
checkPolicies := func(node *LightningNode, expectedIn, expectedOut bool) {
calls := 0
err := graph.ForEachNodeChannel(nil, node.PubKeyBytes,
err := graph.ForEachNodeChannel(node.PubKeyBytes,
func(_ kvdb.RTx, _ *models.ChannelEdgeInfo, outEdge,
inEdge *models.ChannelEdgePolicy) error {
@ -3014,7 +3014,7 @@ func TestPruneGraphNodes(t *testing.T) {
// Finally, we'll ensure that node3, the only fully unconnected node as
// properly deleted from the graph and not another node in its place.
_, err = graph.FetchLightningNode(nil, node3.PubKeyBytes)
_, err = graph.FetchLightningNode(node3.PubKeyBytes)
if err == nil {
t.Fatalf("node 3 should have been deleted!")
}
@ -3048,13 +3048,13 @@ func TestAddChannelEdgeShellNodes(t *testing.T) {
// Ensure that node1 was inserted as a full node, while node2 only has
// a shell node present.
node1, err = graph.FetchLightningNode(nil, node1.PubKeyBytes)
node1, err = graph.FetchLightningNode(node1.PubKeyBytes)
require.NoError(t, err, "unable to fetch node1")
if !node1.HaveNodeAnnouncement {
t.Fatalf("have shell announcement for node1, shouldn't")
}
node2, err = graph.FetchLightningNode(nil, node2.PubKeyBytes)
node2, err = graph.FetchLightningNode(node2.PubKeyBytes)
require.NoError(t, err, "unable to fetch node2")
if node2.HaveNodeAnnouncement {
t.Fatalf("should have shell announcement for node2, but is full")

View file

@ -0,0 +1,141 @@
package graphsession
import (
"fmt"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/routing/route"
)
// Factory implements the routing.GraphSessionFactory and can be used to start
// a session with a ReadOnlyGraph.
type Factory struct {
graph ReadOnlyGraph
}
// NewGraphSessionFactory constructs a new Factory which can then be used to
// start a new session.
func NewGraphSessionFactory(graph ReadOnlyGraph) routing.GraphSessionFactory {
return &Factory{
graph: graph,
}
}
// NewGraphSession will produce a new Graph to use for a path-finding session.
// It returns the Graph along with a call-back that must be called once Graph
// access is complete. This call-back will close any read-only transaction that
// was created at Graph construction time.
//
// NOTE: This is part of the routing.GraphSessionFactory interface.
func (g *Factory) NewGraphSession() (routing.Graph, func() error, error) {
tx, err := g.graph.NewPathFindTx()
if err != nil {
return nil, nil, err
}
session := &session{
graph: g.graph,
tx: tx,
}
return session, session.close, nil
}
// A compile-time check to ensure that Factory implements the
// routing.GraphSessionFactory interface.
var _ routing.GraphSessionFactory = (*Factory)(nil)
// session is an implementation of the routing.Graph interface where the same
// read-only transaction is held across calls to the graph and can be used to
// access the backing channel graph.
type session struct {
graph graph
tx kvdb.RTx
}
// NewRoutingGraph constructs a session that which does not first start a
// read-only transaction and so each call on the routing.Graph will create a
// new transaction.
func NewRoutingGraph(graph ReadOnlyGraph) routing.Graph {
return &session{
graph: graph,
}
}
// close closes the read-only transaction being used to access the backing
// graph. If no transaction was started then this is a no-op.
func (g *session) close() error {
if g.tx == nil {
return nil
}
err := g.tx.Rollback()
if err != nil {
return fmt.Errorf("error closing db tx: %w", err)
}
return nil
}
// ForEachNodeChannel calls the callback for every channel of the given node.
//
// NOTE: Part of the routing.Graph interface.
func (g *session) ForEachNodeChannel(nodePub route.Vertex,
cb func(channel *channeldb.DirectedChannel) error) error {
return g.graph.ForEachNodeDirectedChannel(g.tx, nodePub, cb)
}
// FetchNodeFeatures returns the features of the given node. If the node is
// unknown, assume no additional features are supported.
//
// NOTE: Part of the routing.Graph interface.
func (g *session) FetchNodeFeatures(nodePub route.Vertex) (
*lnwire.FeatureVector, error) {
return g.graph.FetchNodeFeatures(nodePub)
}
// A compile-time check to ensure that *session implements the
// routing.Graph interface.
var _ routing.Graph = (*session)(nil)
// ReadOnlyGraph is a graph extended with a call to create a new read-only
// transaction that can then be used to make further queries to the graph.
type ReadOnlyGraph interface {
// NewPathFindTx returns a new read transaction that can be used for a
// single path finding session. Will return nil if the graph cache is
// enabled.
NewPathFindTx() (kvdb.RTx, error)
graph
}
// graph describes the API necessary for a graph source to have access to on a
// database implementation, like channeldb.ChannelGraph, in order to be used by
// the Router for pathfinding.
type graph interface {
// ForEachNodeDirectedChannel iterates through all channels of a given
// node, executing the passed callback on the directed edge representing
// the channel and its incoming policy. If the callback returns an
// error, then the iteration is halted with the error propagated back
// up to the caller.
//
// Unknown policies are passed into the callback as nil values.
//
// NOTE: if a nil tx is provided, then it is expected that the
// implementation create a read only tx.
ForEachNodeDirectedChannel(tx kvdb.RTx, node route.Vertex,
cb func(channel *channeldb.DirectedChannel) error) error
// FetchNodeFeatures returns the features of a given node. If no
// features are known for the node, an empty feature vector is returned.
FetchNodeFeatures(node route.Vertex) (*lnwire.FeatureVector, error)
}
// A compile-time check to ensure that *channeldb.ChannelGraph implements the
// graph interface.
var _ graph = (*channeldb.ChannelGraph)(nil)

View file

@ -5,9 +5,9 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/graph"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/routing/route"
)
@ -136,7 +136,7 @@ func (c *ChanSeries) UpdatesInHorizon(chain chainhash.Hash,
if edge1 != nil {
// We don't want to send channel updates that don't
// conform to the spec (anymore).
err := routing.ValidateChannelUpdateFields(0, edge1)
err := graph.ValidateChannelUpdateFields(0, edge1)
if err != nil {
log.Errorf("not sending invalid channel "+
"update %v: %v", edge1, err)
@ -145,7 +145,7 @@ func (c *ChanSeries) UpdatesInHorizon(chain chainhash.Hash,
}
}
if edge2 != nil {
err := routing.ValidateChannelUpdateFields(0, edge2)
err := graph.ValidateChannelUpdateFields(0, edge2)
if err != nil {
log.Errorf("not sending invalid channel "+
"update %v: %v", edge2, err)
@ -249,7 +249,7 @@ func (c *ChanSeries) FetchChanAnns(chain chainhash.Hash,
chanIDs = append(chanIDs, chanID.ToUint64())
}
channels, err := c.graph.FetchChanInfos(nil, chanIDs)
channels, err := c.graph.FetchChanInfos(chanIDs)
if err != nil {
return nil, err
}

View file

@ -20,6 +20,7 @@ import (
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/graph"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnpeer"
@ -28,7 +29,6 @@ import (
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/multimutex"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/ticker"
"golang.org/x/time/rate"
@ -165,11 +165,11 @@ type Config struct {
// * also need to do same for Notifier
ChainHash chainhash.Hash
// Router is the subsystem which is responsible for managing the
// Graph is the subsystem which is responsible for managing the
// topology of lightning network. After incoming channel, node, channel
// updates announcements are validated they are sent to the router in
// order to be included in the LN graph.
Router routing.ChannelGraphSource
Graph graph.ChannelGraphSource
// ChanSeries is an interfaces that provides access to a time series
// view of the current known channel graph. Each GossipSyncer enabled
@ -591,7 +591,7 @@ func (d *AuthenticatedGossiper) start() error {
}
d.blockEpochs = blockEpochs
height, err := d.cfg.Router.CurrentBlockHeight()
height, err := d.cfg.Graph.CurrentBlockHeight()
if err != nil {
return err
}
@ -1360,7 +1360,7 @@ func (d *AuthenticatedGossiper) networkHandler() {
// We'll use this validation to ensure that we process jobs in their
// dependency order during parallel validation.
validationBarrier := routing.NewValidationBarrier(1000, d.quit)
validationBarrier := graph.NewValidationBarrier(1000, d.quit)
for {
select {
@ -1485,7 +1485,7 @@ func (d *AuthenticatedGossiper) networkHandler() {
//
// NOTE: must be run as a goroutine.
func (d *AuthenticatedGossiper) handleNetworkMessages(nMsg *networkMsg,
deDuped *deDupedAnnouncements, vb *routing.ValidationBarrier) {
deDuped *deDupedAnnouncements, vb *graph.ValidationBarrier) {
defer d.wg.Done()
defer vb.CompleteJob()
@ -1501,10 +1501,10 @@ func (d *AuthenticatedGossiper) handleNetworkMessages(nMsg *networkMsg,
log.Debugf("Validating network message %s got err: %v",
nMsg.msg.MsgType(), err)
if !routing.IsError(
if !graph.IsError(
err,
routing.ErrVBarrierShuttingDown,
routing.ErrParentValidationFailed,
graph.ErrVBarrierShuttingDown,
graph.ErrParentValidationFailed,
) {
log.Warnf("unexpected error during validation "+
@ -1595,7 +1595,7 @@ func (d *AuthenticatedGossiper) retransmitStaleAnns(now time.Time) error {
havePublicChannels bool
edgesToUpdate []updateTuple
)
err := d.cfg.Router.ForAllOutgoingChannels(func(
err := d.cfg.Graph.ForAllOutgoingChannels(func(
_ kvdb.RTx,
info *models.ChannelEdgeInfo,
edge *models.ChannelEdgePolicy) error {
@ -1831,7 +1831,7 @@ func (d *AuthenticatedGossiper) processRejectedEdge(
// First, we'll fetch the state of the channel as we know if from the
// database.
chanInfo, e1, e2, err := d.cfg.Router.GetChannelByID(
chanInfo, e1, e2, err := d.cfg.Graph.GetChannelByID(
chanAnnMsg.ShortChannelID,
)
if err != nil {
@ -1860,7 +1860,7 @@ func (d *AuthenticatedGossiper) processRejectedEdge(
if err != nil {
return nil, err
}
err = routing.ValidateChannelAnn(chanAnn)
err = graph.ValidateChannelAnn(chanAnn)
if err != nil {
err := fmt.Errorf("assembled channel announcement proof "+
"for shortChanID=%v isn't valid: %v",
@ -1871,7 +1871,7 @@ func (d *AuthenticatedGossiper) processRejectedEdge(
// If everything checks out, then we'll add the fully assembled proof
// to the database.
err = d.cfg.Router.AddProof(chanAnnMsg.ShortChannelID, proof)
err = d.cfg.Graph.AddProof(chanAnnMsg.ShortChannelID, proof)
if err != nil {
err := fmt.Errorf("unable add proof to shortChanID=%v: %w",
chanAnnMsg.ShortChannelID, err)
@ -1909,7 +1909,7 @@ func (d *AuthenticatedGossiper) processRejectedEdge(
func (d *AuthenticatedGossiper) addNode(msg *lnwire.NodeAnnouncement,
op ...batch.SchedulerOption) error {
if err := routing.ValidateNodeAnn(msg); err != nil {
if err := graph.ValidateNodeAnn(msg); err != nil {
return fmt.Errorf("unable to validate node announcement: %w",
err)
}
@ -1928,7 +1928,7 @@ func (d *AuthenticatedGossiper) addNode(msg *lnwire.NodeAnnouncement,
ExtraOpaqueData: msg.ExtraOpaqueData,
}
return d.cfg.Router.AddNode(node, op...)
return d.cfg.Graph.AddNode(node, op...)
}
// isPremature decides whether a given network message has a block height+delta
@ -2063,7 +2063,7 @@ func (d *AuthenticatedGossiper) processZombieUpdate(
"with chan_id=%v", msg.ShortChannelID)
}
err := routing.VerifyChannelUpdateSignature(msg, pubKey)
err := graph.VerifyChannelUpdateSignature(msg, pubKey)
if err != nil {
return fmt.Errorf("unable to verify channel "+
"update signature: %v", err)
@ -2072,7 +2072,7 @@ func (d *AuthenticatedGossiper) processZombieUpdate(
// With the signature valid, we'll proceed to mark the
// edge as live and wait for the channel announcement to
// come through again.
err = d.cfg.Router.MarkEdgeLive(scid)
err = d.cfg.Graph.MarkEdgeLive(scid)
switch {
case errors.Is(err, channeldb.ErrZombieEdgeNotFound):
log.Errorf("edge with chan_id=%v was not found in the "+
@ -2099,7 +2099,7 @@ func (d *AuthenticatedGossiper) processZombieUpdate(
func (d *AuthenticatedGossiper) fetchNodeAnn(
pubKey [33]byte) (*lnwire.NodeAnnouncement, error) {
node, err := d.cfg.Router.FetchLightningNode(pubKey)
node, err := d.cfg.Graph.FetchLightningNode(pubKey)
if err != nil {
return nil, err
}
@ -2112,7 +2112,7 @@ func (d *AuthenticatedGossiper) fetchNodeAnn(
func (d *AuthenticatedGossiper) isMsgStale(msg lnwire.Message) bool {
switch msg := msg.(type) {
case *lnwire.AnnounceSignatures:
chanInfo, _, _, err := d.cfg.Router.GetChannelByID(
chanInfo, _, _, err := d.cfg.Graph.GetChannelByID(
msg.ShortChannelID,
)
@ -2134,7 +2134,7 @@ func (d *AuthenticatedGossiper) isMsgStale(msg lnwire.Message) bool {
return chanInfo.AuthProof != nil
case *lnwire.ChannelUpdate:
_, p1, p2, err := d.cfg.Router.GetChannelByID(msg.ShortChannelID)
_, p1, p2, err := d.cfg.Graph.GetChannelByID(msg.ShortChannelID)
// If the channel cannot be found, it is most likely a leftover
// message for a channel that was closed, so we can consider it
@ -2200,14 +2200,16 @@ func (d *AuthenticatedGossiper) updateChannel(info *models.ChannelEdgeInfo,
// To ensure that our signature is valid, we'll verify it ourself
// before committing it to the slice returned.
err = routing.ValidateChannelUpdateAnn(d.selfKey, info.Capacity, chanUpdate)
err = graph.ValidateChannelUpdateAnn(
d.selfKey, info.Capacity, chanUpdate,
)
if err != nil {
return nil, nil, fmt.Errorf("generated invalid channel "+
"update sig: %v", err)
}
// Finally, we'll write the new edge policy to disk.
if err := d.cfg.Router.UpdateEdge(edge); err != nil {
if err := d.cfg.Graph.UpdateEdge(edge); err != nil {
return nil, nil, err
}
@ -2327,7 +2329,7 @@ func (d *AuthenticatedGossiper) handleNodeAnnouncement(nMsg *networkMsg,
// We'll quickly ask the router if it already has a newer update for
// this node so we can skip validating signatures if not required.
if d.cfg.Router.IsStaleNode(nodeAnn.NodeID, timestamp) {
if d.cfg.Graph.IsStaleNode(nodeAnn.NodeID, timestamp) {
log.Debugf("Skipped processing stale node: %x", nodeAnn.NodeID)
nMsg.err <- nil
return nil, true
@ -2337,11 +2339,11 @@ func (d *AuthenticatedGossiper) handleNodeAnnouncement(nMsg *networkMsg,
log.Debugf("Adding node: %x got error: %v", nodeAnn.NodeID,
err)
if !routing.IsError(
if !graph.IsError(
err,
routing.ErrOutdated,
routing.ErrIgnored,
routing.ErrVBarrierShuttingDown,
graph.ErrOutdated,
graph.ErrIgnored,
graph.ErrVBarrierShuttingDown,
) {
log.Error(err)
@ -2354,7 +2356,7 @@ func (d *AuthenticatedGossiper) handleNodeAnnouncement(nMsg *networkMsg,
// In order to ensure we don't leak unadvertised nodes, we'll make a
// quick check to ensure this node intends to publicly advertise itself
// to the network.
isPublic, err := d.cfg.Router.IsPublicNode(nodeAnn.NodeID)
isPublic, err := d.cfg.Graph.IsPublicNode(nodeAnn.NodeID)
if err != nil {
log.Errorf("Unable to determine if node %x is advertised: %v",
nodeAnn.NodeID, err)
@ -2447,7 +2449,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
// At this point, we'll now ask the router if this is a zombie/known
// edge. If so we can skip all the processing below.
if d.cfg.Router.IsKnownEdge(ann.ShortChannelID) {
if d.cfg.Graph.IsKnownEdge(ann.ShortChannelID) {
nMsg.err <- nil
return nil, true
}
@ -2456,7 +2458,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
// the signatures within the proof as it should be well formed.
var proof *models.ChannelAuthProof
if nMsg.isRemote {
if err := routing.ValidateChannelAnn(ann); err != nil {
if err := graph.ValidateChannelAnn(ann); err != nil {
err := fmt.Errorf("unable to validate announcement: "+
"%v", err)
@ -2527,9 +2529,9 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
// database and is now making decisions based on this DB state, before
// it writes to the DB.
d.channelMtx.Lock(ann.ShortChannelID.ToUint64())
err := d.cfg.Router.AddEdge(edge, ops...)
err := d.cfg.Graph.AddEdge(edge, ops...)
if err != nil {
log.Debugf("Router rejected edge for short_chan_id(%v): %v",
log.Debugf("Graph rejected edge for short_chan_id(%v): %v",
ann.ShortChannelID.ToUint64(), err)
defer d.channelMtx.Unlock(ann.ShortChannelID.ToUint64())
@ -2537,7 +2539,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
// If the edge was rejected due to already being known, then it
// may be the case that this new message has a fresh channel
// proof, so we'll check.
if routing.IsError(err, routing.ErrIgnored) {
if graph.IsError(err, graph.ErrIgnored) {
// Attempt to process the rejected message to see if we
// get any new announcements.
anns, rErr := d.processRejectedEdge(ann, proof)
@ -2725,7 +2727,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
graphScid = upd.ShortChannelID
}
if d.cfg.Router.IsStaleEdgePolicy(
if d.cfg.Graph.IsStaleEdgePolicy(
graphScid, timestamp, upd.ChannelFlags,
) {
@ -2749,7 +2751,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
d.channelMtx.Lock(graphScid.ToUint64())
defer d.channelMtx.Unlock(graphScid.ToUint64())
chanInfo, e1, e2, err := d.cfg.Router.GetChannelByID(graphScid)
chanInfo, e1, e2, err := d.cfg.Graph.GetChannelByID(graphScid)
switch {
// No error, break.
case err == nil:
@ -2861,7 +2863,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
// Validate the channel announcement with the expected public key and
// channel capacity. In the case of an invalid channel update, we'll
// return an error to the caller and exit early.
err = routing.ValidateChannelUpdateAnn(pubKey, chanInfo.Capacity, upd)
err = graph.ValidateChannelUpdateAnn(pubKey, chanInfo.Capacity, upd)
if err != nil {
rErr := fmt.Errorf("unable to validate channel update "+
"announcement for short_chan_id=%v: %v",
@ -2945,11 +2947,11 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
ExtraOpaqueData: upd.ExtraOpaqueData,
}
if err := d.cfg.Router.UpdateEdge(update, ops...); err != nil {
if routing.IsError(
err, routing.ErrOutdated,
routing.ErrIgnored,
routing.ErrVBarrierShuttingDown,
if err := d.cfg.Graph.UpdateEdge(update, ops...); err != nil {
if graph.IsError(
err, graph.ErrOutdated,
graph.ErrIgnored,
graph.ErrVBarrierShuttingDown,
) {
log.Debugf("Update edge for short_chan_id(%v) got: %v",
@ -3092,7 +3094,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(nMsg *networkMsg,
d.channelMtx.Lock(ann.ShortChannelID.ToUint64())
defer d.channelMtx.Unlock(ann.ShortChannelID.ToUint64())
chanInfo, e1, e2, err := d.cfg.Router.GetChannelByID(
chanInfo, e1, e2, err := d.cfg.Graph.GetChannelByID(
ann.ShortChannelID,
)
if err != nil {
@ -3267,7 +3269,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(nMsg *networkMsg,
// With all the necessary components assembled validate the full
// channel announcement proof.
if err := routing.ValidateChannelAnn(chanAnn); err != nil {
if err := graph.ValidateChannelAnn(chanAnn); err != nil {
err := fmt.Errorf("channel announcement proof for "+
"short_chan_id=%v isn't valid: %v", shortChanID, err)
@ -3282,7 +3284,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(nMsg *networkMsg,
// attest to the bitcoin keys by validating the signatures of
// announcement. If proof is valid then we'll populate the channel edge
// with it, so we can announce it on peer connect.
err = d.cfg.Router.AddProof(ann.ShortChannelID, &dbProof)
err = d.cfg.Graph.AddProof(ann.ShortChannelID, &dbProof)
if err != nil {
err := fmt.Errorf("unable add proof to the channel chanID=%v:"+
" %v", ann.ChannelID, err)

View file

@ -25,6 +25,7 @@ import (
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/graph"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnpeer"
@ -32,7 +33,6 @@ import (
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/ticker"
"github.com/stretchr/testify/require"
@ -108,7 +108,7 @@ func newMockRouter(height uint32) *mockGraphSource {
}
}
var _ routing.ChannelGraphSource = (*mockGraphSource)(nil)
var _ graph.ChannelGraphSource = (*mockGraphSource)(nil)
func (r *mockGraphSource) AddNode(node *channeldb.LightningNode,
_ ...batch.SchedulerOption) error {
@ -350,7 +350,7 @@ func (r *mockGraphSource) IsStaleEdgePolicy(chanID lnwire.ShortChannelID,
// Since it exists within our zombie index, we'll check that it
// respects the router's live edge horizon to determine whether
// it is stale or not.
return time.Since(timestamp) > routing.DefaultChannelPruneExpiry
return time.Since(timestamp) > graph.DefaultChannelPruneExpiry
}
switch {
@ -783,7 +783,7 @@ func createTestCtx(t *testing.T, startHeight uint32) (*testCtx, error) {
Timestamp: testTimestamp,
}, nil
},
Router: router,
Graph: router,
TrickleDelay: trickleDelay,
RetransmitTicker: ticker.NewForce(retransmitDelay),
RebroadcastInterval: rebroadcastInterval,
@ -1457,7 +1457,7 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) {
NotifyWhenOffline: ctx.gossiper.reliableSender.cfg.NotifyWhenOffline,
FetchSelfAnnouncement: ctx.gossiper.cfg.FetchSelfAnnouncement,
UpdateSelfAnnouncement: ctx.gossiper.cfg.UpdateSelfAnnouncement,
Router: ctx.gossiper.cfg.Router,
Graph: ctx.gossiper.cfg.Graph,
TrickleDelay: trickleDelay,
RetransmitTicker: ticker.NewForce(retransmitDelay),
RebroadcastInterval: rebroadcastInterval,
@ -2257,7 +2257,7 @@ func TestProcessZombieEdgeNowLive(t *testing.T) {
// We'll generate a channel update with a timestamp far enough in the
// past to consider it a zombie.
zombieTimestamp := time.Now().Add(-routing.DefaultChannelPruneExpiry)
zombieTimestamp := time.Now().Add(-graph.DefaultChannelPruneExpiry)
batch.chanUpdAnn2.Timestamp = uint32(zombieTimestamp.Unix())
if err := signUpdate(remoteKeyPriv2, batch.chanUpdAnn2); err != nil {
t.Fatalf("unable to sign update with new timestamp: %v", err)

View file

@ -40,10 +40,6 @@
## BOLT Spec Updates
## Testing
## Database
* [Fixed](https://github.com/lightningnetwork/lnd/pull/8854) pagination issues
in SQL invoicedb queries.
## Code Health
## Tooling and Documentation

View file

@ -100,7 +100,16 @@
invoice database. Invoices with incorrect expiry values will be updated to
24-hour expiry, which is the default behavior in LND.
* [Fixed](https://github.com/lightningnetwork/lnd/pull/8854) pagination issues
in SQL invoicedb queries.
## Code Health
* [Move graph building and
maintaining](https://github.com/lightningnetwork/lnd/pull/8848) duties from
the `routing.ChannelRouter` to the new `graph.Builder` sub-system and also
remove the `channeldb.ChannelGraph` pointer from the `ChannelRouter`.
## Tooling and Documentation
# Contributors (Alphabetical Order)

View file

@ -23,6 +23,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/graph"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/labels"
@ -33,7 +34,6 @@ import (
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing"
"golang.org/x/crypto/salsa20"
)
@ -629,11 +629,11 @@ const (
// but we still haven't announced the channel to the network.
channelReadySent
// addedToRouterGraph is the opening state of a channel if the
// channel has been successfully added to the router graph
// immediately after the channelReady message has been sent, but
// we still haven't announced the channel to the network.
addedToRouterGraph
// addedToGraph is the opening state of a channel if the channel has
// been successfully added to the graph immediately after the
// channelReady message has been sent, but we still haven't announced
// the channel to the network.
addedToGraph
)
func (c channelOpeningState) String() string {
@ -642,8 +642,8 @@ func (c channelOpeningState) String() string {
return "markedOpen"
case channelReadySent:
return "channelReadySent"
case addedToRouterGraph:
return "addedToRouterGraph"
case addedToGraph:
return "addedToGraph"
default:
return "unknown"
}
@ -1039,9 +1039,9 @@ func (f *Manager) reservationCoordinator() {
// advanceFundingState will advance the channel through the steps after the
// funding transaction is broadcasted, up until the point where the channel is
// ready for operation. This includes waiting for the funding transaction to
// confirm, sending channel_ready to the peer, adding the channel to the
// router graph, and announcing the channel. The updateChan can be set non-nil
// to get OpenStatusUpdates.
// confirm, sending channel_ready to the peer, adding the channel to the graph,
// and announcing the channel. The updateChan can be set non-nil to get
// OpenStatusUpdates.
//
// NOTE: This MUST be run as a goroutine.
func (f *Manager) advanceFundingState(channel *channeldb.OpenChannel,
@ -1152,7 +1152,7 @@ func (f *Manager) stateStep(channel *channeldb.OpenChannel,
return nil
// channelReady was sent to peer, but the channel was not added to the
// router graph and the channel announcement was not sent.
// graph and the channel announcement was not sent.
case channelReadySent:
// We must wait until we've received the peer's channel_ready
// before sending a channel_update according to BOLT#07.
@ -1183,7 +1183,7 @@ func (f *Manager) stateStep(channel *channeldb.OpenChannel,
// The channel was added to the Router's topology, but the channel
// announcement was not sent.
case addedToRouterGraph:
case addedToGraph:
if channel.IsZeroConf() {
// If this is a zero-conf channel, then we will wait
// for it to be confirmed before announcing it to the
@ -3377,15 +3377,15 @@ func (f *Manager) extractAnnounceParams(c *channeldb.OpenChannel) (
return fwdMinHTLC, fwdMaxHTLC
}
// addToRouterGraph sends a ChannelAnnouncement and a ChannelUpdate to the
// gossiper so that the channel is added to the Router's internal graph.
// addToGraph sends a ChannelAnnouncement and a ChannelUpdate to the
// gossiper so that the channel is added to the graph builder's internal graph.
// These announcement messages are NOT broadcasted to the greater network,
// only to the channel counter party. The proofs required to announce the
// channel to the greater network will be created and sent in annAfterSixConfs.
// The peerAlias is used for zero-conf channels to give the counter-party a
// ChannelUpdate they understand. ourPolicy may be set for various
// option-scid-alias channels to re-use the same policy.
func (f *Manager) addToRouterGraph(completeChan *channeldb.OpenChannel,
func (f *Manager) addToGraph(completeChan *channeldb.OpenChannel,
shortChanID *lnwire.ShortChannelID,
peerAlias *lnwire.ShortChannelID,
ourPolicy *models.ChannelEdgePolicy) error {
@ -3415,10 +3415,10 @@ func (f *Manager) addToRouterGraph(completeChan *channeldb.OpenChannel,
select {
case err := <-errChan:
if err != nil {
if routing.IsError(err, routing.ErrOutdated,
routing.ErrIgnored) {
if graph.IsError(err, graph.ErrOutdated,
graph.ErrIgnored) {
log.Debugf("Router rejected "+
log.Debugf("Graph rejected "+
"ChannelAnnouncement: %v", err)
} else {
return fmt.Errorf("error sending channel "+
@ -3435,10 +3435,10 @@ func (f *Manager) addToRouterGraph(completeChan *channeldb.OpenChannel,
select {
case err := <-errChan:
if err != nil {
if routing.IsError(err, routing.ErrOutdated,
routing.ErrIgnored) {
if graph.IsError(err, graph.ErrOutdated,
graph.ErrIgnored) {
log.Debugf("Router rejected "+
log.Debugf("Graph rejected "+
"ChannelUpdate: %v", err)
} else {
return fmt.Errorf("error sending channel "+
@ -3454,8 +3454,8 @@ func (f *Manager) addToRouterGraph(completeChan *channeldb.OpenChannel,
// annAfterSixConfs broadcasts the necessary channel announcement messages to
// the network after 6 confs. Should be called after the channelReady message
// is sent and the channel is added to the router graph (channelState is
// 'addedToRouterGraph') and the channel is ready to be used. This is the last
// is sent and the channel is added to the graph (channelState is
// 'addedToGraph') and the channel is ready to be used. This is the last
// step in the channel opening process, and the opening state will be deleted
// from the database if successful.
func (f *Manager) annAfterSixConfs(completeChan *channeldb.OpenChannel,
@ -3566,7 +3566,7 @@ func (f *Manager) annAfterSixConfs(completeChan *channeldb.OpenChannel,
}
// We'll delete the edge and add it again via
// addToRouterGraph. This is because the peer may have
// addToGraph. This is because the peer may have
// sent us a ChannelUpdate with an alias and we don't
// want to relay this.
ourPolicy, err := f.cfg.DeleteAliasEdge(baseScid)
@ -3576,12 +3576,12 @@ func (f *Manager) annAfterSixConfs(completeChan *channeldb.OpenChannel,
err)
}
err = f.addToRouterGraph(
err = f.addToGraph(
completeChan, &baseScid, nil, ourPolicy,
)
if err != nil {
return fmt.Errorf("failed to re-add to "+
"router graph: %v", err)
"graph: %v", err)
}
}
@ -3605,9 +3605,9 @@ func (f *Manager) annAfterSixConfs(completeChan *channeldb.OpenChannel,
return nil
}
// waitForZeroConfChannel is called when the state is addedToRouterGraph with
// waitForZeroConfChannel is called when the state is addedToGraph with
// a zero-conf channel. This will wait for the real confirmation, add the
// confirmed SCID to the router graph, and then announce after six confs.
// confirmed SCID to the graph, and then announce after six confs.
func (f *Manager) waitForZeroConfChannel(c *channeldb.OpenChannel) error {
// First we'll check whether the channel is confirmed on-chain. If it
// is already confirmed, the chainntnfs subsystem will return with the
@ -3662,15 +3662,15 @@ func (f *Manager) waitForZeroConfChannel(c *channeldb.OpenChannel) error {
}
// We'll need to update the graph with the new ShortChannelID
// via an addToRouterGraph call. We don't pass in the peer's
// via an addToGraph call. We don't pass in the peer's
// alias since we'll be using the confirmed SCID from now on
// regardless if it's public or not.
err = f.addToRouterGraph(
err = f.addToGraph(
c, &confChan.shortChanID, nil, ourPolicy,
)
if err != nil {
return fmt.Errorf("failed adding confirmed zero-conf "+
"SCID to router graph: %v", err)
"SCID to graph: %v", err)
}
}
@ -3972,7 +3972,7 @@ func (f *Manager) handleChannelReady(peer lnpeer.Peer, //nolint:funlen
// handleChannelReadyReceived is called once the remote's channelReady message
// is received and processed. At this stage, we must have sent out our
// channelReady message, once the remote's channelReady is processed, the
// channel is now active, thus we change its state to `addedToRouterGraph` to
// channel is now active, thus we change its state to `addedToGraph` to
// let the channel start handling routing.
func (f *Manager) handleChannelReadyReceived(channel *channeldb.OpenChannel,
scid *lnwire.ShortChannelID, pendingChanID [32]byte,
@ -4004,9 +4004,9 @@ func (f *Manager) handleChannelReadyReceived(channel *channeldb.OpenChannel,
peerAlias = &foundAlias
}
err := f.addToRouterGraph(channel, scid, peerAlias, nil)
err := f.addToGraph(channel, scid, peerAlias, nil)
if err != nil {
return fmt.Errorf("failed adding to router graph: %w", err)
return fmt.Errorf("failed adding to graph: %w", err)
}
// As the channel is now added to the ChannelRouter's topology, the
@ -4014,15 +4014,15 @@ func (f *Manager) handleChannelReadyReceived(channel *channeldb.OpenChannel,
// moved to the last state (actually deleted from the database) after
// the channel is finally announced.
err = f.saveChannelOpeningState(
&channel.FundingOutpoint, addedToRouterGraph, scid,
&channel.FundingOutpoint, addedToGraph, scid,
)
if err != nil {
return fmt.Errorf("error setting channel state to"+
" addedToRouterGraph: %w", err)
" addedToGraph: %w", err)
}
log.Debugf("Channel(%v) with ShortChanID %v: successfully "+
"added to router graph", chanID, scid)
"added to graph", chanID, scid)
// Give the caller a final update notifying them that the channel is
fundingPoint := channel.FundingOutpoint
@ -4347,17 +4347,17 @@ func (f *Manager) announceChannel(localIDKey, remoteIDKey *btcec.PublicKey,
}
// We only send the channel proof announcement and the node announcement
// because addToRouterGraph previously sent the ChannelAnnouncement and
// because addToGraph previously sent the ChannelAnnouncement and
// the ChannelUpdate announcement messages. The channel proof and node
// announcements are broadcast to the greater network.
errChan := f.cfg.SendAnnouncement(ann.chanProof)
select {
case err := <-errChan:
if err != nil {
if routing.IsError(err, routing.ErrOutdated,
routing.ErrIgnored) {
if graph.IsError(err, graph.ErrOutdated,
graph.ErrIgnored) {
log.Debugf("Router rejected "+
log.Debugf("Graph rejected "+
"AnnounceSignatures: %v", err)
} else {
log.Errorf("Unable to send channel "+
@ -4384,10 +4384,10 @@ func (f *Manager) announceChannel(localIDKey, remoteIDKey *btcec.PublicKey,
select {
case err := <-errChan:
if err != nil {
if routing.IsError(err, routing.ErrOutdated,
routing.ErrIgnored) {
if graph.IsError(err, graph.ErrOutdated,
graph.ErrIgnored) {
log.Debugf("Router rejected "+
log.Debugf("Graph rejected "+
"NodeAnnouncement: %v", err)
} else {
log.Errorf("Unable to send node "+

View file

@ -1140,13 +1140,13 @@ func assertChannelReadySent(t *testing.T, alice, bob *testNode,
assertDatabaseState(t, bob, fundingOutPoint, channelReadySent)
}
func assertAddedToRouterGraph(t *testing.T, alice, bob *testNode,
func assertAddedToGraph(t *testing.T, alice, bob *testNode,
fundingOutPoint *wire.OutPoint) {
t.Helper()
assertDatabaseState(t, alice, fundingOutPoint, addedToRouterGraph)
assertDatabaseState(t, bob, fundingOutPoint, addedToRouterGraph)
assertDatabaseState(t, alice, fundingOutPoint, addedToGraph)
assertDatabaseState(t, bob, fundingOutPoint, addedToGraph)
}
// assertChannelAnnouncements checks that alice and bob both sends the expected
@ -1523,7 +1523,7 @@ func testNormalWorkflow(t *testing.T, chanType *lnwire.ChannelType) {
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
// Check that the state machine is updated accordingly
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
assertAddedToGraph(t, alice, bob, fundingOutPoint)
// The funding transaction is now confirmed, wait for the
// OpenStatusUpdate_ChanOpen update
@ -1877,7 +1877,7 @@ func TestFundingManagerRestartBehavior(t *testing.T) {
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
// Check that the state machine is updated accordingly
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
assertAddedToGraph(t, alice, bob, fundingOutPoint)
// Next, we check that Alice sends the announcement signatures
// on restart after six confirmations. Bob should as expected send
@ -2042,7 +2042,7 @@ func TestFundingManagerOfflinePeer(t *testing.T) {
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
// Check that the state machine is updated accordingly
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
assertAddedToGraph(t, alice, bob, fundingOutPoint)
// The funding transaction is now confirmed, wait for the
// OpenStatusUpdate_ChanOpen update
@ -2501,7 +2501,7 @@ func TestFundingManagerReceiveChannelReadyTwice(t *testing.T) {
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
// Check that the state machine is updated accordingly
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
assertAddedToGraph(t, alice, bob, fundingOutPoint)
// The funding transaction is now confirmed, wait for the
// OpenStatusUpdate_ChanOpen update
@ -2594,7 +2594,7 @@ func TestFundingManagerRestartAfterChanAnn(t *testing.T) {
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
// Check that the state machine is updated accordingly
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
assertAddedToGraph(t, alice, bob, fundingOutPoint)
// The funding transaction is now confirmed, wait for the
// OpenStatusUpdate_ChanOpen update
@ -2698,7 +2698,7 @@ func TestFundingManagerRestartAfterReceivingChannelReady(t *testing.T) {
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
// Check that the state machine is updated accordingly
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
assertAddedToGraph(t, alice, bob, fundingOutPoint)
// Notify that six confirmations has been reached on funding
// transaction.
@ -2912,9 +2912,9 @@ func TestFundingManagerPrivateRestart(t *testing.T) {
// announcements.
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
// Note: We don't check for the addedToRouterGraph state because in
// Note: We don't check for the addedToGraph state because in
// the private channel mode, the state is quickly changed from
// addedToRouterGraph to deleted from the database since the public
// addedToGraph to deleted from the database since the public
// announcement phase is skipped.
// The funding transaction is now confirmed, wait for the
@ -4563,8 +4563,8 @@ func testZeroConf(t *testing.T, chanType *lnwire.ChannelType) {
// We'll now wait for the OpenStatusUpdate_ChanOpen update.
waitForOpenUpdate(t, updateChan)
// Assert that both Alice & Bob are in the addedToRouterGraph state.
assertAddedToRouterGraph(t, alice, bob, fundingOp)
// Assert that both Alice & Bob are in the addedToGraph state.
assertAddedToGraph(t, alice, bob, fundingOp)
// We'll now restart Alice's funding manager and assert that the tx
// is rebroadcast.

View file

@ -1,4 +1,4 @@
package routing
package graph
import (
"bytes"

1837
graph/builder.go Normal file

File diff suppressed because it is too large Load diff

2073
graph/builder_test.go Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
package routing
package graph
import "github.com/go-errors/errors"
@ -39,27 +39,27 @@ const (
ErrParentValidationFailed
)
// routerError is a structure that represent the error inside the routing package,
// graphError is a structure that represent the error inside the graph package,
// this structure carries additional information about error code in order to
// be able distinguish errors outside of the current package.
type routerError struct {
type graphError struct {
err *errors.Error
code errorCode
}
// Error represents errors as the string
// NOTE: Part of the error interface.
func (e *routerError) Error() string {
func (e *graphError) Error() string {
return e.err.Error()
}
// A compile time check to ensure routerError implements the error interface.
var _ error = (*routerError)(nil)
// A compile time check to ensure graphError implements the error interface.
var _ error = (*graphError)(nil)
// newErrf creates a routerError by the given error formatted description and
// newErrf creates a graphError by the given error formatted description and
// its corresponding error code.
func newErrf(code errorCode, format string, a ...interface{}) *routerError {
return &routerError{
func newErrf(code errorCode, format string, a ...interface{}) *graphError {
return &graphError{
code: code,
err: errors.Errorf(format, a...),
}
@ -68,7 +68,7 @@ func newErrf(code errorCode, format string, a ...interface{}) *routerError {
// IsError is a helper function which is needed to have ability to check that
// returned error has specific error code.
func IsError(e interface{}, codes ...errorCode) bool {
err, ok := e.(*routerError)
err, ok := e.(*graphError)
if !ok {
return false
}

280
graph/interfaces.go Normal file
View file

@ -0,0 +1,280 @@
package graph
import (
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/batch"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
// ChannelGraphSource represents the source of information about the topology
// of the lightning network. It's responsible for the addition of nodes, edges,
// applying edge updates, and returning the current block height with which the
// topology is synchronized.
//
//nolint:interfacebloat
type ChannelGraphSource interface {
// AddNode is used to add information about a node to the router
// database. If the node with this pubkey is not present in an existing
// channel, it will be ignored.
AddNode(node *channeldb.LightningNode,
op ...batch.SchedulerOption) error
// AddEdge is used to add edge/channel to the topology of the router,
// after all information about channel will be gathered this
// edge/channel might be used in construction of payment path.
AddEdge(edge *models.ChannelEdgeInfo,
op ...batch.SchedulerOption) error
// AddProof updates the channel edge info with proof which is needed to
// properly announce the edge to the rest of the network.
AddProof(chanID lnwire.ShortChannelID,
proof *models.ChannelAuthProof) error
// UpdateEdge is used to update edge information, without this message
// edge considered as not fully constructed.
UpdateEdge(policy *models.ChannelEdgePolicy,
op ...batch.SchedulerOption) error
// IsStaleNode returns true if the graph source has a node announcement
// for the target node with a more recent timestamp. This method will
// also return true if we don't have an active channel announcement for
// the target node.
IsStaleNode(node route.Vertex, timestamp time.Time) bool
// IsPublicNode determines whether the given vertex is seen as a public
// node in the graph from the graph's source node's point of view.
IsPublicNode(node route.Vertex) (bool, error)
// IsKnownEdge returns true if the graph source already knows of the
// passed channel ID either as a live or zombie edge.
IsKnownEdge(chanID lnwire.ShortChannelID) bool
// IsStaleEdgePolicy returns true if the graph source has a channel
// edge for the passed channel ID (and flags) that have a more recent
// timestamp.
IsStaleEdgePolicy(chanID lnwire.ShortChannelID, timestamp time.Time,
flags lnwire.ChanUpdateChanFlags) bool
// MarkEdgeLive clears an edge from our zombie index, deeming it as
// live.
MarkEdgeLive(chanID lnwire.ShortChannelID) error
// ForAllOutgoingChannels is used to iterate over all channels
// emanating from the "source" node which is the center of the
// star-graph.
ForAllOutgoingChannels(cb func(tx kvdb.RTx,
c *models.ChannelEdgeInfo,
e *models.ChannelEdgePolicy) error) error
// CurrentBlockHeight returns the block height from POV of the router
// subsystem.
CurrentBlockHeight() (uint32, error)
// GetChannelByID return the channel by the channel id.
GetChannelByID(chanID lnwire.ShortChannelID) (
*models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
*models.ChannelEdgePolicy, error)
// FetchLightningNode attempts to look up a target node by its identity
// public key. channeldb.ErrGraphNodeNotFound is returned if the node
// doesn't exist within the graph.
FetchLightningNode(route.Vertex) (*channeldb.LightningNode, error)
// ForEachNode is used to iterate over every node in the known graph.
ForEachNode(func(node *channeldb.LightningNode) error) error
}
// DB is an interface describing a persisted Lightning Network graph.
//
//nolint:interfacebloat
type DB interface {
// PruneTip returns the block height and hash of the latest block that
// has been used to prune channels in the graph. Knowing the "prune tip"
// allows callers to tell if the graph is currently in sync with the
// current best known UTXO state.
PruneTip() (*chainhash.Hash, uint32, error)
// PruneGraph prunes newly closed channels from the channel graph in
// response to a new block being solved on the network. Any transactions
// which spend the funding output of any known channels within the graph
// will be deleted. Additionally, the "prune tip", or the last block
// which has been used to prune the graph is stored so callers can
// ensure the graph is fully in sync with the current UTXO state. A
// slice of channels that have been closed by the target block are
// returned if the function succeeds without error.
PruneGraph(spentOutputs []*wire.OutPoint, blockHash *chainhash.Hash,
blockHeight uint32) ([]*models.ChannelEdgeInfo, error)
// ChannelView returns the verifiable edge information for each active
// channel within the known channel graph. The set of UTXO's (along with
// their scripts) returned are the ones that need to be watched on
// chain to detect channel closes on the resident blockchain.
ChannelView() ([]channeldb.EdgePoint, error)
// PruneGraphNodes is a garbage collection method which attempts to
// prune out any nodes from the channel graph that are currently
// unconnected. This ensure that we only maintain a graph of reachable
// nodes. In the event that a pruned node gains more channels, it will
// be re-added back to the graph.
PruneGraphNodes() error
// SourceNode returns the source node of the graph. The source node is
// treated as the center node within a star-graph. This method may be
// used to kick off a path finding algorithm in order to explore the
// reachability of another node based off the source node.
SourceNode() (*channeldb.LightningNode, error)
// DisabledChannelIDs returns the channel ids of disabled channels.
// A channel is disabled when two of the associated ChanelEdgePolicies
// have their disabled bit on.
DisabledChannelIDs() ([]uint64, error)
// FetchChanInfos returns the set of channel edges that correspond to
// the passed channel ID's. If an edge is the query is unknown to the
// database, it will skipped and the result will contain only those
// edges that exist at the time of the query. This can be used to
// respond to peer queries that are seeking to fill in gaps in their
// view of the channel graph.
FetchChanInfos(chanIDs []uint64) ([]channeldb.ChannelEdge, error)
// ChanUpdatesInHorizon returns all the known channel edges which have
// at least one edge that has an update timestamp within the specified
// horizon.
ChanUpdatesInHorizon(startTime, endTime time.Time) (
[]channeldb.ChannelEdge, error)
// DeleteChannelEdges removes edges with the given channel IDs from the
// database and marks them as zombies. This ensures that we're unable to
// re-add it to our database once again. If an edge does not exist
// within the database, then ErrEdgeNotFound will be returned. If
// strictZombiePruning is true, then when we mark these edges as
// zombies, we'll set up the keys such that we require the node that
// failed to send the fresh update to be the one that resurrects the
// channel from its zombie state. The markZombie bool denotes whether
// to mark the channel as a zombie.
DeleteChannelEdges(strictZombiePruning, markZombie bool,
chanIDs ...uint64) error
// DisconnectBlockAtHeight is used to indicate that the block specified
// by the passed height has been disconnected from the main chain. This
// will "rewind" the graph back to the height below, deleting channels
// that are no longer confirmed from the graph. The prune log will be
// set to the last prune height valid for the remaining chain.
// Channels that were removed from the graph resulting from the
// disconnected block are returned.
DisconnectBlockAtHeight(height uint32) ([]*models.ChannelEdgeInfo,
error)
// HasChannelEdge returns true if the database knows of a channel edge
// with the passed channel ID, and false otherwise. If an edge with that
// ID is found within the graph, then two time stamps representing the
// last time the edge was updated for both directed edges are returned
// along with the boolean. If it is not found, then the zombie index is
// checked and its result is returned as the second boolean.
HasChannelEdge(chanID uint64) (time.Time, time.Time, bool, bool, error)
// FetchChannelEdgesByID attempts to lookup the two directed edges for
// the channel identified by the channel ID. If the channel can't be
// found, then ErrEdgeNotFound is returned. A struct which houses the
// general information for the channel itself is returned as well as
// two structs that contain the routing policies for the channel in
// either direction.
//
// ErrZombieEdge an be returned if the edge is currently marked as a
// zombie within the database. In this case, the ChannelEdgePolicy's
// will be nil, and the ChannelEdgeInfo will only include the public
// keys of each node.
FetchChannelEdgesByID(chanID uint64) (*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy, *models.ChannelEdgePolicy, error)
// AddLightningNode adds a vertex/node to the graph database. If the
// node is not in the database from before, this will add a new,
// unconnected one to the graph. If it is present from before, this will
// update that node's information. Note that this method is expected to
// only be called to update an already present node from a node
// announcement, or to insert a node found in a channel update.
AddLightningNode(node *channeldb.LightningNode,
op ...batch.SchedulerOption) error
// AddChannelEdge adds a new (undirected, blank) edge to the graph
// database. An undirected edge from the two target nodes are created.
// The information stored denotes the static attributes of the channel,
// such as the channelID, the keys involved in creation of the channel,
// and the set of features that the channel supports. The chanPoint and
// chanID are used to uniquely identify the edge globally within the
// database.
AddChannelEdge(edge *models.ChannelEdgeInfo,
op ...batch.SchedulerOption) error
// MarkEdgeZombie attempts to mark a channel identified by its channel
// ID as a zombie. This method is used on an ad-hoc basis, when channels
// need to be marked as zombies outside the normal pruning cycle.
MarkEdgeZombie(chanID uint64, pubKey1, pubKey2 [33]byte) error
// UpdateEdgePolicy updates the edge routing policy for a single
// directed edge within the database for the referenced channel. The
// `flags` attribute within the ChannelEdgePolicy determines which of
// the directed edges are being updated. If the flag is 1, then the
// first node's information is being updated, otherwise it's the second
// node's information. The node ordering is determined by the
// lexicographical ordering of the identity public keys of the nodes on
// either side of the channel.
UpdateEdgePolicy(edge *models.ChannelEdgePolicy,
op ...batch.SchedulerOption) error
// HasLightningNode determines if the graph has a vertex identified by
// the target node identity public key. If the node exists in the
// database, a timestamp of when the data for the node was lasted
// updated is returned along with a true boolean. Otherwise, an empty
// time.Time is returned with a false boolean.
HasLightningNode(nodePub [33]byte) (time.Time, bool, error)
// FetchLightningNode attempts to look up a target node by its identity
// public key. If the node isn't found in the database, then
// ErrGraphNodeNotFound is returned.
FetchLightningNode(nodePub route.Vertex) (*channeldb.LightningNode,
error)
// ForEachNode iterates through all the stored vertices/nodes in the
// graph, executing the passed callback with each node encountered. If
// the callback returns an error, then the transaction is aborted and
// the iteration stops early.
ForEachNode(cb func(kvdb.RTx, *channeldb.LightningNode) error) error
// ForEachNodeChannel iterates through all channels of the given node,
// executing the passed callback with an edge info structure and the
// policies of each end of the channel. The first edge policy is the
// outgoing edge *to* the connecting node, while the second is the
// incoming edge *from* the connecting node. If the callback returns an
// error, then the iteration is halted with the error propagated back up
// to the caller.
//
// Unknown policies are passed into the callback as nil values.
ForEachNodeChannel(nodePub route.Vertex, cb func(kvdb.RTx,
*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy,
*models.ChannelEdgePolicy) error) error
// UpdateChannelEdge retrieves and update edge of the graph database.
// Method only reserved for updating an edge info after its already been
// created. In order to maintain this constraints, we return an error in
// the scenario that an edge info hasn't yet been created yet, but
// someone attempts to update it.
UpdateChannelEdge(edge *models.ChannelEdgeInfo) error
// IsPublicNode is a helper method that determines whether the node with
// the given public key is seen as a public node in the graph from the
// graph's source node's point of view.
IsPublicNode(pubKey [33]byte) (bool, error)
// MarkEdgeLive clears an edge from our zombie index, deeming it as
// live.
MarkEdgeLive(chanID uint64) error
}

47
graph/log.go Normal file
View file

@ -0,0 +1,47 @@
package graph
import (
"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This means the
// package will not perform any logging by default until the caller requests
// it.
var log btclog.Logger
const Subsystem = "GRPH"
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info. This
// should be used in preference to SetLogWriter if the caller is also using
// btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
// logClosure is used to provide a closure over expensive logging operations so
// don't have to be performed when the logging level doesn't warrant it.
type logClosure func() string
// String invokes the underlying function and returns the result.
func (c logClosure) String() string {
return c()
}
// newLogClosure returns a new closure over a function that returns a string
// which itself provides a Stringer interface so that it can be used with the
// logging system.
func newLogClosure(c func() string) logClosure {
return logClosure(c)
}

View file

@ -1,11 +1,10 @@
package routing
package graph
import (
"fmt"
"image/color"
"net"
"sync"
"sync/atomic"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
@ -56,16 +55,16 @@ type topologyClientUpdate struct {
// topology occurs. Changes that will be sent at notifications include: new
// nodes appearing, node updating their attributes, new channels, channels
// closing, and updates in the routing policies of a channel's directed edges.
func (r *ChannelRouter) SubscribeTopology() (*TopologyClient, error) {
func (b *Builder) SubscribeTopology() (*TopologyClient, error) {
// If the router is not yet started, return an error to avoid a
// deadlock waiting for it to handle the subscription request.
if atomic.LoadUint32(&r.started) == 0 {
if !b.started.Load() {
return nil, fmt.Errorf("router not started")
}
// We'll first atomically obtain the next ID for this client from the
// incrementing client ID counter.
clientID := atomic.AddUint64(&r.ntfnClientCounter, 1)
clientID := b.ntfnClientCounter.Add(1)
log.Debugf("New graph topology client subscription, client %v",
clientID)
@ -73,12 +72,12 @@ func (r *ChannelRouter) SubscribeTopology() (*TopologyClient, error) {
ntfnChan := make(chan *TopologyChange, 10)
select {
case r.ntfnClientUpdates <- &topologyClientUpdate{
case b.ntfnClientUpdates <- &topologyClientUpdate{
cancel: false,
clientID: clientID,
ntfnChan: ntfnChan,
}:
case <-r.quit:
case <-b.quit:
return nil, errors.New("ChannelRouter shutting down")
}
@ -86,11 +85,11 @@ func (r *ChannelRouter) SubscribeTopology() (*TopologyClient, error) {
TopologyChanges: ntfnChan,
Cancel: func() {
select {
case r.ntfnClientUpdates <- &topologyClientUpdate{
case b.ntfnClientUpdates <- &topologyClientUpdate{
cancel: true,
clientID: clientID,
}:
case <-r.quit:
case <-b.quit:
return
}
},
@ -116,8 +115,7 @@ type topologyClient struct {
// notifyTopologyChange notifies all registered clients of a new change in
// graph topology in a non-blocking.
func (r *ChannelRouter) notifyTopologyChange(topologyDiff *TopologyChange) {
func (b *Builder) notifyTopologyChange(topologyDiff *TopologyChange) {
// notifyClient is a helper closure that will send topology updates to
// the given client.
notifyClient := func(clientID uint64, client *topologyClient) bool {
@ -145,7 +143,7 @@ func (r *ChannelRouter) notifyTopologyChange(topologyDiff *TopologyChange) {
// Similarly, if the ChannelRouter itself exists early,
// then we'll also exit ourselves.
case <-r.quit:
case <-b.quit:
}
}(client)
@ -157,7 +155,7 @@ func (r *ChannelRouter) notifyTopologyChange(topologyDiff *TopologyChange) {
// Range over the set of active clients, and attempt to send the
// topology updates.
r.topologyClients.Range(notifyClient)
b.topologyClients.Range(notifyClient)
}
// TopologyChange represents a new set of modifications to the channel graph.
@ -313,7 +311,7 @@ type ChannelEdgeUpdate struct {
// constitutes. This function will also fetch any required auxiliary
// information required to create the topology change update from the graph
// database.
func addToTopologyChange(graph *channeldb.ChannelGraph, update *TopologyChange,
func addToTopologyChange(graph DB, update *TopologyChange,
msg interface{}) error {
switch m := msg.(type) {

View file

@ -1,7 +1,8 @@
package routing
package graph
import (
"bytes"
"encoding/hex"
"fmt"
"image/color"
prand "math/rand"
@ -11,13 +12,17 @@ import (
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/kvdb"
lnmock "github.com/lightningnetwork/lnd/lntest/mock"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/lnwire"
@ -49,15 +54,34 @@ var (
bitcoinKey2 = priv2.PubKey()
timeout = time.Second * 5
testRBytes, _ = hex.DecodeString(
"8ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa1bf03" +
"14f882d7",
)
testSBytes, _ = hex.DecodeString(
"299105481d63e0f4bc2a88121167221b6700d72a0ead154c03be696a2" +
"92d24ae",
)
testRScalar = new(btcec.ModNScalar)
testSScalar = new(btcec.ModNScalar)
_ = testRScalar.SetByteSlice(testRBytes)
_ = testSScalar.SetByteSlice(testSBytes)
testSig = ecdsa.NewSignature(testRScalar, testSScalar)
testAuthProof = models.ChannelAuthProof{
NodeSig1Bytes: testSig.Serialize(),
NodeSig2Bytes: testSig.Serialize(),
BitcoinSig1Bytes: testSig.Serialize(),
BitcoinSig2Bytes: testSig.Serialize(),
}
)
func createTestNode() (*channeldb.LightningNode, error) {
func createTestNode(t *testing.T) *channeldb.LightningNode {
updateTime := prand.Int63()
priv, err := btcec.NewPrivateKey()
if err != nil {
return nil, errors.Errorf("unable create private key: %v", err)
}
require.NoError(t, err)
pub := priv.PubKey().SerializeCompressed()
n := &channeldb.LightningNode{
@ -71,7 +95,7 @@ func createTestNode() (*channeldb.LightningNode, error) {
}
copy(n.PubKeyBytes[:], pub)
return n, nil
return n
}
func randEdgePolicy(chanID *lnwire.ShortChannelID,
@ -271,7 +295,7 @@ type mockChainView struct {
}
// A compile time check to ensure mockChainView implements the
// chainview.FilteredChainView.
// chainview.FilteredChainViewReader.
var _ chainview.FilteredChainView = (*mockChainView)(nil)
func newMockChainView(chain lnwallet.BlockChainIO) *mockChainView {
@ -302,6 +326,15 @@ func (m *mockChainView) UpdateFilter(ops []channeldb.EdgePoint, updateHeight uin
return nil
}
func (m *mockChainView) Start() error {
return nil
}
func (m *mockChainView) Stop() error {
close(m.quit)
return nil
}
func (m *mockChainView) notifyBlock(hash chainhash.Hash, height uint32,
txns []*wire.MsgTx, t *testing.T) {
@ -405,15 +438,6 @@ func (m *mockChainView) FilterBlock(blockHash *chainhash.Hash) (*chainview.Filte
return filteredBlock, nil
}
func (m *mockChainView) Start() error {
return nil
}
func (m *mockChainView) Stop() error {
close(m.quit)
return nil
}
// TestEdgeUpdateNotification tests that when edges are updated or added,
// a proper notification is sent of to all registered clients.
func TestEdgeUpdateNotification(t *testing.T) {
@ -437,10 +461,8 @@ func TestEdgeUpdateNotification(t *testing.T) {
// Next we'll create two test nodes that the fake channel will be open
// between.
node1, err := createTestNode()
require.NoError(t, err, "unable to create test node")
node2, err := createTestNode()
require.NoError(t, err, "unable to create test node")
node1 := createTestNode(t)
node2 := createTestNode(t)
// Finally, to conclude our test set up, we'll create a channel
// update to announce the created channel between the two nodes.
@ -458,13 +480,13 @@ func TestEdgeUpdateNotification(t *testing.T) {
copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
if err := ctx.router.AddEdge(edge); err != nil {
if err := ctx.builder.AddEdge(edge); err != nil {
t.Fatalf("unable to add edge: %v", err)
}
// With the channel edge now in place, we'll subscribe for topology
// notifications.
ntfnClient, err := ctx.router.SubscribeTopology()
ntfnClient, err := ctx.builder.SubscribeTopology()
require.NoError(t, err, "unable to subscribe for channel notifications")
// Create random policy edges that are stemmed to the channel id
@ -477,10 +499,10 @@ func TestEdgeUpdateNotification(t *testing.T) {
require.NoError(t, err, "unable to create a random chan policy")
edge2.ChannelFlags = 1
if err := ctx.router.UpdateEdge(edge1); err != nil {
if err := ctx.builder.UpdateEdge(edge1); err != nil {
t.Fatalf("unable to add edge update: %v", err)
}
if err := ctx.router.UpdateEdge(edge2); err != nil {
if err := ctx.builder.UpdateEdge(edge2); err != nil {
t.Fatalf("unable to add edge update: %v", err)
}
@ -625,10 +647,8 @@ func TestNodeUpdateNotification(t *testing.T) {
// Create two nodes acting as endpoints in the created channel, and use
// them to trigger notifications by sending updated node announcement
// messages.
node1, err := createTestNode()
require.NoError(t, err, "unable to create test node")
node2, err := createTestNode()
require.NoError(t, err, "unable to create test node")
node1 := createTestNode(t)
node2 := createTestNode(t)
testFeaturesBuf := new(bytes.Buffer)
require.NoError(t, testFeatures.Encode(testFeaturesBuf))
@ -649,20 +669,20 @@ func TestNodeUpdateNotification(t *testing.T) {
// Adding the edge will add the nodes to the graph, but with no info
// except the pubkey known.
if err := ctx.router.AddEdge(edge); err != nil {
if err := ctx.builder.AddEdge(edge); err != nil {
t.Fatalf("unable to add edge: %v", err)
}
// Create a new client to receive notifications.
ntfnClient, err := ctx.router.SubscribeTopology()
ntfnClient, err := ctx.builder.SubscribeTopology()
require.NoError(t, err, "unable to subscribe for channel notifications")
// Change network topology by adding the updated info for the two nodes
// to the channel router.
if err := ctx.router.AddNode(node1); err != nil {
if err := ctx.builder.AddNode(node1); err != nil {
t.Fatalf("unable to add node: %v", err)
}
if err := ctx.router.AddNode(node2); err != nil {
if err := ctx.builder.AddNode(node2); err != nil {
t.Fatalf("unable to add node: %v", err)
}
@ -756,7 +776,7 @@ func TestNodeUpdateNotification(t *testing.T) {
nodeUpdateAnn.LastUpdate = node1.LastUpdate.Add(300 * time.Millisecond)
// Add new node topology update to the channel router.
if err := ctx.router.AddNode(&nodeUpdateAnn); err != nil {
if err := ctx.builder.AddNode(&nodeUpdateAnn); err != nil {
t.Fatalf("unable to add node: %v", err)
}
@ -788,7 +808,7 @@ func TestNotificationCancellation(t *testing.T) {
ctx := createTestCtxSingleNode(t, startingBlockHeight)
// Create a new client to receive notifications.
ntfnClient, err := ctx.router.SubscribeTopology()
ntfnClient, err := ctx.builder.SubscribeTopology()
require.NoError(t, err, "unable to subscribe for channel notifications")
// We'll create the utxo for a new channel.
@ -808,10 +828,8 @@ func TestNotificationCancellation(t *testing.T) {
// We'll create a fresh new node topology update to feed to the channel
// router.
node1, err := createTestNode()
require.NoError(t, err, "unable to create test node")
node2, err := createTestNode()
require.NoError(t, err, "unable to create test node")
node1 := createTestNode(t)
node2 := createTestNode(t)
// Before we send the message to the channel router, we'll cancel the
// notifications for this client. As a result, the notification
@ -832,15 +850,15 @@ func TestNotificationCancellation(t *testing.T) {
}
copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
if err := ctx.router.AddEdge(edge); err != nil {
if err := ctx.builder.AddEdge(edge); err != nil {
t.Fatalf("unable to add edge: %v", err)
}
if err := ctx.router.AddNode(node1); err != nil {
if err := ctx.builder.AddNode(node1); err != nil {
t.Fatalf("unable to add node: %v", err)
}
if err := ctx.router.AddNode(node2); err != nil {
if err := ctx.builder.AddNode(node2); err != nil {
t.Fatalf("unable to add node: %v", err)
}
@ -883,10 +901,8 @@ func TestChannelCloseNotification(t *testing.T) {
// Next we'll create two test nodes that the fake channel will be open
// between.
node1, err := createTestNode()
require.NoError(t, err, "unable to create test node")
node2, err := createTestNode()
require.NoError(t, err, "unable to create test node")
node1 := createTestNode(t)
node2 := createTestNode(t)
// Finally, to conclude our test set up, we'll create a channel
// announcement to announce the created channel between the two nodes.
@ -903,13 +919,13 @@ func TestChannelCloseNotification(t *testing.T) {
}
copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed())
copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed())
if err := ctx.router.AddEdge(edge); err != nil {
if err := ctx.builder.AddEdge(edge); err != nil {
t.Fatalf("unable to add edge: %v", err)
}
// With the channel edge now in place, we'll subscribe for topology
// notifications.
ntfnClient, err := ctx.router.SubscribeTopology()
ntfnClient, err := ctx.builder.SubscribeTopology()
require.NoError(t, err, "unable to subscribe for channel notifications")
// Next, we'll simulate the closure of our channel by generating a new
@ -999,3 +1015,184 @@ func TestEncodeHexColor(t *testing.T) {
}
}
}
type testCtx struct {
builder *Builder
graph *channeldb.ChannelGraph
aliases map[string]route.Vertex
privKeys map[string]*btcec.PrivateKey
channelIDs map[route.Vertex]map[route.Vertex]uint64
chain *mockChain
chainView *mockChainView
notifier *lnmock.ChainNotifier
}
func createTestCtxSingleNode(t *testing.T,
startingHeight uint32) *testCtx {
graph, graphBackend, err := makeTestGraph(t, true)
require.NoError(t, err, "failed to make test graph")
sourceNode := createTestNode(t)
require.NoError(t,
graph.SetSourceNode(sourceNode), "failed to set source node",
)
graphInstance := &testGraphInstance{
graph: graph,
graphBackend: graphBackend,
}
return createTestCtxFromGraphInstance(
t, startingHeight, graphInstance, false,
)
}
func (c *testCtx) RestartBuilder(t *testing.T) {
c.chainView.Reset()
selfNode, err := c.graph.SourceNode()
require.NoError(t, err)
// With the chainView reset, we'll now re-create the builder itself, and
// start it.
builder, err := NewBuilder(&Config{
SelfNode: selfNode.PubKeyBytes,
Graph: c.graph,
Chain: c.chain,
ChainView: c.chainView,
Notifier: c.builder.cfg.Notifier,
ChannelPruneExpiry: time.Hour * 24,
GraphPruneInterval: time.Hour * 2,
AssumeChannelValid: c.builder.cfg.AssumeChannelValid,
FirstTimePruneDelay: c.builder.cfg.FirstTimePruneDelay,
StrictZombiePruning: c.builder.cfg.StrictZombiePruning,
IsAlias: func(scid lnwire.ShortChannelID) bool {
return false
},
})
require.NoError(t, err)
require.NoError(t, builder.Start())
// Finally, we'll swap out the pointer in the testCtx with this fresh
// instance of the router.
c.builder = builder
}
// makeTestGraph creates a new instance of a channeldb.ChannelGraph for testing
// purposes.
func makeTestGraph(t *testing.T, useCache bool) (*channeldb.ChannelGraph,
kvdb.Backend, error) {
// Create channelgraph for the first time.
backend, backendCleanup, err := kvdb.GetTestBackend(t.TempDir(), "cgr")
if err != nil {
return nil, nil, err
}
t.Cleanup(backendCleanup)
opts := channeldb.DefaultOptions()
graph, err := channeldb.NewChannelGraph(
backend, opts.RejectCacheSize, opts.ChannelCacheSize,
opts.BatchCommitInterval, opts.PreAllocCacheNumNodes,
useCache, false,
)
if err != nil {
return nil, nil, err
}
return graph, backend, nil
}
type testGraphInstance struct {
graph *channeldb.ChannelGraph
graphBackend kvdb.Backend
// aliasMap is a map from a node's alias to its public key. This type is
// provided in order to allow easily look up from the human memorable
// alias to an exact node's public key.
aliasMap map[string]route.Vertex
// privKeyMap maps a node alias to its private key. This is used to be
// able to mock a remote node's signing behaviour.
privKeyMap map[string]*btcec.PrivateKey
// channelIDs stores the channel ID for each node.
channelIDs map[route.Vertex]map[route.Vertex]uint64
// links maps channel ids to a mock channel update handler.
links map[lnwire.ShortChannelID]htlcswitch.ChannelLink
}
func createTestCtxFromGraphInstance(t *testing.T,
startingHeight uint32, graphInstance *testGraphInstance,
strictPruning bool) *testCtx {
return createTestCtxFromGraphInstanceAssumeValid(
t, startingHeight, graphInstance, false, strictPruning,
)
}
func createTestCtxFromGraphInstanceAssumeValid(t *testing.T,
startingHeight uint32, graphInstance *testGraphInstance,
assumeValid bool, strictPruning bool) *testCtx {
// We'll initialize an instance of the channel router with mock
// versions of the chain and channel notifier. As we don't need to test
// any p2p functionality, the peer send and switch send messages won't
// be populated.
chain := newMockChain(startingHeight)
chainView := newMockChainView(chain)
notifier := &lnmock.ChainNotifier{
EpochChan: make(chan *chainntnfs.BlockEpoch),
SpendChan: make(chan *chainntnfs.SpendDetail),
ConfChan: make(chan *chainntnfs.TxConfirmation),
}
selfnode, err := graphInstance.graph.SourceNode()
require.NoError(t, err)
graphBuilder, err := NewBuilder(&Config{
SelfNode: selfnode.PubKeyBytes,
Graph: graphInstance.graph,
Chain: chain,
ChainView: chainView,
Notifier: notifier,
ChannelPruneExpiry: time.Hour * 24,
GraphPruneInterval: time.Hour * 2,
AssumeChannelValid: assumeValid,
FirstTimePruneDelay: 0,
StrictZombiePruning: strictPruning,
IsAlias: func(scid lnwire.ShortChannelID) bool {
return false
},
})
require.NoError(t, err)
require.NoError(t, graphBuilder.Start())
ctx := &testCtx{
builder: graphBuilder,
graph: graphInstance.graph,
aliases: graphInstance.aliasMap,
privKeys: graphInstance.privKeyMap,
channelIDs: graphInstance.channelIDs,
chain: chain,
chainView: chainView,
notifier: notifier,
}
t.Cleanup(func() {
require.NoError(t, graphBuilder.Stop())
})
return ctx
}

11
graph/setup_test.go Normal file
View file

@ -0,0 +1,11 @@
package graph
import (
"testing"
"github.com/lightningnetwork/lnd/kvdb"
)
func TestMain(m *testing.M) {
kvdb.RunTests(m)
}

View file

@ -1,4 +1,4 @@
package routing
package graph
import (
"fmt"

298
graph/testdata/basic_graph.json vendored Normal file
View file

@ -0,0 +1,298 @@
{
"info": [
"This file encodes a basic graph that resembles the following ascii graph:",
"",
" 50k satoshis ┌──────┐ ",
" ┌───────────────────▶│luo ji│◀─┐ ",
" │ └──────┘ │ ┌──────┐ ",
" │ │ | elst | ",
" │ │ └──────┘ ",
" │ │ ▲ ",
" │ │ | 100k sat ",
" │ │ ▼ ",
" ▼ │ ┌──────┐ ",
" ┌────────┐ │ │sophon│◀┐ ",
" │satoshi │ │ └──────┘ │ ",
" └────────┘ │ ▲ │ ",
" ▲ │ | │ 110k satoshis ",
" │ ┌───────────────────┘ | │ ",
" │ │ 100k satoshis | │ ",
" │ │ | │ ",
" │ │ 120k sat | │ ┌────────┐ ",
" └──────────┤ (hi fee) ▼ └─▶│son goku│ ",
" 10k satoshis │ ┌────────────┐ └────────┘ ",
" │ | pham nuwen | ▲ ",
" │ └────────────┘ │ ",
" │ ▲ │ ",
" ▼ | 120k sat (hi fee) │ ",
" ┌──────────┐ | │ ",
" │ roasbeef │◀──────────────┴──────────────────────┘ ",
" └──────────┘ 100k satoshis ",
" the graph also includes a channel from roasbeef to sophon via pham nuwen"
],
"nodes": [
{
"source": true,
"pubkey": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"alias": "roasbeef"
},
{
"source": false,
"pubkey": "026c43a8ac1cd8519985766e90748e1e06871dab0ff6b8af27e8c1a61640481318",
"privkey": "82b266f659bd83a976bac11b2cc442baec5508e84e61085d7ec2b0fc52156c87",
"alias": "songoku"
},
{
"source": false,
"pubkey": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99",
"alias": "satoshi"
},
{
"source": false,
"pubkey": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd",
"alias": "luoji"
},
{
"source": false,
"pubkey": "036264734b40c9e91d3d990a8cdfbbe23b5b0b7ad3cd0e080a25dcd05d39eeb7eb",
"alias": "sophon"
},
{
"source": false,
"pubkey": "02a1d2856be336a58af08989aea0d8c41e072ccc392c46f8ce0e6e069f002035f3",
"alias": "phamnuwen"
},
{
"source": false,
"pubkey": "02a4b236b69b09b8efe6ccf822fa95ee95a0196451f4d066a450b7489e2e354a64",
"alias": "elst"
}
],
"edges": [
{
"node_1": "02a4b236b69b09b8efe6ccf822fa95ee95a0196451f4d066a450b7489e2e354a64",
"node_2": "036264734b40c9e91d3d990a8cdfbbe23b5b0b7ad3cd0e080a25dcd05d39eeb7eb",
"channel_id": 15433,
"channel_point": "33bd5d49a50e284221561b91e781f1fca0d60341c9f9dd785b5e379a6d88af3d:0",
"channel_flags": 1,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1000,
"max_htlc": 100000000,
"fee_base_msat": 200,
"fee_rate": 0,
"capacity": 100000
},
{
"node_1": "02a4b236b69b09b8efe6ccf822fa95ee95a0196451f4d066a450b7489e2e354a64",
"node_2": "036264734b40c9e91d3d990a8cdfbbe23b5b0b7ad3cd0e080a25dcd05d39eeb7eb",
"channel_id": 15433,
"channel_point": "33bd5d49a50e284221561b91e781f1fca0d60341c9f9dd785b5e379a6d88af3d:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1000,
"max_htlc": 100000000,
"fee_base_msat": 200,
"fee_rate": 0,
"capacity": 100000
},
{
"node_1": "02a1d2856be336a58af08989aea0d8c41e072ccc392c46f8ce0e6e069f002035f3",
"node_2": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"channel_id": 999991,
"channel_point": "48a0e8b856fef01d9feda7d25a4fac6dae48749e28ba356b92d712ab7f5bd2d0:0",
"channel_flags": 1,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1000,
"max_htlc": 120000000,
"fee_base_msat": 10000,
"fee_rate": 100000,
"capacity": 120000
},
{
"node_1": "02a1d2856be336a58af08989aea0d8c41e072ccc392c46f8ce0e6e069f002035f3",
"node_2": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"channel_id": 999991,
"channel_point": "48a0e8b856fef01d9feda7d25a4fac6dae48749e28ba356b92d712ab7f5bd2d0:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1000,
"max_htlc": 120000000,
"fee_base_msat": 10000,
"fee_rate": 100000,
"capacity": 120000
},
{
"node_1": "02a1d2856be336a58af08989aea0d8c41e072ccc392c46f8ce0e6e069f002035f3",
"node_2": "036264734b40c9e91d3d990a8cdfbbe23b5b0b7ad3cd0e080a25dcd05d39eeb7eb",
"channel_id": 99999,
"channel_point": "05ffda8890d0a4fffe0ddca0b1932ba0415b1d5868a99515384a4e7883d96b88:0",
"channel_flags": 1,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1000,
"max_htlc": 120000000,
"fee_base_msat": 10000,
"fee_rate": 100000,
"capacity": 120000
},
{
"node_1": "02a1d2856be336a58af08989aea0d8c41e072ccc392c46f8ce0e6e069f002035f3",
"node_2": "036264734b40c9e91d3d990a8cdfbbe23b5b0b7ad3cd0e080a25dcd05d39eeb7eb",
"channel_id": 99999,
"channel_point": "05ffda8890d0a4fffe0ddca0b1932ba0415b1d5868a99515384a4e7883d96b88:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1000,
"max_htlc": 120000000,
"fee_base_msat": 10000,
"fee_rate": 100000,
"capacity": 120000
},
{
"node_1": "026c43a8ac1cd8519985766e90748e1e06871dab0ff6b8af27e8c1a61640481318",
"node_2": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"channel_id": 12345,
"channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 1,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1000,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 1000,
"capacity": 100000
},
{
"node_1": "026c43a8ac1cd8519985766e90748e1e06871dab0ff6b8af27e8c1a61640481318",
"node_2": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"channel_id": 12345,
"channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 1000,
"capacity": 100000
},
{
"node_1": "026c43a8ac1cd8519985766e90748e1e06871dab0ff6b8af27e8c1a61640481318",
"node_2": "036264734b40c9e91d3d990a8cdfbbe23b5b0b7ad3cd0e080a25dcd05d39eeb7eb",
"channel_id": 3495345,
"channel_point": "9f155756b33a0a6827713965babbd561b55f9520444ac5db0cf7cb2eb0deb5bc:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 110000000,
"fee_base_msat": 10,
"fee_rate": 1000,
"capacity": 110000
},
{
"node_1": "026c43a8ac1cd8519985766e90748e1e06871dab0ff6b8af27e8c1a61640481318",
"node_2": "036264734b40c9e91d3d990a8cdfbbe23b5b0b7ad3cd0e080a25dcd05d39eeb7eb",
"channel_id": 3495345,
"channel_point": "9f155756b33a0a6827713965babbd561b55f9520444ac5db0cf7cb2eb0deb5bc:0",
"channel_flags": 1,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 110000000,
"fee_base_msat": 10,
"fee_rate": 1000,
"capacity": 110000
},
{
"node_1": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"node_2": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99",
"channel_id": 2340213491,
"channel_point": "72cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 10000000,
"fee_base_msat": 10,
"fee_rate": 1000,
"capacity": 10000
},
{
"node_1": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"node_2": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99",
"channel_id": 2340213491,
"channel_point": "72cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793:0",
"channel_flags": 1,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 10000000,
"fee_base_msat": 10,
"fee_rate": 1000,
"capacity": 10000
},
{
"node_1": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd",
"node_2": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"channel_id": 689530843,
"channel_point": "25376aa6cb81913ad30416bd22d4083241bd6d68e811d0284d3c3a17795c458a:0",
"channel_flags": 1,
"message_flags": 1,
"expiry": 10,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 1000,
"capacity": 100000
},
{
"node_1": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd",
"node_2": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"channel_id": 689530843,
"channel_point": "25376aa6cb81913ad30416bd22d4083241bd6d68e811d0284d3c3a17795c458a:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 1000,
"capacity": 100000
},
{
"node_1": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd",
"node_2": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99",
"channel_id": 523452362,
"channel_point": "704a5675c91b1c674309a6475fc51072c2913d6117ee6103c9f1b86956bcbe02:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 50000000,
"fee_base_msat": 10,
"fee_rate": 1000,
"capacity": 50000
},
{
"node_1": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd",
"node_2": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99",
"channel_id": 523452362,
"channel_point": "704a5675c91b1c674309a6475fc51072c2913d6117ee6103c9f1b86956bcbe02:0",
"channel_flags": 1,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 50000000,
"fee_base_msat": 10,
"fee_rate": 1000,
"capacity": 50000
}
]
}

147
graph/testdata/spec_example.json vendored Normal file
View file

@ -0,0 +1,147 @@
{
"nodes": [
{
"source": false,
"pubkey": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"alias": "A"
},
{
"source": true,
"pubkey": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add",
"alias": "B"
},
{
"source": false,
"pubkey": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99",
"alias": "C"
},
{
"source": false,
"pubkey": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd",
"alias": "D"
}
],
"edges": [
{
"comment": "A -> B channel",
"node_1": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add",
"node_2": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"channel_id": 12345,
"channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 1,
"message_flags": 1,
"expiry": 10,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 100,
"fee_rate": 1000,
"capacity": 100000
},
{
"comment": "B -> A channel",
"node_1": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add",
"node_2": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"channel_id": 12345,
"channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 20,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 200,
"fee_rate": 2000,
"capacity": 100000
},
{
"comment": "A -> D channel",
"node_1": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd",
"node_2": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"channel_id": 12345839,
"channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 1,
"message_flags": 1,
"expiry": 10,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 100,
"fee_rate": 1000,
"capacity": 100000
},
{
"comment": "D -> A channel",
"node_1": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd",
"node_2": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"channel_id": 12345839,
"channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 40,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 400,
"fee_rate": 4000,
"capacity": 100000
},
{
"comment": "D -> C channel",
"node_1": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd",
"node_2": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99",
"channel_id": 1234583,
"channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 40,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 400,
"fee_rate": 4000,
"capacity": 100000
},
{
"comment": "C -> D channel",
"node_1": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd",
"node_2": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99",
"channel_id": 1234583,
"channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 1,
"message_flags": 1,
"expiry": 30,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 300,
"fee_rate": 3000,
"capacity": 100000
},
{
"comment": "C -> B channel",
"node_1": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add",
"node_2": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99",
"channel_id": 1234589,
"channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 1,
"message_flags": 1,
"expiry": 30,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 300,
"fee_rate": 3000,
"capacity": 100000
},
{
"comment": "B -> C channel",
"node_1": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add",
"node_2": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99",
"channel_id": 1234589,
"channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 20,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 200,
"fee_rate": 2000,
"capacity": 100000
}
]
}

View file

@ -1,4 +1,4 @@
package routing
package graph
import (
"fmt"

View file

@ -1,12 +1,12 @@
package routing_test
package graph_test
import (
"encoding/binary"
"testing"
"time"
"github.com/lightningnetwork/lnd/graph"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing"
)
// TestValidationBarrierSemaphore checks basic properties of the validation
@ -21,7 +21,7 @@ func TestValidationBarrierSemaphore(t *testing.T) {
)
quit := make(chan struct{})
barrier := routing.NewValidationBarrier(numTasks, quit)
barrier := graph.NewValidationBarrier(numTasks, quit)
// Saturate the semaphore with jobs.
for i := 0; i < numTasks; i++ {
@ -69,7 +69,7 @@ func TestValidationBarrierQuit(t *testing.T) {
)
quit := make(chan struct{})
barrier := routing.NewValidationBarrier(2*numTasks, quit)
barrier := graph.NewValidationBarrier(2*numTasks, quit)
// Create a set of unique channel announcements that we will prep for
// validation.
@ -141,8 +141,8 @@ func TestValidationBarrierQuit(t *testing.T) {
switch {
// First half should return without failure.
case i < numTasks/4 && !routing.IsError(
err, routing.ErrParentValidationFailed,
case i < numTasks/4 && !graph.IsError(
err, graph.ErrParentValidationFailed,
):
t.Fatalf("unexpected failure while waiting: %v", err)
@ -150,11 +150,11 @@ func TestValidationBarrierQuit(t *testing.T) {
t.Fatalf("unexpected failure while waiting: %v", err)
// Last half should return the shutdown error.
case i >= numTasks/2 && !routing.IsError(
err, routing.ErrVBarrierShuttingDown,
case i >= numTasks/2 && !graph.IsError(
err, graph.ErrVBarrierShuttingDown,
):
t.Fatalf("expected failure after quitting: want %v, "+
"got %v", routing.ErrVBarrierShuttingDown, err)
"got %v", graph.ErrVBarrierShuttingDown, err)
}
}
}

2
log.go
View file

@ -18,6 +18,7 @@ import (
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/graph"
"github.com/lightningnetwork/lnd/healthcheck"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/invoices"
@ -179,6 +180,7 @@ func SetupLoggers(root *build.RotatingLogWriter, interceptor signal.Interceptor)
AddSubLogger(root, btcwallet.Subsystem, interceptor, btcwallet.UseLogger)
AddSubLogger(root, rpcwallet.Subsystem, interceptor, rpcwallet.UseLogger)
AddSubLogger(root, peersrpc.Subsystem, interceptor, peersrpc.UseLogger)
AddSubLogger(root, graph.Subsystem, interceptor, graph.UseLogger)
}
// AddSubLogger is a helper method to conveniently create and register the

View file

@ -7,11 +7,11 @@ import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/lightningnetwork/lnd/graph"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/routing"
)
type mockSigner struct {
@ -182,7 +182,7 @@ func TestUpdateDisableFlag(t *testing.T) {
// Finally, validate the signature using the router's
// verification logic.
err = routing.VerifyChannelUpdateSignature(
err = graph.VerifyChannelUpdateSignature(
newUpdate, pubKey,
)
if err != nil {

View file

@ -295,6 +295,6 @@ func initAutoPilot(svr *server, cfg *lncfg.AutoPilot,
}, nil
},
SubscribeTransactions: svr.cc.Wallet.SubscribeTransactions,
SubscribeTopology: svr.chanRouter.SubscribeTopology,
SubscribeTopology: svr.graphBuilder.SubscribeTopology,
}, nil
}

View file

@ -39,7 +39,7 @@ type bandwidthManager struct {
// 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 newBandwidthManager(graph routingGraph, sourceNode route.Vertex,
func newBandwidthManager(graph Graph, sourceNode route.Vertex,
linkQuery getLinkQuery) (*bandwidthManager, error) {
manager := &bandwidthManager{
@ -49,7 +49,7 @@ func newBandwidthManager(graph routingGraph, sourceNode route.Vertex,
// First, we'll collect the set of outbound edges from the target
// source node and add them to our bandwidth manager's map of channels.
err := graph.forEachNodeChannel(sourceNode,
err := graph.ForEachNodeChannel(sourceNode,
func(channel *channeldb.DirectedChannel) error {
shortID := lnwire.NewShortChanIDFromInt(
channel.ChannelID,

View file

@ -5,110 +5,46 @@ import (
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
// routingGraph is an abstract interface that provides information about nodes
// and edges to pathfinding.
type routingGraph interface {
// forEachNodeChannel calls the callback for every channel of the given
// Graph is an abstract interface that provides information about nodes and
// edges to pathfinding.
type Graph interface {
// ForEachNodeChannel calls the callback for every channel of the given
// node.
forEachNodeChannel(nodePub route.Vertex,
ForEachNodeChannel(nodePub route.Vertex,
cb func(channel *channeldb.DirectedChannel) error) error
// sourceNode returns the source node of the graph.
sourceNode() route.Vertex
// fetchNodeFeatures returns the features of the given node.
fetchNodeFeatures(nodePub route.Vertex) (*lnwire.FeatureVector, error)
// FetchAmountPairCapacity determines the maximal capacity between two
// pairs of nodes.
FetchAmountPairCapacity(nodeFrom, nodeTo route.Vertex,
amount lnwire.MilliSatoshi) (btcutil.Amount, error)
// FetchNodeFeatures returns the features of the given node.
FetchNodeFeatures(nodePub route.Vertex) (*lnwire.FeatureVector, error)
}
// CachedGraph is a routingGraph implementation that retrieves from the
// database.
type CachedGraph struct {
graph *channeldb.ChannelGraph
tx kvdb.RTx
source route.Vertex
}
// A compile time assertion to make sure CachedGraph implements the routingGraph
// interface.
var _ routingGraph = (*CachedGraph)(nil)
// NewCachedGraph instantiates a new db-connected routing graph. It implicitly
// instantiates a new read transaction.
func NewCachedGraph(sourceNode *channeldb.LightningNode,
graph *channeldb.ChannelGraph) (*CachedGraph, error) {
tx, err := graph.NewPathFindTx()
if err != nil {
return nil, err
}
return &CachedGraph{
graph: graph,
tx: tx,
source: sourceNode.PubKeyBytes,
}, nil
}
// Close attempts to close the underlying db transaction. This is a no-op in
// case the underlying graph uses an in-memory cache.
func (g *CachedGraph) Close() error {
if g.tx == nil {
return nil
}
return g.tx.Rollback()
}
// forEachNodeChannel calls the callback for every channel of the given node.
//
// NOTE: Part of the routingGraph interface.
func (g *CachedGraph) forEachNodeChannel(nodePub route.Vertex,
cb func(channel *channeldb.DirectedChannel) error) error {
return g.graph.ForEachNodeDirectedChannel(g.tx, nodePub, cb)
}
// sourceNode returns the source node of the graph.
//
// NOTE: Part of the routingGraph interface.
func (g *CachedGraph) sourceNode() route.Vertex {
return g.source
}
// fetchNodeFeatures returns the features of the given node. If the node is
// unknown, assume no additional features are supported.
//
// NOTE: Part of the routingGraph interface.
func (g *CachedGraph) fetchNodeFeatures(nodePub route.Vertex) (
*lnwire.FeatureVector, error) {
return g.graph.FetchNodeFeatures(nodePub)
// GraphSessionFactory can be used to produce a new Graph instance which can
// then be used for a path-finding session. Depending on the implementation,
// the Graph session will represent a DB connection where a read-lock is being
// held across calls to the backing Graph.
type GraphSessionFactory interface {
// NewGraphSession will produce a new Graph to use for a path-finding
// session. It returns the Graph along with a call-back that must be
// called once Graph access is complete. This call-back will close any
// read-only transaction that was created at Graph construction time.
NewGraphSession() (Graph, func() error, error)
}
// FetchAmountPairCapacity determines the maximal public capacity between two
// nodes depending on the amount we try to send.
//
// NOTE: Part of the routingGraph interface.
func (g *CachedGraph) FetchAmountPairCapacity(nodeFrom, nodeTo route.Vertex,
func FetchAmountPairCapacity(graph Graph, source, nodeFrom, nodeTo route.Vertex,
amount lnwire.MilliSatoshi) (btcutil.Amount, error) {
// Create unified edges for all incoming connections.
//
// Note: Inbound fees are not used here because this method is only used
// by a deprecated router rpc.
u := newNodeEdgeUnifier(g.sourceNode(), nodeTo, false, nil)
u := newNodeEdgeUnifier(source, nodeTo, false, nil)
err := u.addGraphPolicies(g)
err := u.addGraphPolicies(graph)
if err != nil {
return 0, err
}

View file

@ -7,6 +7,7 @@ import (
"testing"
"time"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
@ -163,7 +164,7 @@ func (c *integratedRoutingContext) testPayment(maxParts uint32,
c.t.Fatal(err)
}
getBandwidthHints := func(_ routingGraph) (bandwidthHints, error) {
getBandwidthHints := func(_ Graph) (bandwidthHints, error) {
// Create bandwidth hints based on local channel balances.
bandwidthHints := map[uint64]lnwire.MilliSatoshi{}
for _, ch := range c.graph.nodes[c.source.pubkey].channels {
@ -200,11 +201,8 @@ func (c *integratedRoutingContext) testPayment(maxParts uint32,
}
session, err := newPaymentSession(
&payment, getBandwidthHints,
func() (routingGraph, func(), error) {
return c.graph, func() {}, nil
},
mc, c.pathFindingCfg,
&payment, c.graph.source.pubkey, getBandwidthHints,
newMockGraphSessionFactory(c.graph), mc, c.pathFindingCfg,
)
if err != nil {
c.t.Fatal(err)
@ -307,3 +305,88 @@ func getNodeIndex(route *route.Route, failureSource route.Vertex) *int {
}
return nil
}
type mockGraphSessionFactory struct {
Graph
}
func newMockGraphSessionFactory(graph Graph) GraphSessionFactory {
return &mockGraphSessionFactory{Graph: graph}
}
func (m *mockGraphSessionFactory) NewGraphSession() (Graph, func() error,
error) {
return m, func() error {
return nil
}, nil
}
var _ GraphSessionFactory = (*mockGraphSessionFactory)(nil)
var _ Graph = (*mockGraphSessionFactory)(nil)
type mockGraphSessionFactoryChanDB struct {
graph *channeldb.ChannelGraph
}
func newMockGraphSessionFactoryFromChanDB(
graph *channeldb.ChannelGraph) *mockGraphSessionFactoryChanDB {
return &mockGraphSessionFactoryChanDB{
graph: graph,
}
}
func (g *mockGraphSessionFactoryChanDB) NewGraphSession() (Graph, func() error,
error) {
tx, err := g.graph.NewPathFindTx()
if err != nil {
return nil, nil, err
}
session := &mockGraphSessionChanDB{
graph: g.graph,
tx: tx,
}
return session, session.close, nil
}
var _ GraphSessionFactory = (*mockGraphSessionFactoryChanDB)(nil)
type mockGraphSessionChanDB struct {
graph *channeldb.ChannelGraph
tx kvdb.RTx
}
func newMockGraphSessionChanDB(graph *channeldb.ChannelGraph) Graph {
return &mockGraphSessionChanDB{
graph: graph,
}
}
func (g *mockGraphSessionChanDB) close() error {
if g.tx == nil {
return nil
}
err := g.tx.Rollback()
if err != nil {
return fmt.Errorf("error closing db tx: %w", err)
}
return nil
}
func (g *mockGraphSessionChanDB) ForEachNodeChannel(nodePub route.Vertex,
cb func(channel *channeldb.DirectedChannel) error) error {
return g.graph.ForEachNodeDirectedChannel(g.tx, nodePub, cb)
}
func (g *mockGraphSessionChanDB) FetchNodeFeatures(nodePub route.Vertex) (
*lnwire.FeatureVector, error) {
return g.graph.FetchNodeFeatures(nodePub)
}

View file

@ -164,8 +164,8 @@ func (m *mockGraph) addChannel(id uint64, node1id, node2id byte,
// forEachNodeChannel calls the callback for every channel of the given node.
//
// NOTE: Part of the routingGraph interface.
func (m *mockGraph) forEachNodeChannel(nodePub route.Vertex,
// NOTE: Part of the Graph interface.
func (m *mockGraph) ForEachNodeChannel(nodePub route.Vertex,
cb func(channel *channeldb.DirectedChannel) error) error {
// Look up the mock node.
@ -213,45 +213,20 @@ func (m *mockGraph) forEachNodeChannel(nodePub route.Vertex,
// sourceNode returns the source node of the graph.
//
// NOTE: Part of the routingGraph interface.
// NOTE: Part of the Graph interface.
func (m *mockGraph) sourceNode() route.Vertex {
return m.source.pubkey
}
// fetchNodeFeatures returns the features of the given node.
//
// NOTE: Part of the routingGraph interface.
func (m *mockGraph) fetchNodeFeatures(nodePub route.Vertex) (
// NOTE: Part of the Graph interface.
func (m *mockGraph) FetchNodeFeatures(nodePub route.Vertex) (
*lnwire.FeatureVector, error) {
return lnwire.EmptyFeatureVector(), nil
}
// FetchAmountPairCapacity returns the maximal capacity between nodes in the
// graph.
//
// NOTE: Part of the routingGraph interface.
func (m *mockGraph) FetchAmountPairCapacity(nodeFrom, nodeTo route.Vertex,
amount lnwire.MilliSatoshi) (btcutil.Amount, error) {
var capacity btcutil.Amount
cb := func(channel *channeldb.DirectedChannel) error {
if channel.OtherNode == nodeTo {
capacity = channel.Capacity
}
return nil
}
err := m.forEachNodeChannel(nodeFrom, cb)
if err != nil {
return 0, err
}
return capacity, nil
}
// htlcResult describes the resolution of an htlc. If failure is nil, the htlc
// was settled.
type htlcResult struct {
@ -295,5 +270,5 @@ func (m *mockGraph) sendHtlc(route *route.Route) (htlcResult, error) {
return source.fwd(nil, next)
}
// Compile-time check for the routingGraph interface.
var _ routingGraph = &mockGraph{}
// Compile-time check for the Graph interface.
var _ Graph = &mockGraph{}

View file

@ -48,7 +48,7 @@ const (
// pathFinder defines the interface of a path finding algorithm.
type pathFinder = func(g *graphParams, r *RestrictParams,
cfg *PathFindingConfig, source, target route.Vertex,
cfg *PathFindingConfig, self, source, target route.Vertex,
amt lnwire.MilliSatoshi, timePref float64, finalHtlcExpiry int32) (
[]*unifiedEdge, float64, error)
@ -369,7 +369,7 @@ func edgeWeight(lockedAmt lnwire.MilliSatoshi, fee lnwire.MilliSatoshi,
// graphParams wraps the set of graph parameters passed to findPath.
type graphParams struct {
// graph is the ChannelGraph to be used during path finding.
graph routingGraph
graph Graph
// additionalEdges is an optional set of edges that should be
// considered during path finding, that is not already found in the
@ -464,7 +464,7 @@ type PathFindingConfig struct {
// available balance.
func getOutgoingBalance(node route.Vertex, outgoingChans map[uint64]struct{},
bandwidthHints bandwidthHints,
g routingGraph) (lnwire.MilliSatoshi, lnwire.MilliSatoshi, error) {
g Graph) (lnwire.MilliSatoshi, lnwire.MilliSatoshi, error) {
var max, total lnwire.MilliSatoshi
cb := func(channel *channeldb.DirectedChannel) error {
@ -502,7 +502,7 @@ func getOutgoingBalance(node route.Vertex, outgoingChans map[uint64]struct{},
}
// Iterate over all channels of the to node.
err := g.forEachNodeChannel(node, cb)
err := g.ForEachNodeChannel(node, cb)
if err != nil {
return 0, 0, err
}
@ -521,8 +521,9 @@ func getOutgoingBalance(node route.Vertex, outgoingChans map[uint64]struct{},
// path and accurately check the amount to forward at every node against the
// available bandwidth.
func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
source, target route.Vertex, amt lnwire.MilliSatoshi, timePref float64,
finalHtlcExpiry int32) ([]*unifiedEdge, float64, error) {
self, source, target route.Vertex, amt lnwire.MilliSatoshi,
timePref float64, finalHtlcExpiry int32) ([]*unifiedEdge, float64,
error) {
// Pathfinding can be a significant portion of the total payment
// latency, especially on low-powered devices. Log several metrics to
@ -541,7 +542,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
features := r.DestFeatures
if features == nil {
var err error
features, err = g.graph.fetchNodeFeatures(target)
features, err = g.graph.FetchNodeFeatures(target)
if err != nil {
return nil, 0, err
}
@ -583,8 +584,6 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
// If we are routing from ourselves, check that we have enough local
// balance available.
self := g.graph.sourceNode()
if source == self {
max, total, err := getOutgoingBalance(
self, outgoingChanMap, g.bandwidthHints, g.graph,
@ -921,7 +920,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
}
// Fetch node features fresh from the graph.
fromFeatures, err := g.graph.fetchNodeFeatures(node)
fromFeatures, err := g.graph.FetchNodeFeatures(node)
if err != nil {
return nil, err
}

View file

@ -1392,10 +1392,6 @@ func TestNewRoute(t *testing.T) {
// to fail or succeed.
expectError bool
// expectedErrorCode indicates the expected error code when
// expectError is true.
expectedErrorCode errorCode
expectedMPP *record.MPP
}{
{
@ -1606,23 +1602,9 @@ func TestNewRoute(t *testing.T) {
metadata: testCase.metadata,
}, nil,
)
require.NoError(t, err)
if testCase.expectError {
expectedCode := testCase.expectedErrorCode
if err == nil || !IsError(err, expectedCode) {
t.Fatalf("expected newRoute to fail "+
"with error code %v but got "+
"%v instead",
expectedCode, err)
}
} else {
if err != nil {
t.Errorf("unable to create path: %v", err)
return
}
assertRoute(t, route)
}
assertRoute(t, route)
})
}
}
@ -2227,17 +2209,12 @@ func TestPathFindSpecExample(t *testing.T) {
// Carol, so we set "B" as the source node so path finding starts from
// Bob.
bob := ctx.aliases["B"]
bobNode, err := ctx.graph.FetchLightningNode(nil, bob)
require.NoError(t, err, "unable to find bob")
if err := ctx.graph.SetSourceNode(bobNode); err != nil {
t.Fatalf("unable to set source node: %v", err)
}
// Query for a route of 4,999,999 mSAT to carol.
carol := ctx.aliases["C"]
const amt lnwire.MilliSatoshi = 4999999
req, err := NewRouteRequest(
bobNode.PubKeyBytes, &carol, amt, 0, noRestrictions, nil, nil,
bob, &carol, amt, 0, noRestrictions, nil, nil,
nil, MinCLTVDelta,
)
require.NoError(t, err, "invalid route request")
@ -2249,49 +2226,23 @@ func TestPathFindSpecExample(t *testing.T) {
//
// It should be sending the exact payment amount as there are no
// additional hops.
if route.TotalAmount != amt {
t.Fatalf("wrong total amount: got %v, expected %v",
route.TotalAmount, amt)
}
if route.Hops[0].AmtToForward != amt {
t.Fatalf("wrong forward amount: got %v, expected %v",
route.Hops[0].AmtToForward, amt)
}
fee := route.HopFee(0)
if fee != 0 {
t.Fatalf("wrong hop fee: got %v, expected %v", fee, 0)
}
require.Equal(t, amt, route.TotalAmount)
require.Equal(t, amt, route.Hops[0].AmtToForward)
require.Zero(t, route.HopFee(0))
// The CLTV expiry should be the current height plus 18 (the expiry for
// the B -> C channel.
if route.TotalTimeLock !=
startingHeight+MinCLTVDelta {
t.Fatalf("wrong total time lock: got %v, expecting %v",
route.TotalTimeLock,
startingHeight+MinCLTVDelta)
}
require.EqualValues(t, startingHeight+MinCLTVDelta, route.TotalTimeLock)
// Next, we'll set A as the source node so we can assert that we create
// the proper route for any queries starting with Alice.
alice := ctx.aliases["A"]
aliceNode, err := ctx.graph.FetchLightningNode(nil, alice)
require.NoError(t, err, "unable to find alice")
if err := ctx.graph.SetSourceNode(aliceNode); err != nil {
t.Fatalf("unable to set source node: %v", err)
}
ctx.router.selfNode = aliceNode
source, err := ctx.graph.SourceNode()
require.NoError(t, err, "unable to retrieve source node")
if source.PubKeyBytes != alice {
t.Fatalf("source node not set")
}
ctx.router.cfg.SelfNode = alice
// We'll now request a route from A -> B -> C.
req, err = NewRouteRequest(
source.PubKeyBytes, &carol, amt, 0, noRestrictions, nil, nil,
nil, MinCLTVDelta,
alice, &carol, amt, 0, noRestrictions, nil, nil, nil,
MinCLTVDelta,
)
require.NoError(t, err, "invalid route request")
@ -2299,32 +2250,21 @@ func TestPathFindSpecExample(t *testing.T) {
require.NoError(t, err, "unable to find routes")
// The route should be two hops.
if len(route.Hops) != 2 {
t.Fatalf("route should be %v hops, is instead %v", 2,
len(route.Hops))
}
require.Len(t, route.Hops, 2)
// The total amount should factor in a fee of 10199 and also use a CLTV
// delta total of 38 (20 + 18),
expectedAmt := lnwire.MilliSatoshi(5010198)
if route.TotalAmount != expectedAmt {
t.Fatalf("wrong amount: got %v, expected %v",
route.TotalAmount, expectedAmt)
}
require.Equal(t, expectedAmt, route.TotalAmount)
expectedDelta := uint32(20 + MinCLTVDelta)
if route.TotalTimeLock != startingHeight+expectedDelta {
t.Fatalf("wrong total time lock: got %v, expecting %v",
route.TotalTimeLock, startingHeight+expectedDelta)
}
require.Equal(t, startingHeight+expectedDelta, route.TotalTimeLock)
// Ensure that the hops of the route are properly crafted.
//
// After taking the fee, Bob should be forwarding the remainder which
// is the exact payment to Bob.
if route.Hops[0].AmtToForward != amt {
t.Fatalf("wrong forward amount: got %v, expected %v",
route.Hops[0].AmtToForward, amt)
}
require.Equal(t, amt, route.Hops[0].AmtToForward)
// We shouldn't pay any fee for the first, hop, but the fee for the
// second hop posted fee should be exactly:
@ -2333,59 +2273,33 @@ func TestPathFindSpecExample(t *testing.T) {
// hop, so we should get a fee of exactly:
//
// * 200 + 4999999 * 2000 / 1000000 = 10199
fee = route.HopFee(0)
if fee != 10199 {
t.Fatalf("wrong hop fee: got %v, expected %v", fee, 10199)
}
require.EqualValues(t, 10199, route.HopFee(0))
// While for the final hop, as there's no additional hop afterwards, we
// pay no fee.
fee = route.HopFee(1)
if fee != 0 {
t.Fatalf("wrong hop fee: got %v, expected %v", fee, 0)
}
require.Zero(t, route.HopFee(1))
// The outgoing CLTV value itself should be the current height plus 30
// to meet Carol's requirements.
if route.Hops[0].OutgoingTimeLock !=
startingHeight+MinCLTVDelta {
t.Fatalf("wrong total time lock: got %v, expecting %v",
route.Hops[0].OutgoingTimeLock,
startingHeight+MinCLTVDelta)
}
require.EqualValues(t, startingHeight+MinCLTVDelta,
route.Hops[0].OutgoingTimeLock)
// For B -> C, we assert that the final hop also has the proper
// parameters.
lastHop := route.Hops[1]
if lastHop.AmtToForward != amt {
t.Fatalf("wrong forward amount: got %v, expected %v",
lastHop.AmtToForward, amt)
}
if lastHop.OutgoingTimeLock !=
startingHeight+MinCLTVDelta {
t.Fatalf("wrong total time lock: got %v, expecting %v",
lastHop.OutgoingTimeLock,
startingHeight+MinCLTVDelta)
}
require.EqualValues(t, amt, lastHop.AmtToForward)
require.EqualValues(t, startingHeight+MinCLTVDelta,
lastHop.OutgoingTimeLock)
}
func assertExpectedPath(t *testing.T, aliasMap map[string]route.Vertex,
path []*unifiedEdge, nodeAliases ...string) {
if len(path) != len(nodeAliases) {
t.Fatalf("number of hops=(%v) and number of aliases=(%v) do "+
"not match", len(path), len(nodeAliases))
}
require.Len(t, path, len(nodeAliases))
for i, hop := range path {
if hop.policy.ToNodePubKey() != aliasMap[nodeAliases[i]] {
t.Fatalf("expected %v to be pos #%v in hop, instead "+
"%v was", nodeAliases[i], i,
hop.policy.ToNodePubKey())
}
require.Equal(t, aliasMap[nodeAliases[i]],
hop.policy.ToNodePubKey())
}
}
@ -2396,9 +2310,7 @@ func TestNewRouteFromEmptyHops(t *testing.T) {
var source route.Vertex
_, err := route.NewRouteFromHops(0, 0, source, []*route.Hop{})
if err != route.ErrNoRouteHopsProvided {
t.Fatalf("expected empty hops error: instead got: %v", err)
}
require.ErrorIs(t, err, route.ErrNoRouteHopsProvided)
}
// runRestrictOutgoingChannel asserts that a outgoing channel restriction is
@ -2441,11 +2353,6 @@ func runRestrictOutgoingChannel(t *testing.T, useCache bool) {
ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef")
const (
startingHeight = 100
finalHopCLTV = 1
)
paymentAmt := lnwire.NewMSatFromSatoshis(100)
target := ctx.keyFromAlias("target")
outgoingChannelID := uint64(chanSourceB1)
@ -3201,14 +3108,16 @@ func dbFindPath(graph *channeldb.ChannelGraph,
return nil, err
}
routingGraph, err := NewCachedGraph(sourceNode, graph)
graphSessFactory := newMockGraphSessionFactoryFromChanDB(graph)
graphSess, closeGraphSess, err := graphSessFactory.NewGraphSession()
if err != nil {
return nil, err
}
defer func() {
if err := routingGraph.Close(); err != nil {
log.Errorf("Error closing db tx: %v", err)
if err := closeGraphSess(); err != nil {
log.Errorf("Error closing graph session: %v", err)
}
}()
@ -3216,9 +3125,10 @@ func dbFindPath(graph *channeldb.ChannelGraph,
&graphParams{
additionalEdges: additionalEdges,
bandwidthHints: bandwidthHints,
graph: routingGraph,
graph: graphSess,
},
r, cfg, source, target, amt, timePref, finalHtlcExpiry,
r, cfg, sourceNode.PubKeyBytes, source, target, amt, timePref,
finalHtlcExpiry,
)
return route, err

View file

@ -912,7 +912,7 @@ func (p *paymentLifecycle) handleFailureMessage(rt *route.Route,
}
// Apply channel update to the channel edge policy in our db.
if !p.router.applyChannelUpdate(update) {
if !p.router.cfg.ApplyChannelUpdate(update) {
log.Debugf("Invalid channel update received: node=%v",
errVertex)
}

View file

@ -9,6 +9,7 @@ import (
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/graph"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
@ -163,9 +164,11 @@ type PaymentSession interface {
// loop if payment attempts take long enough. An additional set of edges can
// also be provided to assist in reaching the payment's destination.
type paymentSession struct {
selfNode route.Vertex
additionalEdges map[route.Vertex][]AdditionalEdge
getBandwidthHints func(routingGraph) (bandwidthHints, error)
getBandwidthHints func(Graph) (bandwidthHints, error)
payment *LightningPayment
@ -173,7 +176,7 @@ type paymentSession struct {
pathFinder pathFinder
getRoutingGraph func() (routingGraph, func(), error)
graphSessFactory GraphSessionFactory
// pathFindingConfig defines global parameters that control the
// trade-off in path finding between fees and probability.
@ -192,11 +195,10 @@ type paymentSession struct {
}
// newPaymentSession instantiates a new payment session.
func newPaymentSession(p *LightningPayment,
getBandwidthHints func(routingGraph) (bandwidthHints, error),
getRoutingGraph func() (routingGraph, func(), error),
missionControl MissionController, pathFindingConfig PathFindingConfig) (
*paymentSession, error) {
func newPaymentSession(p *LightningPayment, selfNode route.Vertex,
getBandwidthHints func(Graph) (bandwidthHints, error),
graphSessFactory GraphSessionFactory, missionControl MissionController,
pathFindingConfig PathFindingConfig) (*paymentSession, error) {
edges, err := RouteHintsToEdges(p.RouteHints, p.Target)
if err != nil {
@ -206,11 +208,12 @@ func newPaymentSession(p *LightningPayment,
logPrefix := fmt.Sprintf("PaymentSession(%x):", p.Identifier())
return &paymentSession{
selfNode: selfNode,
additionalEdges: edges,
getBandwidthHints: getBandwidthHints,
payment: p,
pathFinder: findPath,
getRoutingGraph: getRoutingGraph,
graphSessFactory: graphSessFactory,
pathFindingConfig: pathFindingConfig,
missionControl: missionControl,
minShardAmt: DefaultShardMinAmt,
@ -277,8 +280,8 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
}
for {
// Get a routing graph.
routingGraph, cleanup, err := p.getRoutingGraph()
// Get a routing graph session.
graph, closeGraph, err := p.graphSessFactory.NewGraphSession()
if err != nil {
return nil, err
}
@ -289,29 +292,35 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
// don't have enough bandwidth to carry the payment. New
// bandwidth hints are queried for every new path finding
// attempt, because concurrent payments may change balances.
bandwidthHints, err := p.getBandwidthHints(routingGraph)
bandwidthHints, err := p.getBandwidthHints(graph)
if err != nil {
// Close routing graph session.
if graphErr := closeGraph(); graphErr != nil {
log.Errorf("could not close graph session: %v",
graphErr)
}
return nil, err
}
p.log.Debugf("pathfinding for amt=%v", maxAmt)
sourceVertex := routingGraph.sourceNode()
// Find a route for the current amount.
path, _, err := p.pathFinder(
&graphParams{
additionalEdges: p.additionalEdges,
bandwidthHints: bandwidthHints,
graph: routingGraph,
graph: graph,
},
restrictions, &p.pathFindingConfig,
sourceVertex, p.payment.Target,
p.selfNode, p.selfNode, p.payment.Target,
maxAmt, p.payment.TimePref, finalHtlcExpiry,
)
// Close routing graph.
cleanup()
// Close routing graph session.
if err := closeGraph(); err != nil {
log.Errorf("could not close graph session: %v", err)
}
switch {
case err == errNoPathFound:
@ -384,7 +393,7 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
// this into a route by applying the time-lock and fee
// requirements.
route, err := newRoute(
sourceVertex, path, height,
p.selfNode, path, height,
finalHopParams{
amt: maxAmt,
totalAmt: p.payment.Amount,
@ -410,7 +419,7 @@ func (p *paymentSession) UpdateAdditionalEdge(msg *lnwire.ChannelUpdate,
pubKey *btcec.PublicKey, policy *models.CachedEdgePolicy) bool {
// Validate the message signature.
if err := VerifyChannelUpdateSignature(msg, pubKey); err != nil {
if err := graph.VerifyChannelUpdateSignature(msg, pubKey); err != nil {
log.Errorf(
"Unable to validate channel update signature: %v", err,
)

View file

@ -16,9 +16,10 @@ 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
// GraphSessionFactory can be used to gain access to a Graph session.
// If the backing DB allows it, this will mean that a read transaction
// is being held during the use of the session.
GraphSessionFactory GraphSessionFactory
// SourceNode is the graph's source node.
SourceNode *channeldb.LightningNode
@ -44,21 +45,6 @@ type SessionSource struct {
PathFindingConfig PathFindingConfig
}
// getRoutingGraph returns a routing graph and a clean-up function for
// pathfinding.
func (m *SessionSource) getRoutingGraph() (routingGraph, func(), error) {
routingTx, err := NewCachedGraph(m.SourceNode, m.Graph)
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
}
// 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
@ -66,15 +52,15 @@ func (m *SessionSource) getRoutingGraph() (routingGraph, func(), error) {
func (m *SessionSource) NewPaymentSession(p *LightningPayment) (
PaymentSession, error) {
getBandwidthHints := func(graph routingGraph) (bandwidthHints, error) {
getBandwidthHints := func(graph Graph) (bandwidthHints, error) {
return newBandwidthManager(
graph, m.SourceNode.PubKeyBytes, m.GetLink,
)
}
session, err := newPaymentSession(
p, getBandwidthHints, m.getRoutingGraph,
m.MissionControl, m.PathFindingConfig,
p, m.SourceNode.PubKeyBytes, getBandwidthHints,
m.GraphSessionFactory, m.MissionControl, m.PathFindingConfig,
)
if err != nil {
return nil, err

View file

@ -115,13 +115,11 @@ func TestUpdateAdditionalEdge(t *testing.T) {
// Create the paymentsession.
session, err := newPaymentSession(
payment,
func(routingGraph) (bandwidthHints, error) {
payment, route.Vertex{},
func(Graph) (bandwidthHints, error) {
return &mockBandwidthHints{}, nil
},
func() (routingGraph, func(), error) {
return &sessionGraph{}, func() {}, nil
},
newMockGraphSessionFactory(&sessionGraph{}),
&MissionControl{},
PathFindingConfig{},
)
@ -195,13 +193,11 @@ func TestRequestRoute(t *testing.T) {
}
session, err := newPaymentSession(
payment,
func(routingGraph) (bandwidthHints, error) {
payment, route.Vertex{},
func(Graph) (bandwidthHints, error) {
return &mockBandwidthHints{}, nil
},
func() (routingGraph, func(), error) {
return &sessionGraph{}, func() {}, nil
},
newMockGraphSessionFactory(&sessionGraph{}),
&MissionControl{},
PathFindingConfig{},
)
@ -211,9 +207,9 @@ func TestRequestRoute(t *testing.T) {
// Override pathfinder with a mock.
session.pathFinder = func(_ *graphParams, r *RestrictParams,
_ *PathFindingConfig, _, _ route.Vertex, _ lnwire.MilliSatoshi,
_ float64, _ int32) ([]*unifiedEdge, float64,
error) {
_ *PathFindingConfig, _, _, _ route.Vertex,
_ lnwire.MilliSatoshi, _ float64, _ int32) ([]*unifiedEdge,
float64, error) {
// We expect find path to receive a cltv limit excluding the
// final cltv delta (including the block padding).
@ -253,7 +249,7 @@ func TestRequestRoute(t *testing.T) {
}
type sessionGraph struct {
routingGraph
Graph
}
func (g *sessionGraph) sourceNode() route.Vertex {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -94,7 +94,7 @@ func (u *nodeEdgeUnifier) addPolicy(fromNode route.Vertex,
// addGraphPolicies adds all policies that are known for the toNode in the
// graph.
func (u *nodeEdgeUnifier) addGraphPolicies(g routingGraph) error {
func (u *nodeEdgeUnifier) addGraphPolicies(g Graph) 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
@ -120,7 +120,7 @@ func (u *nodeEdgeUnifier) addGraphPolicies(g routingGraph) error {
}
// Iterate over all channels of the to node.
return g.forEachNodeChannel(u.toNode, cb)
return g.ForEachNodeChannel(u.toNode, cb)
}
// unifiedEdge is the individual channel data that is kept inside an edgeUnifier

View file

@ -41,6 +41,7 @@ import (
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/chanfitness"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/graphsession"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/channelnotifier"
"github.com/lightningnetwork/lnd/contractcourt"
@ -48,6 +49,7 @@ import (
"github.com/lightningnetwork/lnd/feature"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/graph"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/input"
@ -691,23 +693,9 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service,
FetchAmountPairCapacity: func(nodeFrom, nodeTo route.Vertex,
amount lnwire.MilliSatoshi) (btcutil.Amount, error) {
routingGraph, err := routing.NewCachedGraph(
selfNode, graph,
)
if err != nil {
return 0, err
}
defer func() {
closeErr := routingGraph.Close()
if closeErr != nil {
rpcsLog.Errorf("not able to close "+
"routing graph tx: %v",
closeErr)
}
}()
return routingGraph.FetchAmountPairCapacity(
nodeFrom, nodeTo, amount,
return routing.FetchAmountPairCapacity(
graphsession.NewRoutingGraph(graph),
selfNode.PubKeyBytes, nodeFrom, nodeTo, amount,
)
},
FetchChannelEndpoints: func(chanID uint64) (route.Vertex,
@ -3088,7 +3076,7 @@ func (r *rpcServer) GetInfo(_ context.Context,
// date, we add the router's state to it. So the flag will only toggle
// to true once the router was also able to catch up.
if !r.cfg.Routing.AssumeChannelValid {
routerHeight := r.server.chanRouter.SyncedHeight()
routerHeight := r.server.graphBuilder.SyncedHeight()
isSynced = isSynced && uint32(bestHeight) == routerHeight
}
@ -3131,7 +3119,7 @@ func (r *rpcServer) GetInfo(_ context.Context,
// TODO(roasbeef): add synced height n stuff
isTestNet := chainreg.IsTestnet(&r.cfg.ActiveNetParams)
nodeColor := routing.EncodeHexColor(nodeAnn.RGBColor)
nodeColor := graph.EncodeHexColor(nodeAnn.RGBColor)
version := build.Version() + " commit=" + build.Commit
return &lnrpc.GetInfoResponse{
@ -6358,7 +6346,7 @@ func (r *rpcServer) GetNodeInfo(ctx context.Context,
// With the public key decoded, attempt to fetch the node corresponding
// to this public key. If the node cannot be found, then an error will
// be returned.
node, err := graph.FetchLightningNode(nil, pubKey)
node, err := graph.FetchLightningNode(pubKey)
switch {
case err == channeldb.ErrGraphNodeNotFound:
return nil, status.Error(codes.NotFound, err.Error())
@ -6374,7 +6362,7 @@ func (r *rpcServer) GetNodeInfo(ctx context.Context,
channels []*lnrpc.ChannelEdge
)
err = graph.ForEachNodeChannel(nil, node.PubKeyBytes,
err = graph.ForEachNodeChannel(node.PubKeyBytes,
func(_ kvdb.RTx, edge *models.ChannelEdgeInfo,
c1, c2 *models.ChannelEdgePolicy) error {
@ -6431,7 +6419,7 @@ func marshalNode(node *channeldb.LightningNode) *lnrpc.LightningNode {
PubKey: hex.EncodeToString(node.PubKeyBytes[:]),
Addresses: nodeAddrs,
Alias: node.Alias,
Color: routing.EncodeHexColor(node.Color),
Color: graph.EncodeHexColor(node.Color),
Features: features,
CustomRecords: customRecords,
}
@ -6626,7 +6614,7 @@ func (r *rpcServer) SubscribeChannelGraph(req *lnrpc.GraphTopologySubscription,
// First, we start by subscribing to a new intent to receive
// notifications from the channel router.
client, err := r.server.chanRouter.SubscribeTopology()
client, err := r.server.graphBuilder.SubscribeTopology()
if err != nil {
return err
}
@ -6678,7 +6666,8 @@ func (r *rpcServer) SubscribeChannelGraph(req *lnrpc.GraphTopologySubscription,
// marshallTopologyChange performs a mapping from the topology change struct
// returned by the router to the form of notifications expected by the current
// gRPC service.
func marshallTopologyChange(topChange *routing.TopologyChange) *lnrpc.GraphTopologyUpdate {
func marshallTopologyChange(
topChange *graph.TopologyChange) *lnrpc.GraphTopologyUpdate {
// encodeKey is a simple helper function that converts a live public
// key into a hex-encoded version of the compressed serialization for
@ -6689,7 +6678,9 @@ func marshallTopologyChange(topChange *routing.TopologyChange) *lnrpc.GraphTopol
nodeUpdates := make([]*lnrpc.NodeUpdate, len(topChange.NodeUpdates))
for i, nodeUpdate := range topChange.NodeUpdates {
nodeAddrs := make([]*lnrpc.NodeAddress, 0, len(nodeUpdate.Addresses))
nodeAddrs := make(
[]*lnrpc.NodeAddress, 0, len(nodeUpdate.Addresses),
)
for _, addr := range nodeUpdate.Addresses {
nodeAddr := &lnrpc.NodeAddress{
Network: addr.Network(),
@ -7027,7 +7018,7 @@ func (r *rpcServer) FeeReport(ctx context.Context,
}
var feeReports []*lnrpc.ChannelFeeReport
err = channelGraph.ForEachNodeChannel(nil, selfNode.PubKeyBytes,
err = channelGraph.ForEachNodeChannel(selfNode.PubKeyBytes,
func(_ kvdb.RTx, chanInfo *models.ChannelEdgeInfo,
edgePolicy, _ *models.ChannelEdgePolicy) error {
@ -7406,7 +7397,7 @@ func (r *rpcServer) ForwardingHistory(ctx context.Context,
return "", err
}
peer, err := r.server.graphDB.FetchLightningNode(nil, vertex)
peer, err := r.server.graphDB.FetchLightningNode(vertex)
if err != nil {
return "", err
}

View file

@ -32,6 +32,7 @@ import (
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/chanfitness"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/graphsession"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/channelnotifier"
"github.com/lightningnetwork/lnd/clock"
@ -40,6 +41,7 @@ import (
"github.com/lightningnetwork/lnd/feature"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/graph"
"github.com/lightningnetwork/lnd/healthcheck"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
@ -270,6 +272,8 @@ type server struct {
missionControl *routing.MissionControl
graphBuilder *graph.Builder
chanRouter *routing.ChannelRouter
controlTower routing.ControlTower
@ -338,7 +342,7 @@ type server struct {
// updatePersistentPeerAddrs subscribes to topology changes and stores
// advertised addresses for any NodeAnnouncements from our persisted peers.
func (s *server) updatePersistentPeerAddrs() error {
graphSub, err := s.chanRouter.SubscribeTopology()
graphSub, err := s.graphBuilder.SubscribeTopology()
if err != nil {
return err
}
@ -956,7 +960,9 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
return nil, fmt.Errorf("error getting source node: %w", err)
}
paymentSessionSource := &routing.SessionSource{
Graph: chanGraph,
GraphSessionFactory: graphsession.NewGraphSessionFactory(
chanGraph,
),
SourceNode: sourceNode,
MissionControl: s.missionControl,
GetLink: s.htlcSwitch.GetLinkByShortID,
@ -967,28 +973,40 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
s.controlTower = routing.NewControlTower(paymentControl)
strictPruning := (cfg.Bitcoin.Node == "neutrino" ||
cfg.Routing.StrictZombiePruning)
s.chanRouter, err = routing.New(routing.Config{
strictPruning := cfg.Bitcoin.Node == "neutrino" ||
cfg.Routing.StrictZombiePruning
s.graphBuilder, err = graph.NewBuilder(&graph.Config{
SelfNode: selfNode.PubKeyBytes,
Graph: chanGraph,
Chain: cc.ChainIO,
ChainView: cc.ChainView,
Notifier: cc.ChainNotifier,
Payer: s.htlcSwitch,
Control: s.controlTower,
MissionControl: s.missionControl,
SessionSource: paymentSessionSource,
ChannelPruneExpiry: routing.DefaultChannelPruneExpiry,
ChannelPruneExpiry: graph.DefaultChannelPruneExpiry,
GraphPruneInterval: time.Hour,
FirstTimePruneDelay: routing.DefaultFirstTimePruneDelay,
GetLink: s.htlcSwitch.GetLinkByShortID,
FirstTimePruneDelay: graph.DefaultFirstTimePruneDelay,
AssumeChannelValid: cfg.Routing.AssumeChannelValid,
NextPaymentID: sequencer.NextID,
PathFindingConfig: pathFindingConfig,
Clock: clock.NewDefaultClock(),
StrictZombiePruning: strictPruning,
IsAlias: aliasmgr.IsAlias,
})
if err != nil {
return nil, fmt.Errorf("can't create graph builder: %w", err)
}
s.chanRouter, err = routing.New(routing.Config{
SelfNode: selfNode.PubKeyBytes,
RoutingGraph: graphsession.NewRoutingGraph(chanGraph),
Chain: cc.ChainIO,
Payer: s.htlcSwitch,
Control: s.controlTower,
MissionControl: s.missionControl,
SessionSource: paymentSessionSource,
GetLink: s.htlcSwitch.GetLinkByShortID,
NextPaymentID: sequencer.NextID,
PathFindingConfig: pathFindingConfig,
Clock: clock.NewDefaultClock(),
ApplyChannelUpdate: s.graphBuilder.ApplyChannelUpdate,
})
if err != nil {
return nil, fmt.Errorf("can't create router: %w", err)
}
@ -1004,7 +1022,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
}
s.authGossiper = discovery.New(discovery.Config{
Router: s.chanRouter,
Graph: s.graphBuilder,
Notifier: s.cc.ChainNotifier,
ChainHash: *s.cfg.ActiveNetParams.GenesisHash,
Broadcast: s.BroadcastMessage,
@ -1039,11 +1057,12 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
FindBaseByAlias: s.aliasMgr.FindBaseSCID,
GetAlias: s.aliasMgr.GetPeerAlias,
FindChannel: s.findChannel,
IsStillZombieChannel: s.chanRouter.IsZombieChannel,
IsStillZombieChannel: s.graphBuilder.IsZombieChannel,
}, nodeKeyDesc)
//nolint:lll
s.localChanMgr = &localchans.Manager{
ForAllOutgoingChannels: s.chanRouter.ForAllOutgoingChannels,
ForAllOutgoingChannels: s.graphBuilder.ForAllOutgoingChannels,
PropagateChanPolicyUpdate: s.authGossiper.PropagateChanPolicyUpdate,
UpdateForwardingPolicies: s.htlcSwitch.UpdateForwardingPolicies,
FetchChannel: s.chanStateDB.FetchChannel,
@ -2013,6 +2032,12 @@ func (s *server) Start() error {
}
cleanup = cleanup.add(s.authGossiper.Stop)
if err := s.graphBuilder.Start(); err != nil {
startErr = err
return
}
cleanup = cleanup.add(s.graphBuilder.Stop)
if err := s.chanRouter.Start(); err != nil {
startErr = err
return
@ -3113,7 +3138,7 @@ func (s *server) establishPersistentConnections() error {
// TODO(roasbeef): instead iterate over link nodes and query graph for
// each of the nodes.
selfPub := s.identityECDH.PubKey().SerializeCompressed()
err = s.graphDB.ForEachNodeChannel(nil, sourceNode.PubKeyBytes, func(
err = s.graphDB.ForEachNodeChannel(sourceNode.PubKeyBytes, func(
tx kvdb.RTx,
chanInfo *models.ChannelEdgeInfo,
policy, _ *models.ChannelEdgePolicy) error {
@ -4628,7 +4653,7 @@ func (s *server) fetchNodeAdvertisedAddrs(pub *btcec.PublicKey) ([]net.Addr, err
return nil, err
}
node, err := s.graphDB.FetchLightningNode(nil, vertex)
node, err := s.graphDB.FetchLightningNode(vertex)
if err != nil {
return nil, err
}
@ -4647,7 +4672,7 @@ func (s *server) fetchLastChanUpdate() func(lnwire.ShortChannelID) (
ourPubKey := s.identityECDH.PubKey().SerializeCompressed()
return func(cid lnwire.ShortChannelID) (*lnwire.ChannelUpdate, error) {
info, edge1, edge2, err := s.chanRouter.GetChannelByID(cid)
info, edge1, edge2, err := s.graphBuilder.GetChannelByID(cid)
if err != nil {
return nil, err
}