refactor: move various duties from ChannelRouter to graph.Builder

This commit is a large refactor that moves over various responsibilities
from the ChannelRouter to the graph.Builder. These include all graph
related tasks such as:
- graph pruning
- validation of new network updates & persisting new updates
- notifying topology update clients of any changes.

This is a large commit but:
- many of the files are purely moved from `routing` to `graph`
- the business logic put in the graph Builder is copied exactly as is
  from the ChannelRouter with one exception:
- The ChannelRouter just needs to be able to call the Builder's
  `ApplyChannelUpdate` method. So this is now exported and provided to
the ChannelRouter as a config option.
- The trickiest part was just moving over the test code since quite a
  bit had to be duplicated.
This commit is contained in:
Elle Mouton 2024-06-16 19:30:01 -04:00
parent 0b7364f54b
commit 7f1be39d45
No known key found for this signature in database
GPG Key ID: D7D916376026F177
26 changed files with 5121 additions and 3862 deletions

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

@ -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)

View File

@ -29,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"
@ -1361,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 {
@ -1486,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()
@ -1502,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 "+
@ -1861,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",
@ -1910,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)
}
@ -2064,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)
@ -2201,7 +2200,7 @@ 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)
@ -2338,11 +2337,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)
@ -2457,7 +2456,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)
@ -2538,7 +2537,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)
@ -2862,7 +2861,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",
@ -2947,10 +2946,10 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
}
if err := d.cfg.Router.UpdateEdge(update, ops...); err != nil {
if routing.IsError(
err, routing.ErrOutdated,
routing.ErrIgnored,
routing.ErrVBarrierShuttingDown,
if graph.IsError(
err, graph.ErrOutdated,
graph.ErrIgnored,
graph.ErrVBarrierShuttingDown,
) {
log.Debugf("Update edge for short_chan_id(%v) got: %v",
@ -3268,7 +3267,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)

View File

@ -33,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"
@ -351,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 {
@ -2258,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

@ -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"
)
@ -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 "+
@ -4354,10 +4354,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 "+
"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

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

File diff suppressed because it is too large Load Diff

2051
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
}

View File

@ -1,4 +1,4 @@
package routing
package graph
import (
"fmt"
@ -13,7 +13,6 @@ import (
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/graph"
"github.com/lightningnetwork/lnd/lnwire"
)
@ -57,16 +56,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 := atomic.AddUint64(&b.ntfnClientCounter, 1)
log.Debugf("New graph topology client subscription, client %v",
clientID)
@ -74,12 +73,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")
}
@ -87,11 +86,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
}
},
@ -117,7 +116,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.
@ -146,7 +145,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)
@ -158,7 +157,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.
@ -314,7 +313,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 graph.DB, 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,28 @@ var (
bitcoinKey2 = priv2.PubKey()
timeout = time.Second * 5
testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa1bf0314f882d7")
testSBytes, _ = hex.DecodeString("299105481d63e0f4bc2a88121167221b6700d72a0ead154c03be696a292d24ae")
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 +89,7 @@ func createTestNode() (*channeldb.LightningNode, error) {
}
copy(n.PubKeyBytes[:], pub)
return n, nil
return n
}
func randEdgePolicy(chanID *lnwire.ShortChannelID,
@ -271,7 +289,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 +320,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 +432,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 +455,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 +474,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 +493,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 +641,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 +663,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 +770,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 +802,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 +822,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 +844,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 +895,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 +913,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 +1009,200 @@ 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 (c *testCtx) getChannelIDFromAlias(t *testing.T, a, b string) uint64 {
vertexA, ok := c.aliases[a]
require.True(t, ok, "cannot find aliases for %s", a)
vertexB, ok := c.aliases[b]
require.True(t, ok, "cannot find aliases for %s", b)
channelIDMap, ok := c.channelIDs[vertexA]
require.True(t, ok, "cannot find channelID map %s(%s)", vertexA, a)
channelID, ok := channelIDMap[vertexB]
require.True(t, ok, "cannot find channelID using %s(%s)", vertexB, b)
return channelID
}
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() {
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)
}
}
}

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

@ -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)
})
}
}
@ -2232,8 +2214,8 @@ func TestPathFindSpecExample(t *testing.T) {
carol := ctx.aliases["C"]
const amt lnwire.MilliSatoshi = 4999999
req, err := NewRouteRequest(
bob, &carol, amt, 0, noRestrictions, nil, nil, nil,
MinCLTVDelta,
bob, &carol, amt, 0, noRestrictions, nil, nil,
nil, MinCLTVDelta,
)
require.NoError(t, err, "invalid route request")
@ -2244,33 +2226,18 @@ 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"]
ctx.router.cfg.SelfNode = alice
// We'll now request a route from A -> B -> C.
req, err = NewRouteRequest(
@ -2283,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:
@ -2317,59 +2273,31 @@ 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())
}
}
@ -2380,9 +2308,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
@ -2425,11 +2351,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)

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"
)
@ -412,7 +413,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,
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -49,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"
@ -3075,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
}
@ -3118,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{
@ -6418,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,
}
@ -6613,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
}
@ -6665,7 +6666,7 @@ 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

View File

@ -342,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
}
@ -976,32 +976,36 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
strictPruning := cfg.Bitcoin.Node == "neutrino" ||
cfg.Routing.StrictZombiePruning
s.graphBuilder, err = graph.NewBuilder(&graph.Config{})
s.graphBuilder, err = graph.NewBuilder(&graph.Config{
SelfNode: selfNode.PubKeyBytes,
Graph: chanGraph,
Chain: cc.ChainIO,
ChainView: cc.ChainView,
Notifier: cc.ChainNotifier,
ChannelPruneExpiry: graph.DefaultChannelPruneExpiry,
GraphPruneInterval: time.Hour,
FirstTimePruneDelay: graph.DefaultFirstTimePruneDelay,
AssumeChannelValid: cfg.Routing.AssumeChannelValid,
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),
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,
GraphPruneInterval: time.Hour,
FirstTimePruneDelay: routing.DefaultFirstTimePruneDelay,
GetLink: s.htlcSwitch.GetLinkByShortID,
AssumeChannelValid: cfg.Routing.AssumeChannelValid,
NextPaymentID: sequencer.NextID,
PathFindingConfig: pathFindingConfig,
Clock: clock.NewDefaultClock(),
StrictZombiePruning: strictPruning,
IsAlias: aliasmgr.IsAlias,
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)
@ -1018,7 +1022,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
}
s.authGossiper = discovery.New(discovery.Config{
Router: s.chanRouter,
Router: s.graphBuilder,
Notifier: s.cc.ChainNotifier,
ChainHash: *s.cfg.ActiveNetParams.GenesisHash,
Broadcast: s.BroadcastMessage,
@ -1053,11 +1057,11 @@ 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)
s.localChanMgr = &localchans.Manager{
ForAllOutgoingChannels: s.chanRouter.ForAllOutgoingChannels,
ForAllOutgoingChannels: s.graphBuilder.ForAllOutgoingChannels,
PropagateChanPolicyUpdate: s.authGossiper.PropagateChanPolicyUpdate,
UpdateForwardingPolicies: s.htlcSwitch.UpdateForwardingPolicies,
FetchChannel: s.chanStateDB.FetchChannel,
@ -4667,7 +4671,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
}