Merge pull request #9692 from lightningnetwork/elle-graph

[graph-work-side-branch]: side branch for graph work
This commit is contained in:
Oliver Gugger authored and GitHub committed 2025-05-23 10:29:09 +02:00
commit cbdd1c22b7
75 files changed
+3106 -1492

No files matched your search

+2
View File
@@ -222,6 +222,8 @@ jobs:
- unit tags="kvdb_etcd" - unit tags="kvdb_etcd"
- unit tags="kvdb_postgres" - unit tags="kvdb_postgres"
- unit tags="kvdb_sqlite" - unit tags="kvdb_sqlite"
- unit tags="test_db_sqlite"
- unit tags="test_db_postgres"
- unit-race - unit-race
- unit-module - unit-module
+23 -11
View File
@@ -2,6 +2,7 @@ package autopilot
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"math/rand" "math/rand"
"net" "net"
@@ -11,6 +12,7 @@ import (
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
) )
@@ -166,8 +168,9 @@ type Agent struct {
pendingOpens map[NodeID]LocalChannel pendingOpens map[NodeID]LocalChannel
pendingMtx sync.Mutex pendingMtx sync.Mutex
quit chan struct{} quit chan struct{}
wg sync.WaitGroup wg sync.WaitGroup
cancel fn.Option[context.CancelFunc]
} }
// New creates a new instance of the Agent instantiated using the passed // New creates a new instance of the Agent instantiated using the passed
@@ -202,17 +205,20 @@ func New(cfg Config, initialState []LocalChannel) (*Agent, error) {
func (a *Agent) Start() error { func (a *Agent) Start() error {
var err error var err error
a.started.Do(func() { a.started.Do(func() {
err = a.start() ctx, cancel := context.WithCancel(context.Background())
a.cancel = fn.Some(cancel)
err = a.start(ctx)
}) })
return err return err
} }
func (a *Agent) start() error { func (a *Agent) start(ctx context.Context) error {
rand.Seed(time.Now().Unix()) rand.Seed(time.Now().Unix())
log.Infof("Autopilot Agent starting") log.Infof("Autopilot Agent starting")
a.wg.Add(1) a.wg.Add(1)
go a.controller() go a.controller(ctx)
return nil return nil
} }
@@ -230,6 +236,7 @@ func (a *Agent) Stop() error {
func (a *Agent) stop() error { func (a *Agent) stop() error {
log.Infof("Autopilot Agent stopping") log.Infof("Autopilot Agent stopping")
a.cancel.WhenSome(func(fn context.CancelFunc) { fn() })
close(a.quit) close(a.quit)
a.wg.Wait() a.wg.Wait()
@@ -401,7 +408,7 @@ func mergeChanState(pendingChans map[NodeID]LocalChannel,
// and external state changes as a result of decisions it makes w.r.t channel // and external state changes as a result of decisions it makes w.r.t channel
// allocation, or attributes affecting its control loop being updated by the // allocation, or attributes affecting its control loop being updated by the
// backing Lightning Node. // backing Lightning Node.
func (a *Agent) controller() { func (a *Agent) controller(ctx context.Context) {
defer a.wg.Done() defer a.wg.Done()
// We'll start off by assigning our starting balance, and injecting // We'll start off by assigning our starting balance, and injecting
@@ -502,6 +509,9 @@ func (a *Agent) controller() {
// immediately. // immediately.
case <-a.quit: case <-a.quit:
return return
case <-ctx.Done():
return
} }
a.pendingMtx.Lock() a.pendingMtx.Lock()
@@ -539,7 +549,7 @@ func (a *Agent) controller() {
log.Infof("Triggering attachment directive dispatch, "+ log.Infof("Triggering attachment directive dispatch, "+
"total_funds=%v", a.totalBalance) "total_funds=%v", a.totalBalance)
err := a.openChans(availableFunds, numChans, totalChans) err := a.openChans(ctx, availableFunds, numChans, totalChans)
if err != nil { if err != nil {
log.Errorf("Unable to open channels: %v", err) log.Errorf("Unable to open channels: %v", err)
} }
@@ -548,8 +558,8 @@ func (a *Agent) controller() {
// openChans queries the agent's heuristic for a set of channel candidates, and // openChans queries the agent's heuristic for a set of channel candidates, and
// attempts to open channels to them. // attempts to open channels to them.
func (a *Agent) openChans(availableFunds btcutil.Amount, numChans uint32, func (a *Agent) openChans(ctx context.Context, availableFunds btcutil.Amount,
totalChans []LocalChannel) error { numChans uint32, totalChans []LocalChannel) error {
// As channel size we'll use the maximum channel size available. // As channel size we'll use the maximum channel size available.
chanSize := a.cfg.Constraints.MaxChanSize() chanSize := a.cfg.Constraints.MaxChanSize()
@@ -598,7 +608,9 @@ func (a *Agent) openChans(availableFunds btcutil.Amount, numChans uint32,
selfPubBytes := a.cfg.Self.SerializeCompressed() selfPubBytes := a.cfg.Self.SerializeCompressed()
nodes := make(map[NodeID]struct{}) nodes := make(map[NodeID]struct{})
addresses := make(map[NodeID][]net.Addr) addresses := make(map[NodeID][]net.Addr)
if err := a.cfg.Graph.ForEachNode(func(node Node) error { if err := a.cfg.Graph.ForEachNode(ctx, func(_ context.Context,
node Node) error {
nID := NodeID(node.PubKey()) nID := NodeID(node.PubKey())
// If we come across ourselves, them we'll continue in // If we come across ourselves, them we'll continue in
@@ -636,7 +648,7 @@ func (a *Agent) openChans(availableFunds btcutil.Amount, numChans uint32,
// graph. // graph.
log.Debugf("Scoring %d nodes for chan_size=%v", len(nodes), chanSize) log.Debugf("Scoring %d nodes for chan_size=%v", len(nodes), chanSize)
scores, err := a.cfg.Heuristic.NodeScores( scores, err := a.cfg.Heuristic.NodeScores(
a.cfg.Graph, totalChans, chanSize, nodes, ctx, a.cfg.Graph, totalChans, chanSize, nodes,
) )
if err != nil { if err != nil {
return fmt.Errorf("unable to calculate node scores : %w", err) return fmt.Errorf("unable to calculate node scores : %w", err)
+4 -3
View File
@@ -1,6 +1,7 @@
package autopilot package autopilot
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"net" "net"
@@ -85,9 +86,9 @@ func (m *mockHeuristic) Name() string {
return "mock" return "mock"
} }
func (m *mockHeuristic) NodeScores(g ChannelGraph, chans []LocalChannel, func (m *mockHeuristic) NodeScores(_ context.Context, g ChannelGraph,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( chans []LocalChannel, chanSize btcutil.Amount,
map[NodeID]*NodeScore, error) { nodes map[NodeID]struct{}) (map[NodeID]*NodeScore, error) {
if m.nodeScoresArgs != nil { if m.nodeScoresArgs != nil {
directive := directiveArg{ directive := directiveArg{
+5 -2
View File
@@ -1,6 +1,7 @@
package autopilot package autopilot
import ( import (
"context"
"fmt" "fmt"
"sync" "sync"
) )
@@ -168,8 +169,10 @@ func betweennessCentrality(g *SimpleGraph, s int, centrality []float64) {
} }
// Refresh recalculates and stores centrality values. // Refresh recalculates and stores centrality values.
func (bc *BetweennessCentrality) Refresh(graph ChannelGraph) error { func (bc *BetweennessCentrality) Refresh(ctx context.Context,
cache, err := NewSimpleGraph(graph) graph ChannelGraph) error {
cache, err := NewSimpleGraph(ctx, graph)
if err != nil { if err != nil {
return err return err
} }
+9 -2
View File
@@ -1,6 +1,7 @@
package autopilot package autopilot
import ( import (
"context"
"fmt" "fmt"
"testing" "testing"
@@ -30,6 +31,9 @@ func TestBetweennessCentralityMetricConstruction(t *testing.T) {
// Tests that empty graph results in empty centrality result. // Tests that empty graph results in empty centrality result.
func TestBetweennessCentralityEmptyGraph(t *testing.T) { func TestBetweennessCentralityEmptyGraph(t *testing.T) {
t.Parallel()
ctx := context.Background()
centralityMetric, err := NewBetweennessCentralityMetric(1) centralityMetric, err := NewBetweennessCentralityMetric(1)
require.NoError( require.NoError(
t, err, t, err,
@@ -42,7 +46,7 @@ func TestBetweennessCentralityEmptyGraph(t *testing.T) {
require.NoError(t, err, "unable to create graph") require.NoError(t, err, "unable to create graph")
success := t.Run(chanGraph.name, func(t1 *testing.T) { success := t.Run(chanGraph.name, func(t1 *testing.T) {
err = centralityMetric.Refresh(graph) err = centralityMetric.Refresh(ctx, graph)
require.NoError(t1, err) require.NoError(t1, err)
centrality := centralityMetric.GetMetric(false) centrality := centralityMetric.GetMetric(false)
@@ -59,6 +63,9 @@ func TestBetweennessCentralityEmptyGraph(t *testing.T) {
// Test betweenness centrality calculating using an example graph. // Test betweenness centrality calculating using an example graph.
func TestBetweennessCentralityWithNonEmptyGraph(t *testing.T) { func TestBetweennessCentralityWithNonEmptyGraph(t *testing.T) {
t.Parallel()
ctx := context.Background()
workers := []int{1, 3, 9, 100} workers := []int{1, 3, 9, 100}
tests := []struct { tests := []struct {
@@ -100,7 +107,7 @@ func TestBetweennessCentralityWithNonEmptyGraph(t *testing.T) {
t1, graph, centralityTestGraph, t1, graph, centralityTestGraph,
) )
err = metric.Refresh(graph) err = metric.Refresh(ctx, graph)
require.NoError(t1, err) require.NoError(t1, err)
for _, expected := range tests { for _, expected := range tests {
+5 -4
View File
@@ -1,6 +1,7 @@
package autopilot package autopilot
import ( import (
"context"
"fmt" "fmt"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
@@ -70,9 +71,9 @@ func (c *WeightedCombAttachment) Name() string {
// is the maximum possible improvement in connectivity. // is the maximum possible improvement in connectivity.
// //
// NOTE: This is a part of the AttachmentHeuristic interface. // NOTE: This is a part of the AttachmentHeuristic interface.
func (c *WeightedCombAttachment) NodeScores(g ChannelGraph, chans []LocalChannel, func (c *WeightedCombAttachment) NodeScores(ctx context.Context, g ChannelGraph,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( chans []LocalChannel, chanSize btcutil.Amount,
map[NodeID]*NodeScore, error) { nodes map[NodeID]struct{}) (map[NodeID]*NodeScore, error) {
// We now query each heuristic to determine the score they give to the // We now query each heuristic to determine the score they give to the
// nodes for the given channel size. // nodes for the given channel size.
@@ -81,7 +82,7 @@ func (c *WeightedCombAttachment) NodeScores(g ChannelGraph, chans []LocalChannel
log.Tracef("Getting scores from sub heuristic %v", h.Name()) log.Tracef("Getting scores from sub heuristic %v", h.Name())
s, err := h.NodeScores( s, err := h.NodeScores(
g, chans, chanSize, nodes, ctx, g, chans, chanSize, nodes,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to get sub score: %w", return nil, fmt.Errorf("unable to get sub score: %w",
+4 -3
View File
@@ -1,6 +1,7 @@
package autopilot package autopilot
import ( import (
"context"
"fmt" "fmt"
"sync" "sync"
@@ -80,9 +81,9 @@ func (s *ExternalScoreAttachment) SetNodeScores(targetHeuristic string,
// not known will get a score of 0. // not known will get a score of 0.
// //
// NOTE: This is a part of the AttachmentHeuristic interface. // NOTE: This is a part of the AttachmentHeuristic interface.
func (s *ExternalScoreAttachment) NodeScores(g ChannelGraph, chans []LocalChannel, func (s *ExternalScoreAttachment) NodeScores(_ context.Context, g ChannelGraph,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( chans []LocalChannel, chanSize btcutil.Amount,
map[NodeID]*NodeScore, error) { nodes map[NodeID]struct{}) (map[NodeID]*NodeScore, error) {
existingPeers := make(map[NodeID]struct{}) existingPeers := make(map[NodeID]struct{})
for _, c := range chans { for _, c := range chans {
+3 -1
View File
@@ -1,6 +1,7 @@
package autopilot_test package autopilot_test
import ( import (
"context"
"testing" "testing"
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
@@ -22,6 +23,7 @@ func randKey() (*btcec.PublicKey, error) {
// ExternalScoreAttachment correctly reflects the scores we set last. // ExternalScoreAttachment correctly reflects the scores we set last.
func TestSetNodeScores(t *testing.T) { func TestSetNodeScores(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
const name = "externalscore" const name = "externalscore"
@@ -62,7 +64,7 @@ func TestSetNodeScores(t *testing.T) {
q[nID] = struct{}{} q[nID] = struct{}{}
} }
resp, err := h.NodeScores( resp, err := h.NodeScores(
nil, nil, btcutil.Amount(btcutil.SatoshiPerBitcoin), q, ctx, nil, nil, btcutil.Amount(btcutil.SatoshiPerBitcoin), q,
) )
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
+23 -10
View File
@@ -1,6 +1,7 @@
package autopilot package autopilot
import ( import (
"context"
"encoding/hex" "encoding/hex"
"net" "net"
"sort" "sort"
@@ -80,7 +81,9 @@ func (d *dbNode) Addrs() []net.Addr {
// describes the active channel. // describes the active channel.
// //
// NOTE: Part of the autopilot.Node interface. // NOTE: Part of the autopilot.Node interface.
func (d *dbNode) ForEachChannel(cb func(ChannelEdge) error) error { func (d *dbNode) ForEachChannel(ctx context.Context,
cb func(context.Context, ChannelEdge) error) error {
return d.tx.ForEachChannel(func(ei *models.ChannelEdgeInfo, ep, return d.tx.ForEachChannel(func(ei *models.ChannelEdgeInfo, ep,
_ *models.ChannelEdgePolicy) error { _ *models.ChannelEdgePolicy) error {
@@ -108,7 +111,7 @@ func (d *dbNode) ForEachChannel(cb func(ChannelEdge) error) error {
}, },
} }
return cb(edge) return cb(ctx, edge)
}) })
} }
@@ -117,7 +120,9 @@ func (d *dbNode) ForEachChannel(cb func(ChannelEdge) error) error {
// error, then execution should be terminated. // error, then execution should be terminated.
// //
// NOTE: Part of the autopilot.ChannelGraph interface. // NOTE: Part of the autopilot.ChannelGraph interface.
func (d *databaseChannelGraph) ForEachNode(cb func(Node) error) error { func (d *databaseChannelGraph) ForEachNode(ctx context.Context,
cb func(context.Context, Node) error) error {
return d.db.ForEachNode(func(nodeTx graphdb.NodeRTx) error { return d.db.ForEachNode(func(nodeTx graphdb.NodeRTx) error {
// We'll skip over any node that doesn't have any advertised // We'll skip over any node that doesn't have any advertised
// addresses. As we won't be able to reach them to actually // addresses. As we won't be able to reach them to actually
@@ -129,7 +134,8 @@ func (d *databaseChannelGraph) ForEachNode(cb func(Node) error) error {
node := &dbNode{ node := &dbNode{
tx: nodeTx, tx: nodeTx,
} }
return cb(node)
return cb(ctx, node)
}) })
} }
@@ -185,7 +191,9 @@ func (nc dbNodeCached) Addrs() []net.Addr {
// describes the active channel. // describes the active channel.
// //
// NOTE: Part of the autopilot.Node interface. // NOTE: Part of the autopilot.Node interface.
func (nc dbNodeCached) ForEachChannel(cb func(ChannelEdge) error) error { func (nc dbNodeCached) ForEachChannel(ctx context.Context,
cb func(context.Context, ChannelEdge) error) error {
for cid, channel := range nc.channels { for cid, channel := range nc.channels {
edge := ChannelEdge{ edge := ChannelEdge{
ChanID: lnwire.NewShortChanIDFromInt(cid), ChanID: lnwire.NewShortChanIDFromInt(cid),
@@ -195,7 +203,7 @@ func (nc dbNodeCached) ForEachChannel(cb func(ChannelEdge) error) error {
}, },
} }
if err := cb(edge); err != nil { if err := cb(ctx, edge); err != nil {
return err return err
} }
} }
@@ -208,7 +216,9 @@ func (nc dbNodeCached) ForEachChannel(cb func(ChannelEdge) error) error {
// error, then execution should be terminated. // error, then execution should be terminated.
// //
// NOTE: Part of the autopilot.ChannelGraph interface. // NOTE: Part of the autopilot.ChannelGraph interface.
func (dc *databaseChannelGraphCached) ForEachNode(cb func(Node) error) error { func (dc *databaseChannelGraphCached) ForEachNode(ctx context.Context,
cb func(context.Context, Node) error) error {
return dc.db.ForEachNodeCached(func(n route.Vertex, return dc.db.ForEachNodeCached(func(n route.Vertex,
channels map[uint64]*graphdb.DirectedChannel) error { channels map[uint64]*graphdb.DirectedChannel) error {
@@ -217,7 +227,8 @@ func (dc *databaseChannelGraphCached) ForEachNode(cb func(Node) error) error {
node: n, node: n,
channels: channels, channels: channels,
} }
return cb(node)
return cb(ctx, node)
} }
return nil return nil
}) })
@@ -262,9 +273,11 @@ func (m memNode) Addrs() []net.Addr {
// describes the active channel. // describes the active channel.
// //
// NOTE: Part of the autopilot.Node interface. // NOTE: Part of the autopilot.Node interface.
func (m memNode) ForEachChannel(cb func(ChannelEdge) error) error { func (m memNode) ForEachChannel(ctx context.Context,
cb func(context.Context, ChannelEdge) error) error {
for _, channel := range m.chans { for _, channel := range m.chans {
if err := cb(channel); err != nil { if err := cb(ctx, channel); err != nil {
return err return err
} }
} }
+6 -4
View File
@@ -1,6 +1,7 @@
package autopilot package autopilot
import ( import (
"context"
"net" "net"
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
@@ -35,7 +36,8 @@ type Node interface {
// iterate through all edges emanating from/to the target node. For // iterate through all edges emanating from/to the target node. For
// each active channel, this function should be called with the // each active channel, this function should be called with the
// populated ChannelEdge that describes the active channel. // populated ChannelEdge that describes the active channel.
ForEachChannel(func(ChannelEdge) error) error ForEachChannel(context.Context, func(context.Context,
ChannelEdge) error) error
} }
// LocalChannel is a simple struct which contains relevant details of a // LocalChannel is a simple struct which contains relevant details of a
@@ -83,7 +85,7 @@ type ChannelGraph interface {
// ForEachNode is a higher-order function that should be called once // ForEachNode is a higher-order function that should be called once
// for each connected node within the channel graph. If the passed // for each connected node within the channel graph. If the passed
// callback returns an error, then execution should be terminated. // callback returns an error, then execution should be terminated.
ForEachNode(func(Node) error) error ForEachNode(context.Context, func(context.Context, Node) error) error
} }
// NodeScore is a tuple mapping a NodeID to a score indicating the preference // NodeScore is a tuple mapping a NodeID to a score indicating the preference
@@ -142,7 +144,7 @@ type AttachmentHeuristic interface {
// //
// NOTE: A NodeID not found in the returned map is implicitly given a // NOTE: A NodeID not found in the returned map is implicitly given a
// score of 0. // score of 0.
NodeScores(g ChannelGraph, chans []LocalChannel, NodeScores(ctx context.Context, g ChannelGraph, chans []LocalChannel,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( chanSize btcutil.Amount, nodes map[NodeID]struct{}) (
map[NodeID]*NodeScore, error) map[NodeID]*NodeScore, error)
} }
@@ -155,7 +157,7 @@ type NodeMetric interface {
Name() string Name() string
// Refresh refreshes the metric values based on the current graph. // Refresh refreshes the metric values based on the current graph.
Refresh(graph ChannelGraph) error Refresh(ctx context.Context, graph ChannelGraph) error
// GetMetric returns the latest value of this metric. Values in the // GetMetric returns the latest value of this metric. Values in the
// map are per node and can be in arbitrary domain. If normalize is // map are per node and can be in arbitrary domain. If normalize is
+8 -6
View File
@@ -1,6 +1,7 @@
package autopilot package autopilot
import ( import (
"context"
"fmt" "fmt"
"sync" "sync"
@@ -266,8 +267,8 @@ func (m *Manager) StopAgent() error {
} }
// QueryHeuristics queries the available autopilot heuristics for node scores. // QueryHeuristics queries the available autopilot heuristics for node scores.
func (m *Manager) QueryHeuristics(nodes []NodeID, localState bool) ( func (m *Manager) QueryHeuristics(ctx context.Context, nodes []NodeID,
HeuristicScores, error) { localState bool) (HeuristicScores, error) {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
@@ -278,7 +279,8 @@ func (m *Manager) QueryHeuristics(nodes []NodeID, localState bool) (
} }
log.Debugf("Querying heuristics for %d nodes", len(n)) log.Debugf("Querying heuristics for %d nodes", len(n))
return m.queryHeuristics(n, localState)
return m.queryHeuristics(ctx, n, localState)
} }
// HeuristicScores is an alias for a map that maps heuristic names to a map of // HeuristicScores is an alias for a map that maps heuristic names to a map of
@@ -289,8 +291,8 @@ type HeuristicScores map[string]map[NodeID]float64
// the agent's current active heuristic. // the agent's current active heuristic.
// //
// NOTE: Must be called with the manager's lock. // NOTE: Must be called with the manager's lock.
func (m *Manager) queryHeuristics(nodes map[NodeID]struct{}, localState bool) ( func (m *Manager) queryHeuristics(ctx context.Context,
HeuristicScores, error) { nodes map[NodeID]struct{}, localState bool) (HeuristicScores, error) {
// If we want to take the local state into action when querying the // If we want to take the local state into action when querying the
// heuristics, we fetch it. If not we'll just pass an empty slice to // heuristics, we fetch it. If not we'll just pass an empty slice to
@@ -338,7 +340,7 @@ func (m *Manager) queryHeuristics(nodes map[NodeID]struct{}, localState bool) (
} }
s, err := h.NodeScores( s, err := h.NodeScores(
m.cfg.PilotCfg.Graph, totalChans, chanSize, nodes, ctx, m.cfg.PilotCfg.Graph, totalChans, chanSize, nodes,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to get sub score: %w", return nil, fmt.Errorf("unable to get sub score: %w",
+15 -8
View File
@@ -1,6 +1,7 @@
package autopilot package autopilot
import ( import (
"context"
prand "math/rand" prand "math/rand"
"time" "time"
@@ -78,9 +79,9 @@ func (p *PrefAttachment) Name() string {
// given to nodes already having high connectivity in the graph. // given to nodes already having high connectivity in the graph.
// //
// NOTE: This is a part of the AttachmentHeuristic interface. // NOTE: This is a part of the AttachmentHeuristic interface.
func (p *PrefAttachment) NodeScores(g ChannelGraph, chans []LocalChannel, func (p *PrefAttachment) NodeScores(ctx context.Context, g ChannelGraph,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( chans []LocalChannel, chanSize btcutil.Amount,
map[NodeID]*NodeScore, error) { nodes map[NodeID]struct{}) (map[NodeID]*NodeScore, error) {
// We first run though the graph once in order to find the median // We first run though the graph once in order to find the median
// channel size. // channel size.
@@ -88,8 +89,10 @@ func (p *PrefAttachment) NodeScores(g ChannelGraph, chans []LocalChannel,
allChans []btcutil.Amount allChans []btcutil.Amount
seenChans = make(map[uint64]struct{}) seenChans = make(map[uint64]struct{})
) )
if err := g.ForEachNode(func(n Node) error { if err := g.ForEachNode(ctx, func(ctx context.Context, n Node) error {
err := n.ForEachChannel(func(e ChannelEdge) error { err := n.ForEachChannel(ctx, func(_ context.Context,
e ChannelEdge) error {
if _, ok := seenChans[e.ChanID.ToUint64()]; ok { if _, ok := seenChans[e.ChanID.ToUint64()]; ok {
return nil return nil
} }
@@ -114,15 +117,19 @@ func (p *PrefAttachment) NodeScores(g ChannelGraph, chans []LocalChannel,
// the graph. // the graph.
var maxChans int var maxChans int
nodeChanNum := make(map[NodeID]int) nodeChanNum := make(map[NodeID]int)
if err := g.ForEachNode(func(n Node) error { if err := g.ForEachNode(ctx, func(ctx context.Context, n Node) error {
var nodeChans int var nodeChans int
err := n.ForEachChannel(func(e ChannelEdge) error { err := n.ForEachChannel(ctx, func(_ context.Context,
e ChannelEdge) error {
// Since connecting to nodes with a lot of small // Since connecting to nodes with a lot of small
// channels actually worsens our connectivity in the // channels actually worsens our connectivity in the
// graph (we will potentially waste time trying to use // graph (we will potentially waste time trying to use
// these useless channels in path finding), we decrease // these useless channels in path finding), we decrease
// the counter for such channels. // the counter for such channels.
if e.Capacity < medianChanSize/minMedianChanSizeFraction { if e.Capacity <
medianChanSize/minMedianChanSizeFraction {
nodeChans-- nodeChans--
return nil return nil
} }
+48 -45
View File
@@ -2,6 +2,7 @@ package autopilot
import ( import (
"bytes" "bytes"
"context"
"errors" "errors"
prand "math/rand" prand "math/rand"
"net" "net"
@@ -13,7 +14,6 @@ import (
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
graphdb "github.com/lightningnetwork/lnd/graph/db" graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/route"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -36,19 +36,7 @@ type testDBGraph struct {
} }
func newDiskChanGraph(t *testing.T) (testGraph, error) { func newDiskChanGraph(t *testing.T) (testGraph, error) {
backend, err := kvdb.GetBoltBackend(&kvdb.BoltBackendConfig{ graphDB := graphdb.MakeTestGraph(t)
DBPath: t.TempDir(),
DBFileName: "graph.db",
NoFreelistSync: true,
AutoCompact: false,
AutoCompactMinAge: kvdb.DefaultBoltAutoCompactMinAge,
DBTimeout: kvdb.DefaultDBTimeout,
})
require.NoError(t, err)
graphDB, err := graphdb.NewChannelGraph(&graphdb.Config{KVDB: backend})
require.NoError(t, err)
require.NoError(t, graphDB.Start()) require.NoError(t, graphDB.Start())
t.Cleanup(func() { t.Cleanup(func() {
require.NoError(t, graphDB.Stop()) require.NoError(t, graphDB.Stop())
@@ -87,6 +75,8 @@ var chanGraphs = []struct {
// TestPrefAttachmentSelectEmptyGraph ensures that when passed an // TestPrefAttachmentSelectEmptyGraph ensures that when passed an
// empty graph, the NodeSores function always returns a score of 0. // empty graph, the NodeSores function always returns a score of 0.
func TestPrefAttachmentSelectEmptyGraph(t *testing.T) { func TestPrefAttachmentSelectEmptyGraph(t *testing.T) {
t.Parallel()
ctx := context.Background()
prefAttach := NewPrefAttachment() prefAttach := NewPrefAttachment()
// Create a random public key, which we will query to get a score for. // Create a random public key, which we will query to get a score for.
@@ -107,7 +97,7 @@ func TestPrefAttachmentSelectEmptyGraph(t *testing.T) {
// attempt to get the score for this one node. // attempt to get the score for this one node.
const walletFunds = btcutil.SatoshiPerBitcoin const walletFunds = btcutil.SatoshiPerBitcoin
scores, err := prefAttach.NodeScores( scores, err := prefAttach.NodeScores(
graph, nil, walletFunds, nodes, ctx, graph, nil, walletFunds, nodes,
) )
require.NoError(t1, err) require.NoError(t1, err)
@@ -126,6 +116,7 @@ func TestPrefAttachmentSelectEmptyGraph(t *testing.T) {
// and the funds are appropriately allocated across each peer. // and the funds are appropriately allocated across each peer.
func TestPrefAttachmentSelectTwoVertexes(t *testing.T) { func TestPrefAttachmentSelectTwoVertexes(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
prand.Seed(time.Now().Unix()) prand.Seed(time.Now().Unix())
@@ -156,10 +147,12 @@ func TestPrefAttachmentSelectTwoVertexes(t *testing.T) {
// Get the score for all nodes found in the graph at // Get the score for all nodes found in the graph at
// this point. // this point.
nodes := make(map[NodeID]struct{}) nodes := make(map[NodeID]struct{})
err = graph.ForEachNode(func(n Node) error { err = graph.ForEachNode(ctx,
nodes[n.PubKey()] = struct{}{} func(_ context.Context, n Node) error {
return nil nodes[n.PubKey()] = struct{}{}
}) return nil
},
)
require.NoError(t1, err) require.NoError(t1, err)
require.Len(t1, nodes, 3) require.Len(t1, nodes, 3)
@@ -168,7 +161,7 @@ func TestPrefAttachmentSelectTwoVertexes(t *testing.T) {
// attempt to get our candidates channel score given // attempt to get our candidates channel score given
// the current state of the graph. // the current state of the graph.
candidates, err := prefAttach.NodeScores( candidates, err := prefAttach.NodeScores(
graph, nil, maxChanSize, nodes, ctx, graph, nil, maxChanSize, nodes,
) )
require.NoError(t1, err) require.NoError(t1, err)
@@ -207,6 +200,7 @@ func TestPrefAttachmentSelectTwoVertexes(t *testing.T) {
// allocate all funds to each vertex (up to the max channel size). // allocate all funds to each vertex (up to the max channel size).
func TestPrefAttachmentSelectGreedyAllocation(t *testing.T) { func TestPrefAttachmentSelectGreedyAllocation(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
prand.Seed(time.Now().Unix()) prand.Seed(time.Now().Unix())
@@ -245,22 +239,25 @@ func TestPrefAttachmentSelectGreedyAllocation(t *testing.T) {
numNodes := 0 numNodes := 0
twoChans := false twoChans := false
nodes := make(map[NodeID]struct{}) nodes := make(map[NodeID]struct{})
err = graph.ForEachNode(func(n Node) error { err = graph.ForEachNode(
numNodes++ ctx, func(ctx context.Context, n Node) error {
nodes[n.PubKey()] = struct{}{} numNodes++
numChans := 0 nodes[n.PubKey()] = struct{}{}
err := n.ForEachChannel(func(c ChannelEdge) error { numChans := 0
numChans++ err := n.ForEachChannel(ctx,
func(_ context.Context, c ChannelEdge) error { //nolint:ll
numChans++
return nil
},
)
if err != nil {
return err
}
twoChans = twoChans || (numChans == 2)
return nil return nil
}) })
if err != nil {
return err
}
twoChans = twoChans || (numChans == 2)
return nil
})
require.NoError(t1, err) require.NoError(t1, err)
require.EqualValues(t1, 3, numNodes) require.EqualValues(t1, 3, numNodes)
@@ -272,7 +269,7 @@ func TestPrefAttachmentSelectGreedyAllocation(t *testing.T) {
// result, the heuristic should try to greedily // result, the heuristic should try to greedily
// allocate funds to channels. // allocate funds to channels.
scores, err := prefAttach.NodeScores( scores, err := prefAttach.NodeScores(
graph, nil, maxChanSize, nodes, ctx, graph, nil, maxChanSize, nodes,
) )
require.NoError(t1, err) require.NoError(t1, err)
@@ -290,7 +287,7 @@ func TestPrefAttachmentSelectGreedyAllocation(t *testing.T) {
// candidates of that size. // candidates of that size.
const remBalance = btcutil.SatoshiPerBitcoin * 0.5 const remBalance = btcutil.SatoshiPerBitcoin * 0.5
scores, err = prefAttach.NodeScores( scores, err = prefAttach.NodeScores(
graph, nil, remBalance, nodes, ctx, graph, nil, remBalance, nodes,
) )
require.NoError(t1, err) require.NoError(t1, err)
@@ -313,6 +310,7 @@ func TestPrefAttachmentSelectGreedyAllocation(t *testing.T) {
// of zero during scoring. // of zero during scoring.
func TestPrefAttachmentSelectSkipNodes(t *testing.T) { func TestPrefAttachmentSelectSkipNodes(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
prand.Seed(time.Now().Unix()) prand.Seed(time.Now().Unix())
@@ -335,10 +333,13 @@ func TestPrefAttachmentSelectSkipNodes(t *testing.T) {
require.NoError(t1, err) require.NoError(t1, err)
nodes := make(map[NodeID]struct{}) nodes := make(map[NodeID]struct{})
err = graph.ForEachNode(func(n Node) error { err = graph.ForEachNode(
nodes[n.PubKey()] = struct{}{} ctx, func(_ context.Context, n Node) error {
return nil nodes[n.PubKey()] = struct{}{}
})
return nil
},
)
require.NoError(t1, err) require.NoError(t1, err)
require.Len(t1, nodes, 2) require.Len(t1, nodes, 2)
@@ -346,7 +347,7 @@ func TestPrefAttachmentSelectSkipNodes(t *testing.T) {
// With our graph created, we'll now get the scores for // With our graph created, we'll now get the scores for
// all nodes in the graph. // all nodes in the graph.
scores, err := prefAttach.NodeScores( scores, err := prefAttach.NodeScores(
graph, nil, maxChanSize, nodes, ctx, graph, nil, maxChanSize, nodes,
) )
require.NoError(t1, err) require.NoError(t1, err)
@@ -374,7 +375,7 @@ func TestPrefAttachmentSelectSkipNodes(t *testing.T) {
// then all nodes should have a score of zero, since we // then all nodes should have a score of zero, since we
// already got channels to them. // already got channels to them.
scores, err = prefAttach.NodeScores( scores, err = prefAttach.NodeScores(
graph, chans, maxChanSize, nodes, ctx, graph, chans, maxChanSize, nodes,
) )
require.NoError(t1, err) require.NoError(t1, err)
@@ -583,9 +584,11 @@ func newMemChannelGraph() *memChannelGraph {
// error, then execution should be terminated. // error, then execution should be terminated.
// //
// NOTE: Part of the autopilot.ChannelGraph interface. // NOTE: Part of the autopilot.ChannelGraph interface.
func (m *memChannelGraph) ForEachNode(cb func(Node) error) error { func (m *memChannelGraph) ForEachNode(ctx context.Context,
cb func(context.Context, Node) error) error {
for _, node := range m.graph { for _, node := range m.graph {
if err := cb(node); err != nil { if err := cb(ctx, node); err != nil {
return err return err
} }
} }
@@ -720,7 +723,7 @@ func (t *testNodeTx) Node() *models.LightningNode {
func (t *testNodeTx) ForEachChannel(f func(*models.ChannelEdgeInfo, func (t *testNodeTx) ForEachChannel(f func(*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error) error { *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error) error {
return t.db.db.ForEachNodeChannel(t.node.PubKeyBytes, func(_ kvdb.RTx, return t.db.db.ForEachNodeChannel(t.node.PubKeyBytes, func(
edge *models.ChannelEdgeInfo, policy1, edge *models.ChannelEdgeInfo, policy1,
policy2 *models.ChannelEdgePolicy) error { policy2 *models.ChannelEdgePolicy) error {
+11
View File
@@ -0,0 +1,11 @@
package autopilot
import (
"testing"
"github.com/lightningnetwork/lnd/kvdb"
)
func TestMain(m *testing.M) {
kvdb.RunTests(m)
}
+16 -9
View File
@@ -1,5 +1,7 @@
package autopilot package autopilot
import "context"
// diameterCutoff is used to discard nodes in the diameter calculation. // diameterCutoff is used to discard nodes in the diameter calculation.
// It is the multiplier for the eccentricity of the highest-degree node, // It is the multiplier for the eccentricity of the highest-degree node,
// serving as a cutoff to discard all nodes with a smaller hop distance. This // serving as a cutoff to discard all nodes with a smaller hop distance. This
@@ -20,7 +22,7 @@ type SimpleGraph struct {
// NewSimpleGraph creates a simplified graph from the current channel graph. // NewSimpleGraph creates a simplified graph from the current channel graph.
// Returns an error if the channel graph iteration fails due to underlying // Returns an error if the channel graph iteration fails due to underlying
// failure. // failure.
func NewSimpleGraph(g ChannelGraph) (*SimpleGraph, error) { func NewSimpleGraph(ctx context.Context, g ChannelGraph) (*SimpleGraph, error) {
nodes := make(map[NodeID]int) nodes := make(map[NodeID]int)
adj := make(map[int][]int) adj := make(map[int][]int)
nextIndex := 0 nextIndex := 0
@@ -42,17 +44,22 @@ func NewSimpleGraph(g ChannelGraph) (*SimpleGraph, error) {
return nodeIndex return nodeIndex
} }
// Iterate over each node and each channel and update the adj and the node // Iterate over each node and each channel and update the adj and the
// index. // node index.
err := g.ForEachNode(func(node Node) error { err := g.ForEachNode(ctx, func(ctx context.Context, node Node) error {
u := getNodeIndex(node) u := getNodeIndex(node)
return node.ForEachChannel(func(edge ChannelEdge) error { return node.ForEachChannel(
v := getNodeIndex(edge.Peer) ctx, func(_ context.Context,
edge ChannelEdge) error {
adj[u] = append(adj[u], v) v := getNodeIndex(edge.Peer)
return nil
}) adj[u] = append(adj[u], v)
return nil
},
)
}) })
if err != nil { if err != nil {
return nil, err return nil, err
+5 -4
View File
@@ -1,6 +1,7 @@
package autopilot package autopilot
import ( import (
"context"
"runtime" "runtime"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
@@ -50,12 +51,12 @@ func (g *TopCentrality) Name() string {
// As our current implementation of betweenness centrality is non-incremental, // As our current implementation of betweenness centrality is non-incremental,
// NodeScores will recalculate the centrality values on every call, which is // NodeScores will recalculate the centrality values on every call, which is
// slow for large graphs. // slow for large graphs.
func (g *TopCentrality) NodeScores(graph ChannelGraph, chans []LocalChannel, func (g *TopCentrality) NodeScores(ctx context.Context, graph ChannelGraph,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( chans []LocalChannel, chanSize btcutil.Amount,
map[NodeID]*NodeScore, error) { nodes map[NodeID]struct{}) (map[NodeID]*NodeScore, error) {
// Calculate betweenness centrality for the whole graph. // Calculate betweenness centrality for the whole graph.
if err := g.centralityMetric.Refresh(graph); err != nil { if err := g.centralityMetric.Refresh(ctx, graph); err != nil {
return nil, err return nil, err
} }
+2 -1
View File
@@ -1,6 +1,7 @@
package autopilot package autopilot
import ( import (
"context"
"testing" "testing"
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
@@ -58,7 +59,7 @@ func testTopCentrality(t *testing.T, graph testGraph,
// Attempt to get centrality scores and expect // Attempt to get centrality scores and expect
// that the result equals with the expected set. // that the result equals with the expected set.
scores, err := topCentrality.NodeScores( scores, err := topCentrality.NodeScores(
graph, channels, chanSize, nodes, context.Background(), graph, channels, chanSize, nodes,
) )
require.NoError(t, err) require.NoError(t, err)
+28 -12
View File
@@ -1,10 +1,10 @@
package batch package batch
import ( import (
"context"
"errors" "errors"
"sync" "sync"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/sqldb" "github.com/lightningnetwork/lnd/sqldb"
) )
@@ -14,28 +14,44 @@ var errSolo = errors.New(
"batch function returned an error and should be re-run solo", "batch function returned an error and should be re-run solo",
) )
type request struct { // txOpts implements the sqldb.TxOptions interface. It is used to indicate that
*Request // the transaction can be read-only or not transaction.
type txOpts struct {
readOnly bool
}
// ReadOnly returns true if the transaction should be read only.
//
// NOTE: This is part of the sqldb.TxOptions interface.
func (t *txOpts) ReadOnly() bool {
return t.readOnly
}
type request[Q any] struct {
*Request[Q]
errChan chan error errChan chan error
} }
type batch struct { type batch[Q any] struct {
db kvdb.Backend db sqldb.BatchedTx[Q]
start sync.Once start sync.Once
reqs []*request reqs []*request[Q]
clear func(b *batch) clear func(b *batch[Q])
locker sync.Locker locker sync.Locker
txOpts txOpts
} }
// trigger is the entry point for the batch and ensures that run is started at // trigger is the entry point for the batch and ensures that run is started at
// most once. // most once.
func (b *batch) trigger() { func (b *batch[Q]) trigger(ctx context.Context) {
b.start.Do(b.run) b.start.Do(func() {
b.run(ctx)
})
} }
// run executes the current batch of requests. If any individual requests fail // run executes the current batch of requests. If any individual requests fail
// alongside others they will be retried by the caller. // alongside others they will be retried by the caller.
func (b *batch) run() { func (b *batch[Q]) run(ctx context.Context) {
// Clear the batch from its scheduler, ensuring that no new requests are // Clear the batch from its scheduler, ensuring that no new requests are
// added to this batch. // added to this batch.
b.clear(b) b.clear(b)
@@ -52,9 +68,9 @@ func (b *batch) run() {
// that fail will be retried individually. // that fail will be retried individually.
for len(b.reqs) > 0 { for len(b.reqs) > 0 {
var failIdx = -1 var failIdx = -1
err := kvdb.Update(b.db, func(tx kvdb.RwTx) error { err := b.db.ExecTx(ctx, &b.txOpts, func(tx Q) error {
for i, req := range b.reqs { for i, req := range b.reqs {
err := req.Update(tx) err := req.Do(tx)
if err != nil { if err != nil {
// If we get a serialization error, we // If we get a serialization error, we
// want the underlying SQL retry // want the underlying SQL retry
+628 -7
View File
@@ -1,7 +1,11 @@
package batch package batch
import ( import (
"context"
"database/sql"
"encoding/binary"
"errors" "errors"
"fmt"
"path/filepath" "path/filepath"
"sync" "sync"
"testing" "testing"
@@ -9,10 +13,26 @@ import (
"github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/walletdb"
"github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/sqldb"
"github.com/lightningnetwork/lnd/sqldb/sqlc"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// batchTestIntervals is a list of batch commit intervals to use for
// benchmarking tests.
var batchTestIntervals = []time.Duration{
time.Millisecond * 0,
time.Millisecond * 50,
time.Millisecond * 100,
time.Millisecond * 200,
time.Millisecond * 500,
}
// TestRetry tests the retry logic of the batch scheduler.
func TestRetry(t *testing.T) { func TestRetry(t *testing.T) {
t.Parallel()
ctx := context.Background()
dbDir := t.TempDir() dbDir := t.TempDir()
dbName := filepath.Join(dbDir, "weks.db") dbName := filepath.Join(dbDir, "weks.db")
@@ -30,19 +50,21 @@ func TestRetry(t *testing.T) {
mu sync.Mutex mu sync.Mutex
called int called int
) )
sched := NewTimeScheduler(db, &mu, time.Second) sched := NewTimeScheduler[kvdb.RwTx](
NewBoltBackend[kvdb.RwTx](db), &mu, time.Second,
)
// First, we construct a request that should retry individually and // First, we construct a request that should retry individually and
// execute it non-lazily. It should still return the error the second // execute it non-lazily. It should still return the error the second
// time. // time.
req := &Request{ req := &Request[kvdb.RwTx]{
Update: func(tx kvdb.RwTx) error { Do: func(tx kvdb.RwTx) error {
called++ called++
return errors.New("test") return errors.New("test")
}, },
} }
err = sched.Execute(req) err = sched.Execute(ctx, req)
// Check and reset the called counter. // Check and reset the called counter.
mu.Lock() mu.Lock()
@@ -56,14 +78,14 @@ func TestRetry(t *testing.T) {
// a serialization error, which should cause the underlying postgres // a serialization error, which should cause the underlying postgres
// transaction to retry. Since we aren't using postgres, this will // transaction to retry. Since we aren't using postgres, this will
// cause the transaction to not be retried at all. // cause the transaction to not be retried at all.
req = &Request{ req = &Request[kvdb.RwTx]{
Update: func(tx kvdb.RwTx) error { Do: func(tx kvdb.RwTx) error {
called++ called++
return errors.New("could not serialize access") return errors.New("could not serialize access")
}, },
} }
err = sched.Execute(req) err = sched.Execute(ctx, req)
// Check the called counter. // Check the called counter.
mu.Lock() mu.Lock()
@@ -72,3 +94,602 @@ func TestRetry(t *testing.T) {
require.ErrorContains(t, err, "could not serialize access") require.ErrorContains(t, err, "could not serialize access")
} }
// TestReadOnly just ensures that nothing breaks if we specify a read-only tx
// and then continue to add a write transaction to the same batch.
func TestReadOnly(t *testing.T) {
t.Parallel()
ctx := context.Background()
t.Run("bbolt-ReadWrite", func(t *testing.T) {
db, err := walletdb.Create(
"bdb", filepath.Join(t.TempDir(), "weks.db"), true,
kvdb.DefaultDBTimeout, false,
)
require.NoError(t, err)
if err != nil {
t.Fatalf("unable to create walletdb: %v", err)
}
t.Cleanup(func() {
require.NoError(t, db.Close())
})
// Create a bbolt read-write scheduler.
rwSche := NewTimeScheduler[kvdb.RwTx](
NewBoltBackend[kvdb.RwTx](db), nil, time.Second,
)
// Call it without a read-only option.
var called bool
req := &Request[kvdb.RwTx]{
Do: func(tx kvdb.RwTx) error {
called = true
return nil
},
}
require.NoError(t, rwSche.Execute(ctx, req))
require.True(t, called)
// Call it with a read-only option.
called = false
req = &Request[kvdb.RwTx]{
Opts: NewSchedulerOptions(ReadOnly()),
Do: func(tx kvdb.RwTx) error {
called = true
return nil
},
}
require.NoError(t, rwSche.Execute(ctx, req))
require.True(t, called)
// Now, spin off a bunch of reads and writes at the same time
// so that we can simulate the upgrade from read-only to
// read-write.
var (
wg sync.WaitGroup
reads = 0
readsMu sync.Mutex
writes = 0
writesMu sync.Mutex
)
for i := 0; i < 100; i++ {
// Spin off the reads.
wg.Add(1)
go func() {
defer wg.Done()
req := &Request[kvdb.RwTx]{
Opts: NewSchedulerOptions(ReadOnly()),
Do: func(tx kvdb.RwTx) error {
readsMu.Lock()
reads++
readsMu.Unlock()
return nil
},
}
require.NoError(t, rwSche.Execute(ctx, req))
}()
// Spin off the writes.
wg.Add(1)
go func() {
defer wg.Done()
req := &Request[kvdb.RwTx]{
Do: func(tx kvdb.RwTx) error {
writesMu.Lock()
writes++
writesMu.Unlock()
return nil
},
}
require.NoError(t, rwSche.Execute(ctx, req))
}()
}
wg.Wait()
require.Equal(t, 100, reads)
require.Equal(t, 100, writes)
})
// Note that if the scheduler is initialized with a read-only bbolt tx,
// then the ReadOnly option does nothing as it will be read-only
// regardless.
t.Run("bbolt-ReadOnly", func(t *testing.T) {
db, err := walletdb.Create(
"bdb", filepath.Join(t.TempDir(), "weks.db"), true,
kvdb.DefaultDBTimeout, false,
)
require.NoError(t, err)
if err != nil {
t.Fatalf("unable to create walletdb: %v", err)
}
t.Cleanup(func() {
require.NoError(t, db.Close())
})
// Create a bbolt read only scheduler.
rwSche := NewTimeScheduler[kvdb.RTx](
NewBoltBackend[kvdb.RTx](db), nil, time.Second,
)
// Call it without a read-only option.
var called bool
req := &Request[kvdb.RTx]{
Do: func(tx kvdb.RTx) error {
called = true
return nil
},
}
require.NoError(t, rwSche.Execute(ctx, req))
require.True(t, called)
// Call it with a read-only option.
called = false
req = &Request[kvdb.RTx]{
Opts: NewSchedulerOptions(ReadOnly()),
Do: func(tx kvdb.RTx) error {
called = true
return nil
},
}
require.NoError(t, rwSche.Execute(ctx, req))
require.True(t, called)
})
t.Run("sql", func(t *testing.T) {
base := sqldb.NewTestSqliteDB(t).BaseDB
db := sqldb.NewTransactionExecutor(
base, func(tx *sql.Tx) *sqlc.Queries {
return base.WithTx(tx)
},
)
// Create a SQL scheduler with a long batch interval.
scheduler := NewTimeScheduler[*sqlc.Queries](
db, nil, time.Second,
)
// writeRecord is a helper that adds a single new invoice to the
// database. It uses the 'i' argument to create a unique hash
// for the invoice.
writeRecord := func(t *testing.T, tx *sqlc.Queries, i int64) {
var hash [8]byte
binary.BigEndian.PutUint64(hash[:], uint64(i))
_, err := tx.InsertInvoice(
ctx, sqlc.InsertInvoiceParams{
Hash: hash[:],
PaymentAddr: hash[:],
PaymentRequestHash: hash[:],
Expiry: -123,
},
)
require.NoError(t, err)
}
// readRecord is a helper that reads a single invoice from the
// database. It uses the 'i' argument to create a unique hash
// for the invoice.
readRecord := func(t *testing.T, tx *sqlc.Queries,
i int) error {
var hash [8]byte
binary.BigEndian.PutUint64(hash[:], uint64(i))
_, err := tx.GetInvoiceByHash(ctx, hash[:])
return err
}
// Execute a bunch of read-only requests in parallel. These
// should be batched together and kept as read only.
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
req := &Request[*sqlc.Queries]{
Opts: NewSchedulerOptions(ReadOnly()),
Do: func(tx *sqlc.Queries) error {
err := readRecord(t, tx, i)
require.ErrorIs(
t, err, sql.ErrNoRows,
)
return nil
},
}
require.NoError(t, scheduler.Execute(ctx, req))
}(i)
}
wg.Wait()
// Now, execute reads and writes in parallel. These should be
// batched together and the tx should be updated to read-write.
// We just simulate this scenario. Write transactions succeeding
// are how we know that the tx was upgraded to read-write.
for i := 0; i < 100; i++ {
// Spin off the writes.
wg.Add(1)
go func(i int) {
defer wg.Done()
req := &Request[*sqlc.Queries]{
Do: func(tx *sqlc.Queries) error {
writeRecord(t, tx, int64(i))
return nil
},
}
require.NoError(t, scheduler.Execute(ctx, req))
}(i)
// Spin off the reads.
wg.Add(1)
go func(i int) {
defer wg.Done()
errExpected := func(err error) {
noRows := errors.Is(err, sql.ErrNoRows)
require.True(t, err == nil || noRows)
}
req := &Request[*sqlc.Queries]{
Opts: NewSchedulerOptions(ReadOnly()),
Do: func(tx *sqlc.Queries) error {
err := readRecord(t, tx, i)
errExpected(err)
return nil
},
}
require.NoError(t, scheduler.Execute(ctx, req))
}(i)
}
wg.Wait()
})
}
// BenchmarkBoltBatching benchmarks the performance of the batch scheduler
// against the bolt backend.
func BenchmarkBoltBatching(b *testing.B) {
setUpDB := func(b *testing.B) kvdb.Backend {
// Create a new database backend for the test.
backend, backendCleanup, err := kvdb.GetTestBackend(
b.TempDir(), "db",
)
require.NoError(b, err)
b.Cleanup(func() { backendCleanup() })
return backend
}
// writeRecord is a helper that writes a simple record to the
// database. It creates a top-level bucket and a sub-bucket, then
// writes a record with a sequence number as the key.
writeRecord := func(b *testing.B, tx kvdb.RwTx) {
bucket, err := tx.CreateTopLevelBucket([]byte("top-level"))
require.NoError(b, err)
subBucket, err := bucket.CreateBucketIfNotExists(
[]byte("sub-bucket"),
)
require.NoError(b, err)
seq, err := subBucket.NextSequence()
require.NoError(b, err)
var key [8]byte
binary.BigEndian.PutUint64(key[:], seq)
err = subBucket.Put(key[:], []byte("value"))
require.NoError(b, err)
}
// verifyRecordsWritten is a helper that verifies that the writeRecord
// helper was called the expected number of times.
verifyRecordsWritten := func(b *testing.B, db kvdb.Backend, N int) {
err := db.View(func(tx kvdb.RTx) error {
bucket := tx.ReadBucket([]byte("top-level"))
require.NotNil(b, bucket)
subBucket := bucket.NestedReadBucket(
[]byte("sub-bucket"),
)
require.NotNil(b, subBucket)
require.EqualValues(b, subBucket.Sequence(), N)
return nil
}, func() {})
require.NoError(b, err)
}
// This test benchmarks the performance when using N new transactions
// for N write queries. This does not use the scheduler.
b.Run("N txs for N write queries", func(b *testing.B) {
db := setUpDB(b)
b.ResetTimer()
var wg sync.WaitGroup
for i := 0; i < b.N; i++ {
wg.Add(1)
go func() {
defer wg.Done()
err := db.Update(func(tx kvdb.RwTx) error {
writeRecord(b, tx)
return nil
}, func() {})
require.NoError(b, err)
}()
}
wg.Wait()
b.StopTimer()
verifyRecordsWritten(b, db, b.N)
})
// This test benchmarks the performance when using a single transaction
// for N write queries. This does not use the scheduler.
b.Run("1 txs for N write queries", func(b *testing.B) {
db := setUpDB(b)
b.ResetTimer()
err := db.Update(func(tx kvdb.RwTx) error {
for i := 0; i < b.N; i++ {
writeRecord(b, tx)
}
return nil
}, func() {})
require.NoError(b, err)
b.StopTimer()
verifyRecordsWritten(b, db, b.N)
})
// batchTest benches the performance of the batch scheduler configured
// with/without the LazyAdd option and with the given commit interval.
batchTest := func(b *testing.B, lazy bool, interval time.Duration) {
ctx := context.Background()
db := setUpDB(b)
scheduler := NewTimeScheduler(
NewBoltBackend[kvdb.RwTx](db), nil, interval,
)
var opts []SchedulerOption
if lazy {
opts = append(opts, LazyAdd())
}
b.ResetTimer()
var wg sync.WaitGroup
for i := 0; i < b.N; i++ {
wg.Add(1)
go func() {
defer wg.Done()
r := &Request[kvdb.RwTx]{
Opts: NewSchedulerOptions(
opts...,
),
Do: func(tx kvdb.RwTx) error {
writeRecord(b, tx)
return nil
},
}
err := scheduler.Execute(ctx, r)
require.NoError(b, err)
}()
}
wg.Wait()
b.StopTimer()
verifyRecordsWritten(b, db, b.N)
}
for _, lazy := range []bool{true, false} {
for _, interval := range batchTestIntervals {
name := fmt.Sprintf(
"batched queries %s lazy: %v", interval, lazy,
)
b.Run(name, func(b *testing.B) {
batchTest(b, lazy, interval)
})
}
}
}
// BenchmarkSQLBatching benchmarks the performance of the batch scheduler
// against the sqlite and postgres backends.
func BenchmarkSQLBatching(b *testing.B) {
b.Run("sqlite", func(b *testing.B) {
benchmarkSQLBatching(b, true)
})
b.Run("postgres", func(b *testing.B) {
benchmarkSQLBatching(b, false)
})
}
// benchmarkSQLBatching benchmarks the performance of the batch scheduler
// against an SQL backend. It uses the AddInvoice query as the operation to
// benchmark.
func benchmarkSQLBatching(b *testing.B, sqlite bool) {
// First create a shared Postgres instance so we don't spawn a new
// docker container for each test.
pgFixture := sqldb.NewTestPgFixture(
b, sqldb.DefaultPostgresFixtureLifetime,
)
b.Cleanup(func() {
pgFixture.TearDown(b)
})
setUpDB := func(b *testing.B) sqldb.BatchedTx[*sqlc.Queries] {
var db *sqldb.BaseDB
if sqlite {
db = sqldb.NewTestSqliteDB(b).BaseDB
} else {
db = sqldb.NewTestPostgresDB(b, pgFixture).BaseDB
}
return sqldb.NewTransactionExecutor(
db, func(tx *sql.Tx) *sqlc.Queries {
return db.WithTx(tx)
},
)
}
ctx := context.Background()
var opts txOpts
// writeRecord is a helper that adds a single new invoice to the
// database. It uses the 'i' argument to create a unique hash for the
// invoice.
writeRecord := func(b *testing.B, tx *sqlc.Queries, i int64) {
var hash [8]byte
binary.BigEndian.PutUint64(hash[:], uint64(i))
_, err := tx.InsertInvoice(ctx, sqlc.InsertInvoiceParams{
Hash: hash[:],
PaymentAddr: hash[:],
PaymentRequestHash: hash[:],
Expiry: -123,
})
require.NoError(b, err)
}
// verifyRecordsWritten is a helper that verifies that the writeRecord
// helper was called the expected number of times. We know that N was
// used to derive the hash for each invoice persisted to the DB, so we
// can use it to verify that the last expected invoice was written.
verifyRecordsWritten := func(b *testing.B,
tx sqldb.BatchedTx[*sqlc.Queries], N int) {
var hash [8]byte
binary.BigEndian.PutUint64(hash[:], uint64(N-1))
err := tx.ExecTx(
ctx, &txOpts{}, func(queries *sqlc.Queries) error {
_, err := queries.GetInvoiceByHash(ctx, hash[:])
require.NoError(b, err)
return nil
}, func() {},
)
require.NoError(b, err)
}
// This test benchmarks the performance when using N new transactions
// for N write queries. This does not use the scheduler.
b.Run("N txs for N write queries", func(b *testing.B) {
db := setUpDB(b)
b.ResetTimer()
var wg sync.WaitGroup
for i := 0; i < b.N; i++ {
wg.Add(1)
go func(j int) {
defer wg.Done()
err := db.ExecTx(
ctx, &opts,
func(tx *sqlc.Queries) error {
writeRecord(b, tx, int64(j))
return nil
}, func() {},
)
require.NoError(b, err)
}(i)
}
wg.Wait()
b.StopTimer()
verifyRecordsWritten(b, db, b.N)
})
// This test benchmarks the performance when using a single transaction
// for N write queries. This does not use the scheduler.
b.Run("1 txs for N write queries", func(b *testing.B) {
db := setUpDB(b)
b.ResetTimer()
err := db.ExecTx(
ctx, &opts,
func(tx *sqlc.Queries) error {
for i := 0; i < b.N; i++ {
writeRecord(b, tx, int64(i))
}
return nil
}, func() {},
)
require.NoError(b, err)
b.StopTimer()
verifyRecordsWritten(b, db, b.N)
})
// batchTest benches the performance of the batch scheduler configured
// with/without the LazyAdd option and with the given commit interval.
batchTest := func(b *testing.B, lazy bool, interval time.Duration) {
db := setUpDB(b)
scheduler := NewTimeScheduler[*sqlc.Queries](
db, nil, interval,
)
var opts []SchedulerOption
if lazy {
opts = append(opts, LazyAdd())
}
b.ResetTimer()
var wg sync.WaitGroup
for i := 0; i < b.N; i++ {
wg.Add(1)
go func(j int) {
defer wg.Done()
r := &Request[*sqlc.Queries]{
Opts: NewSchedulerOptions(
opts...,
),
Do: func(tx *sqlc.Queries) error {
writeRecord(b, tx, int64(j))
return nil
},
}
err := scheduler.Execute(ctx, r)
require.NoError(b, err)
}(i)
}
wg.Wait()
b.StopTimer()
verifyRecordsWritten(b, db, b.N)
}
for _, lazy := range []bool{true, false} {
for _, interval := range batchTestIntervals {
name := fmt.Sprintf(
"batched queries %s lazy: %v", interval, lazy,
)
b.Run(name, func(b *testing.B) {
batchTest(b, lazy, interval)
})
}
}
}
+50 -11
View File
@@ -1,10 +1,13 @@
package batch package batch
import "github.com/lightningnetwork/lnd/kvdb" import "context"
// Request defines an operation that can be batched into a single bbolt // Request defines an operation that can be batched into a single bbolt
// transaction. // transaction.
type Request struct { type Request[Q any] struct {
// Opts holds various configuration options for a scheduled request.
Opts *SchedulerOptions
// Reset is called before each invocation of Update and is used to clear // Reset is called before each invocation of Update and is used to clear
// any possible modifications to local state as a result of previous // any possible modifications to local state as a result of previous
// calls to Update that were not committed due to a concurrent batch // calls to Update that were not committed due to a concurrent batch
@@ -13,10 +16,10 @@ type Request struct {
// NOTE: This field is optional. // NOTE: This field is optional.
Reset func() Reset func()
// Update is applied alongside other operations in the batch. // Do is applied alongside other operations in the batch.
// //
// NOTE: This method MUST NOT acquire any mutexes. // NOTE: This method MUST NOT acquire any mutexes.
Update func(tx kvdb.RwTx) error Do func(tx Q) error
// OnCommit is called if the batch or a subset of the batch including // OnCommit is called if the batch or a subset of the batch including
// this request all succeeded without failure. The passed error should // this request all succeeded without failure. The passed error should
@@ -25,31 +28,67 @@ type Request struct {
// //
// NOTE: This field is optional. // NOTE: This field is optional.
OnCommit func(commitErr error) error OnCommit func(commitErr error) error
}
// lazy should be true if we don't have to immediately execute this // SchedulerOptions holds various configuration options for a scheduled request.
type SchedulerOptions struct {
// Lazy should be true if we don't have to immediately execute this
// request when it comes in. This means that it can be scheduled later, // request when it comes in. This means that it can be scheduled later,
// allowing larger batches. // allowing larger batches.
lazy bool Lazy bool
// ReadOnly should be true if the request is read-only. By default,
// this is false.
ReadOnly bool
}
// NewDefaultSchedulerOpts returns a new SchedulerOptions with default values.
func NewDefaultSchedulerOpts() *SchedulerOptions {
return &SchedulerOptions{
Lazy: false,
ReadOnly: false,
}
}
// NewSchedulerOptions returns a new SchedulerOptions with the given options
// applied on top of the default options.
func NewSchedulerOptions(options ...SchedulerOption) *SchedulerOptions {
opts := NewDefaultSchedulerOpts()
for _, o := range options {
o(opts)
}
return opts
} }
// SchedulerOption is a type that can be used to supply options to a scheduled // SchedulerOption is a type that can be used to supply options to a scheduled
// request. // request.
type SchedulerOption func(r *Request) type SchedulerOption func(*SchedulerOptions)
// LazyAdd will make the request be executed lazily, added to the next batch to // LazyAdd will make the request be executed lazily, added to the next batch to
// reduce db contention. // reduce db contention.
func LazyAdd() SchedulerOption { func LazyAdd() SchedulerOption {
return func(r *Request) { return func(opts *SchedulerOptions) {
r.lazy = true opts.Lazy = true
}
}
// ReadOnly will mark the request as read-only. This means that the
// transaction will be executed in read-only mode, and no changes will be
// made to the database. If any requests in the same batch are not read-only,
// then the entire batch will be executed in read-write mode.
func ReadOnly() SchedulerOption {
return func(opts *SchedulerOptions) {
opts.ReadOnly = true
} }
} }
// Scheduler abstracts a generic batching engine that accumulates an incoming // Scheduler abstracts a generic batching engine that accumulates an incoming
// set of Requests, executes them, and returns the error from the operation. // set of Requests, executes them, and returns the error from the operation.
type Scheduler interface { type Scheduler[Q any] interface {
// Execute schedules a Request for execution with the next available // Execute schedules a Request for execution with the next available
// batch. This method blocks until the underlying closure has been // batch. This method blocks until the underlying closure has been
// run against the database. The resulting error is returned to the // run against the database. The resulting error is returned to the
// caller. // caller.
Execute(req *Request) error Execute(ctx context.Context, req *Request[Q]) error
} }
+50
View File
@@ -0,0 +1,50 @@
package batch
import (
"context"
"fmt"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/sqldb"
)
// BoltBatcher is a bbolt implementation of the sqldb.BatchedTx interface.
type BoltBatcher[Q any] struct {
db kvdb.Backend
}
// NewBoltBackend creates a new BoltBackend instance.
func NewBoltBackend[Q any](db kvdb.Backend) *BoltBatcher[Q] {
return &BoltBatcher[Q]{db: db}
}
// ExecTx will execute the passed txBody, operating upon generic
// parameter Q (usually a storage interface) in a single transaction.
//
// NOTE: This is part of the sqldb.BatchedTx interface.
func (t *BoltBatcher[Q]) ExecTx(_ context.Context, opts sqldb.TxOptions,
txBody func(Q) error, reset func()) error {
if opts.ReadOnly() {
return kvdb.View(t.db, func(tx kvdb.RTx) error {
q, ok := any(tx).(Q)
if !ok {
return fmt.Errorf("unable to cast tx(%T) "+
"into the type expected by the "+
"BoltBatcher(%T)", tx, t)
}
return txBody(q)
}, reset)
}
return kvdb.Update(t.db, func(tx kvdb.RwTx) error {
q, ok := any(tx).(Q)
if !ok {
return fmt.Errorf("unable to cast tx(%T) into the "+
"type expected by the BoltBatcher(%T)", tx, t)
}
return txBody(q)
}, reset)
}
+44 -16
View File
@@ -1,10 +1,11 @@
package batch package batch
import ( import (
"context"
"sync" "sync"
"time" "time"
"github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/sqldb"
) )
// TimeScheduler is a batching engine that executes requests within a fixed // TimeScheduler is a batching engine that executes requests within a fixed
@@ -12,13 +13,13 @@ import (
// configurable duration for other concurrent requests to join the batch. Once // configurable duration for other concurrent requests to join the batch. Once
// this time has elapsed, the batch is closed and executed. Subsequent requests // this time has elapsed, the batch is closed and executed. Subsequent requests
// are then added to a new batch which undergoes the same process. // are then added to a new batch which undergoes the same process.
type TimeScheduler struct { type TimeScheduler[Q any] struct {
db kvdb.Backend db sqldb.BatchedTx[Q]
locker sync.Locker locker sync.Locker
duration time.Duration duration time.Duration
mu sync.Mutex mu sync.Mutex
b *batch b *batch[Q]
} }
// NewTimeScheduler initializes a new TimeScheduler with a fixed duration at // NewTimeScheduler initializes a new TimeScheduler with a fixed duration at
@@ -26,10 +27,10 @@ type TimeScheduler struct {
// cache, the cache's lock should be provided to so that external consistency // cache, the cache's lock should be provided to so that external consistency
// can be maintained, as successful db operations will cause a request's // can be maintained, as successful db operations will cause a request's
// OnCommit method to be executed while holding this lock. // OnCommit method to be executed while holding this lock.
func NewTimeScheduler(db kvdb.Backend, locker sync.Locker, func NewTimeScheduler[Q any](db sqldb.BatchedTx[Q], locker sync.Locker,
duration time.Duration) *TimeScheduler { duration time.Duration) *TimeScheduler[Q] {
return &TimeScheduler{ return &TimeScheduler[Q]{
db: db, db: db,
locker: locker, locker: locker,
duration: duration, duration: duration,
@@ -42,8 +43,12 @@ func NewTimeScheduler(db kvdb.Backend, locker sync.Locker,
// underlying operation is returned to the caller. // underlying operation is returned to the caller.
// //
// NOTE: Part of the Scheduler interface. // NOTE: Part of the Scheduler interface.
func (s *TimeScheduler) Execute(r *Request) error { func (s *TimeScheduler[Q]) Execute(ctx context.Context, r *Request[Q]) error {
req := request{ if r.Opts == nil {
r.Opts = NewDefaultSchedulerOpts()
}
req := request[Q]{
Request: r, Request: r,
errChan: make(chan error, 1), errChan: make(chan error, 1),
} }
@@ -52,20 +57,41 @@ func (s *TimeScheduler) Execute(r *Request) error {
// or no batch exists, create a new one. // or no batch exists, create a new one.
s.mu.Lock() s.mu.Lock()
if s.b == nil { if s.b == nil {
s.b = &batch{ s.b = &batch[Q]{
db: s.db, db: s.db,
clear: s.clear, clear: s.clear,
locker: s.locker, locker: s.locker,
// By default, we assume that the batch is read-only,
// and we only upgrade it to read-write if a request
// is added that is not read-only.
txOpts: txOpts{
readOnly: true,
},
} }
time.AfterFunc(s.duration, s.b.trigger) trigger := s.b.trigger
time.AfterFunc(s.duration, func() {
trigger(ctx)
})
} }
s.b.reqs = append(s.b.reqs, &req) s.b.reqs = append(s.b.reqs, &req)
// If this is a non-lazy request, we'll execute the batch immediately. // We only upgrade the batch to read-write if the new request is not
if !r.lazy { // read-only. If it is already read-write, we don't need to do anything.
go s.b.trigger() if s.b.txOpts.readOnly && !r.Opts.ReadOnly {
s.b.txOpts.readOnly = false
} }
// If this is a non-lazy request, we'll execute the batch immediately.
if !r.Opts.Lazy {
go s.b.trigger(ctx)
}
// We need to grab a reference to the batch's txOpts so that we can
// pass it before we unlock the scheduler's mutex since the batch may
// be set to nil before we access the txOpts below.
txOpts := s.b.txOpts
s.mu.Unlock() s.mu.Unlock()
// Wait for the batch to process the request. If the batch didn't // Wait for the batch to process the request. If the batch didn't
@@ -83,7 +109,9 @@ func (s *TimeScheduler) Execute(r *Request) error {
} }
// Otherwise, run the request on its own. // Otherwise, run the request on its own.
commitErr := kvdb.Update(s.db, req.Update, func() { commitErr := s.db.ExecTx(ctx, &txOpts, func(tx Q) error {
return req.Do(tx)
}, func() {
if req.Reset != nil { if req.Reset != nil {
req.Reset() req.Reset()
} }
@@ -100,7 +128,7 @@ func (s *TimeScheduler) Execute(r *Request) error {
// clear resets the scheduler's batch to nil so that no more requests can be // clear resets the scheduler's batch to nil so that no more requests can be
// added. // added.
func (s *TimeScheduler) clear(b *batch) { func (s *TimeScheduler[Q]) clear(b *batch[Q]) {
s.mu.Lock() s.mu.Lock()
if s.b == b { if s.b == b {
s.b = nil s.b = nil
+11 -11
View File
@@ -14,7 +14,6 @@ import (
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/kvdb"
@@ -185,21 +184,22 @@ func TestMultiSourceAddrsForNode(t *testing.T) {
fullDB, err := MakeTestDB(t) fullDB, err := MakeTestDB(t)
require.NoError(t, err, "unable to make test database") require.NoError(t, err, "unable to make test database")
graph, err := graphdb.MakeTestGraph(t) graph := newMockAddrSource(t)
require.NoError(t, err) t.Cleanup(func() {
graph.AssertExpectations(t)
})
// We'll make a test vertex to insert into the database, as the source // We'll make a test vertex, but this node will only have half the
// node, but this node will only have half the number of addresses it // number of addresses it usually does.
// usually does.
testNode := createTestVertex(t) testNode := createTestVertex(t)
require.NoError(t, err, "unable to create test node") nodePub, err := testNode.PubKey()
testNode.Addresses = []net.Addr{testAddr} require.NoError(t, err)
require.NoError(t, graph.SetSourceNode(testNode)) graph.On("AddrsForNode", nodePub).Return(
true, []net.Addr{testAddr}, nil,
).Once()
// Next, we'll make a link node with the same pubkey, but with an // Next, we'll make a link node with the same pubkey, but with an
// additional address. // additional address.
nodePub, err := testNode.PubKey()
require.NoError(t, err, "unable to recv node pub")
linkNode := NewLinkNode( linkNode := NewLinkNode(
fullDB.channelStateDB.linkNodeDB, wire.MainNet, nodePub, fullDB.channelStateDB.linkNodeDB, wire.MainNet, nodePub,
anotherAddr, anotherAddr,
-8
View File
@@ -6,7 +6,6 @@ import (
"github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/walletdb"
"github.com/go-errors/errors" "github.com/go-errors/errors"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/kvdb"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -22,13 +21,6 @@ func applyMigration(t *testing.T, beforeMigration, afterMigration func(d *DB),
} }
cdb.dryRun = dryRun cdb.dryRun = dryRun
// Create a test node that will be our source node.
testNode := createTestVertex(t)
graph, err := graphdb.MakeTestGraph(t)
require.NoError(t, err)
require.NoError(t, graph.SetSourceNode(testNode))
// beforeMigration usually used for populating the database // beforeMigration usually used for populating the database
// with test data. // with test data.
beforeMigration(cdb) beforeMigration(cdb)
+8 -4
View File
@@ -1046,10 +1046,14 @@ func (d *DefaultDatabaseBuilder) BuildDatabase(
) )
} }
dbs.GraphDB, err = graphdb.NewChannelGraph(&graphdb.Config{ graphStore, err := graphdb.NewKVStore(
KVDB: databaseBackends.GraphDB, databaseBackends.GraphDB, graphDBOptions...,
KVStoreOpts: graphDBOptions, )
}, chanGraphOpts...) if err != nil {
return nil, nil, err
}
dbs.GraphDB, err = graphdb.NewChannelGraph(graphStore, chanGraphOpts...)
if err != nil { if err != nil {
cleanUp() cleanUp()
+18 -7
View File
@@ -2,6 +2,7 @@ package discovery
import ( import (
"bytes" "bytes"
"context"
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"errors" "errors"
@@ -36,8 +37,9 @@ type NetworkPeerBootstrapper interface {
// denotes how many valid peer addresses to return. The passed set of // denotes how many valid peer addresses to return. The passed set of
// node nodes allows the caller to ignore a set of nodes perhaps // node nodes allows the caller to ignore a set of nodes perhaps
// because they already have connections established. // because they already have connections established.
SampleNodeAddrs(numAddrs uint32, SampleNodeAddrs(ctx context.Context, numAddrs uint32,
ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, error) ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress,
error)
// Name returns a human readable string which names the concrete // Name returns a human readable string which names the concrete
// implementation of the NetworkPeerBootstrapper. // implementation of the NetworkPeerBootstrapper.
@@ -50,7 +52,8 @@ type NetworkPeerBootstrapper interface {
// bootstrapper will be queried successively until the target amount is met. If // bootstrapper will be queried successively until the target amount is met. If
// the ignore map is populated, then the bootstrappers will be instructed to // the ignore map is populated, then the bootstrappers will be instructed to
// skip those nodes. // skip those nodes.
func MultiSourceBootstrap(ignore map[autopilot.NodeID]struct{}, numAddrs uint32, func MultiSourceBootstrap(ctx context.Context,
ignore map[autopilot.NodeID]struct{}, numAddrs uint32,
bootstrappers ...NetworkPeerBootstrapper) ([]*lnwire.NetAddress, error) { bootstrappers ...NetworkPeerBootstrapper) ([]*lnwire.NetAddress, error) {
// We'll randomly shuffle our bootstrappers before querying them in // We'll randomly shuffle our bootstrappers before querying them in
@@ -73,7 +76,9 @@ func MultiSourceBootstrap(ignore map[autopilot.NodeID]struct{}, numAddrs uint32,
// the number of address remaining that we need to fetch. // the number of address remaining that we need to fetch.
numAddrsLeft := numAddrs - uint32(len(addrs)) numAddrsLeft := numAddrs - uint32(len(addrs))
log.Tracef("Querying for %v addresses", numAddrsLeft) log.Tracef("Querying for %v addresses", numAddrsLeft)
netAddrs, err := bootstrapper.SampleNodeAddrs(numAddrsLeft, ignore) netAddrs, err := bootstrapper.SampleNodeAddrs(
ctx, numAddrsLeft, ignore,
)
if err != nil { if err != nil {
// If we encounter an error with a bootstrapper, then // If we encounter an error with a bootstrapper, then
// we'll continue on to the next available // we'll continue on to the next available
@@ -152,9 +157,12 @@ func NewGraphBootstrapper(cg autopilot.ChannelGraph) (NetworkPeerBootstrapper, e
// many valid peer addresses to return. // many valid peer addresses to return.
// //
// NOTE: Part of the NetworkPeerBootstrapper interface. // NOTE: Part of the NetworkPeerBootstrapper interface.
func (c *ChannelGraphBootstrapper) SampleNodeAddrs(numAddrs uint32, func (c *ChannelGraphBootstrapper) SampleNodeAddrs(_ context.Context,
numAddrs uint32,
ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, error) { ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, error) {
ctx := context.TODO()
// We'll merge the ignore map with our currently selected map in order // We'll merge the ignore map with our currently selected map in order
// to ensure we don't return any duplicate nodes. // to ensure we don't return any duplicate nodes.
for n := range ignore { for n := range ignore {
@@ -177,7 +185,9 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(numAddrs uint32,
errFound = fmt.Errorf("found node") errFound = fmt.Errorf("found node")
) )
err := c.chanGraph.ForEachNode(func(node autopilot.Node) error { err := c.chanGraph.ForEachNode(ctx, func(_ context.Context,
node autopilot.Node) error {
nID := autopilot.NodeID(node.PubKey()) nID := autopilot.NodeID(node.PubKey())
if _, ok := c.tried[nID]; ok { if _, ok := c.tried[nID]; ok {
return nil return nil
@@ -382,7 +392,8 @@ func (d *DNSSeedBootstrapper) fallBackSRVLookup(soaShim string,
// network peer bootstrapper source. The num addrs field passed in denotes how // network peer bootstrapper source. The num addrs field passed in denotes how
// many valid peer addresses to return. The set of DNS seeds are used // many valid peer addresses to return. The set of DNS seeds are used
// successively to retrieve eligible target nodes. // successively to retrieve eligible target nodes.
func (d *DNSSeedBootstrapper) SampleNodeAddrs(numAddrs uint32, func (d *DNSSeedBootstrapper) SampleNodeAddrs(_ context.Context,
numAddrs uint32,
ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, error) { ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, error) {
var netAddrs []*lnwire.NetAddress var netAddrs []*lnwire.NetAddress
+82 -58
View File
@@ -2,6 +2,7 @@ package discovery
import ( import (
"bytes" "bytes"
"context"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
@@ -475,9 +476,6 @@ type AuthenticatedGossiper struct {
// held. // held.
bestHeight uint32 bestHeight uint32
quit chan struct{}
wg sync.WaitGroup
// cfg is a copy of the configuration struct that the gossiper service // cfg is a copy of the configuration struct that the gossiper service
// was initialized with. // was initialized with.
cfg *Config cfg *Config
@@ -555,6 +553,10 @@ type AuthenticatedGossiper struct {
vb *ValidationBarrier vb *ValidationBarrier
sync.Mutex sync.Mutex
cancel fn.Option[context.CancelFunc]
quit chan struct{}
wg sync.WaitGroup
} }
// New creates a new AuthenticatedGossiper instance, initialized with the // New creates a new AuthenticatedGossiper instance, initialized with the
@@ -644,13 +646,16 @@ func (d *AuthenticatedGossiper) PropagateChanPolicyUpdate(
func (d *AuthenticatedGossiper) Start() error { func (d *AuthenticatedGossiper) Start() error {
var err error var err error
d.started.Do(func() { d.started.Do(func() {
ctx, cancel := context.WithCancel(context.Background())
d.cancel = fn.Some(cancel)
log.Info("Authenticated Gossiper starting") log.Info("Authenticated Gossiper starting")
err = d.start() err = d.start(ctx)
}) })
return err return err
} }
func (d *AuthenticatedGossiper) start() error { func (d *AuthenticatedGossiper) start(ctx context.Context) error {
// First we register for new notifications of newly discovered blocks. // First we register for new notifications of newly discovered blocks.
// We do this immediately so we'll later be able to consume any/all // We do this immediately so we'll later be able to consume any/all
// blocks which were discovered. // blocks which were discovered.
@@ -680,7 +685,7 @@ func (d *AuthenticatedGossiper) start() error {
// Start receiving blocks in its dedicated goroutine. // Start receiving blocks in its dedicated goroutine.
d.wg.Add(2) d.wg.Add(2)
go d.syncBlockHeight() go d.syncBlockHeight()
go d.networkHandler() go d.networkHandler(ctx)
return nil return nil
} }
@@ -835,6 +840,7 @@ func (d *AuthenticatedGossiper) stop() {
d.banman.stop() d.banman.stop()
d.cancel.WhenSome(func(fn context.CancelFunc) { fn() })
close(d.quit) close(d.quit)
d.wg.Wait() d.wg.Wait()
@@ -851,8 +857,8 @@ func (d *AuthenticatedGossiper) stop() {
// then added to a queue for batched trickled announcement to all connected // then added to a queue for batched trickled announcement to all connected
// peers. Remote channel announcements should contain the announcement proof // peers. Remote channel announcements should contain the announcement proof
// and be fully validated. // and be fully validated.
func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(msg lnwire.Message, func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(ctx context.Context,
peer lnpeer.Peer) chan error { msg lnwire.Message, peer lnpeer.Peer) chan error {
log.Debugf("Processing remote msg %T from peer=%x", msg, peer.PubKey()) log.Debugf("Processing remote msg %T from peer=%x", msg, peer.PubKey())
@@ -901,7 +907,7 @@ func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(msg lnwire.Message,
// If we've found the message target, then we'll dispatch the // If we've found the message target, then we'll dispatch the
// message directly to it. // message directly to it.
if err := syncer.ApplyGossipFilter(m); err != nil { if err := syncer.ApplyGossipFilter(ctx, m); err != nil {
log.Warnf("Unable to apply gossip filter for peer=%x: "+ log.Warnf("Unable to apply gossip filter for peer=%x: "+
"%v", peer.PubKey(), err) "%v", peer.PubKey(), err)
@@ -942,8 +948,12 @@ func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(msg lnwire.Message,
// If the peer that sent us this error is quitting, then we don't need // If the peer that sent us this error is quitting, then we don't need
// to send back an error and can return immediately. // to send back an error and can return immediately.
// TODO(elle): the peer should now just rely on canceling the passed
// context.
case <-peer.QuitSignal(): case <-peer.QuitSignal():
return nil return nil
case <-ctx.Done():
return nil
case <-d.quit: case <-d.quit:
nMsg.err <- ErrGossiperShuttingDown nMsg.err <- ErrGossiperShuttingDown
} }
@@ -1337,7 +1347,7 @@ func (d *AuthenticatedGossiper) splitAnnouncementBatches(
// split size, and then sends out all items to the set of target peers. Locally // split size, and then sends out all items to the set of target peers. Locally
// generated announcements are always sent before remotely generated // generated announcements are always sent before remotely generated
// announcements. // announcements.
func (d *AuthenticatedGossiper) splitAndSendAnnBatch( func (d *AuthenticatedGossiper) splitAndSendAnnBatch(ctx context.Context,
annBatch msgsToBroadcast) { annBatch msgsToBroadcast) {
// delayNextBatch is a helper closure that blocks for `SubBatchDelay` // delayNextBatch is a helper closure that blocks for `SubBatchDelay`
@@ -1374,7 +1384,7 @@ func (d *AuthenticatedGossiper) splitAndSendAnnBatch(
// Now send the remote announcements. // Now send the remote announcements.
for _, annBatch := range remoteBatches { for _, annBatch := range remoteBatches {
d.sendRemoteBatch(annBatch) d.sendRemoteBatch(ctx, annBatch)
delayNextBatch() delayNextBatch()
} }
}() }()
@@ -1398,14 +1408,16 @@ func (d *AuthenticatedGossiper) sendLocalBatch(annBatch []msgWithSenders) {
// sendRemoteBatch broadcasts a list of remotely generated announcements to our // sendRemoteBatch broadcasts a list of remotely generated announcements to our
// peers. // peers.
func (d *AuthenticatedGossiper) sendRemoteBatch(annBatch []msgWithSenders) { func (d *AuthenticatedGossiper) sendRemoteBatch(ctx context.Context,
annBatch []msgWithSenders) {
syncerPeers := d.syncMgr.GossipSyncers() syncerPeers := d.syncMgr.GossipSyncers()
// We'll first attempt to filter out this new message for all peers // We'll first attempt to filter out this new message for all peers
// that have active gossip syncers active. // that have active gossip syncers active.
for pub, syncer := range syncerPeers { for pub, syncer := range syncerPeers {
log.Tracef("Sending messages batch to GossipSyncer(%s)", pub) log.Tracef("Sending messages batch to GossipSyncer(%s)", pub)
syncer.FilterGossipMsgs(annBatch...) syncer.FilterGossipMsgs(ctx, annBatch...)
} }
for _, msgChunk := range annBatch { for _, msgChunk := range annBatch {
@@ -1431,7 +1443,7 @@ func (d *AuthenticatedGossiper) sendRemoteBatch(annBatch []msgWithSenders) {
// broadcasting our latest topology state to all connected peers. // broadcasting our latest topology state to all connected peers.
// //
// NOTE: This MUST be run as a goroutine. // NOTE: This MUST be run as a goroutine.
func (d *AuthenticatedGossiper) networkHandler() { func (d *AuthenticatedGossiper) networkHandler(ctx context.Context) {
defer d.wg.Done() defer d.wg.Done()
// Initialize empty deDupedAnnouncements to store announcement batch. // Initialize empty deDupedAnnouncements to store announcement batch.
@@ -1446,7 +1458,7 @@ func (d *AuthenticatedGossiper) networkHandler() {
// To start, we'll first check to see if there are any stale channel or // To start, we'll first check to see if there are any stale channel or
// node announcements that we need to re-transmit. // node announcements that we need to re-transmit.
if err := d.retransmitStaleAnns(time.Now()); err != nil { if err := d.retransmitStaleAnns(ctx, time.Now()); err != nil {
log.Errorf("Unable to rebroadcast stale announcements: %v", err) log.Errorf("Unable to rebroadcast stale announcements: %v", err)
} }
@@ -1463,7 +1475,7 @@ func (d *AuthenticatedGossiper) networkHandler() {
// the affected channels and also update the underlying // the affected channels and also update the underlying
// graph with the new state. // graph with the new state.
newChanUpdates, err := d.processChanPolicyUpdate( newChanUpdates, err := d.processChanPolicyUpdate(
policyUpdate.edgesToUpdate, ctx, policyUpdate.edgesToUpdate,
) )
policyUpdate.errChan <- err policyUpdate.errChan <- err
if err != nil { if err != nil {
@@ -1488,7 +1500,7 @@ func (d *AuthenticatedGossiper) networkHandler() {
// messages that we'll process serially. // messages that we'll process serially.
case *lnwire.AnnounceSignatures1: case *lnwire.AnnounceSignatures1:
emittedAnnouncements, _ := d.processNetworkAnnouncement( emittedAnnouncements, _ := d.processNetworkAnnouncement(
announcement, ctx, announcement,
) )
log.Debugf("Processed network message %s, "+ log.Debugf("Processed network message %s, "+
"returned len(announcements)=%v", "returned len(announcements)=%v",
@@ -1528,7 +1540,7 @@ func (d *AuthenticatedGossiper) networkHandler() {
d.wg.Add(1) d.wg.Add(1)
go d.handleNetworkMessages( go d.handleNetworkMessages(
announcement, &announcements, annJobID, ctx, announcement, &announcements, annJobID,
) )
// The trickle timer has ticked, which indicates we should // The trickle timer has ticked, which indicates we should
@@ -1551,7 +1563,7 @@ func (d *AuthenticatedGossiper) networkHandler() {
// announcements, we'll blast them out w/o regard for // announcements, we'll blast them out w/o regard for
// our peer's policies so we ensure they propagate // our peer's policies so we ensure they propagate
// properly. // properly.
d.splitAndSendAnnBatch(announcementBatch) d.splitAndSendAnnBatch(ctx, announcementBatch)
// The retransmission timer has ticked which indicates that we // The retransmission timer has ticked which indicates that we
// should check if we need to prune or re-broadcast any of our // should check if we need to prune or re-broadcast any of our
@@ -1560,7 +1572,7 @@ func (d *AuthenticatedGossiper) networkHandler() {
// have been dropped, or not properly propagated through the // have been dropped, or not properly propagated through the
// network. // network.
case tick := <-d.cfg.RetransmitTicker.Ticks(): case tick := <-d.cfg.RetransmitTicker.Ticks():
if err := d.retransmitStaleAnns(tick); err != nil { if err := d.retransmitStaleAnns(ctx, tick); err != nil {
log.Errorf("unable to rebroadcast stale "+ log.Errorf("unable to rebroadcast stale "+
"announcements: %v", err) "announcements: %v", err)
} }
@@ -1578,8 +1590,8 @@ func (d *AuthenticatedGossiper) networkHandler() {
// signal its dependants and add the new announcements to the announce batch. // signal its dependants and add the new announcements to the announce batch.
// //
// NOTE: must be run as a goroutine. // NOTE: must be run as a goroutine.
func (d *AuthenticatedGossiper) handleNetworkMessages(nMsg *networkMsg, func (d *AuthenticatedGossiper) handleNetworkMessages(ctx context.Context,
deDuped *deDupedAnnouncements, jobID JobID) { nMsg *networkMsg, deDuped *deDupedAnnouncements, jobID JobID) {
defer d.wg.Done() defer d.wg.Done()
defer d.vb.CompleteJob() defer d.vb.CompleteJob()
@@ -1607,7 +1619,7 @@ func (d *AuthenticatedGossiper) handleNetworkMessages(nMsg *networkMsg,
// Process the network announcement to determine if this is either a // Process the network announcement to determine if this is either a
// new announcement from our PoV or an edges to a prior vertex/edge we // new announcement from our PoV or an edges to a prior vertex/edge we
// previously proceeded. // previously proceeded.
newAnns, allow := d.processNetworkAnnouncement(nMsg) newAnns, allow := d.processNetworkAnnouncement(ctx, nMsg)
log.Tracef("Processed network message %s, returned "+ log.Tracef("Processed network message %s, returned "+
"len(announcements)=%v, allowDependents=%v", "len(announcements)=%v, allowDependents=%v",
@@ -1681,7 +1693,9 @@ func (d *AuthenticatedGossiper) isRecentlyRejectedMsg(msg lnwire.Message,
// stale iff, the last timestamp of its rebroadcast is older than the // stale iff, the last timestamp of its rebroadcast is older than the
// RebroadcastInterval. We also check if a refreshed node announcement should // RebroadcastInterval. We also check if a refreshed node announcement should
// be resent. // be resent.
func (d *AuthenticatedGossiper) retransmitStaleAnns(now time.Time) error { func (d *AuthenticatedGossiper) retransmitStaleAnns(ctx context.Context,
now time.Time) error {
// Iterate over all of our channels and check if any of them fall // Iterate over all of our channels and check if any of them fall
// within the prune interval or re-broadcast interval. // within the prune interval or re-broadcast interval.
type updateTuple struct { type updateTuple struct {
@@ -1753,7 +1767,7 @@ func (d *AuthenticatedGossiper) retransmitStaleAnns(now time.Time) error {
// Re-sign and update the channel on disk and retrieve our // Re-sign and update the channel on disk and retrieve our
// ChannelUpdate to broadcast. // ChannelUpdate to broadcast.
chanAnn, chanUpdate, err := d.updateChannel( chanAnn, chanUpdate, err := d.updateChannel(
chanToUpdate.info, chanToUpdate.edge, ctx, chanToUpdate.info, chanToUpdate.edge,
) )
if err != nil { if err != nil {
return fmt.Errorf("unable to update channel: %w", err) return fmt.Errorf("unable to update channel: %w", err)
@@ -1794,7 +1808,7 @@ func (d *AuthenticatedGossiper) retransmitStaleAnns(now time.Time) error {
// Before broadcasting the refreshed node announcement, add it // Before broadcasting the refreshed node announcement, add it
// to our own graph. // to our own graph.
if err := d.addNode(&newNodeAnn); err != nil { if err := d.addNode(ctx, &newNodeAnn); err != nil {
log.Errorf("Unable to add refreshed node announcement "+ log.Errorf("Unable to add refreshed node announcement "+
"to graph: %v", err) "to graph: %v", err)
} }
@@ -1820,7 +1834,7 @@ func (d *AuthenticatedGossiper) retransmitStaleAnns(now time.Time) error {
// processChanPolicyUpdate generates a new set of channel updates for the // processChanPolicyUpdate generates a new set of channel updates for the
// provided list of edges and updates the backing ChannelGraphSource. // provided list of edges and updates the backing ChannelGraphSource.
func (d *AuthenticatedGossiper) processChanPolicyUpdate( func (d *AuthenticatedGossiper) processChanPolicyUpdate(ctx context.Context,
edgesToUpdate []EdgeWithInfo) ([]networkMsg, error) { edgesToUpdate []EdgeWithInfo) ([]networkMsg, error) {
var chanUpdates []networkMsg var chanUpdates []networkMsg
@@ -1829,7 +1843,7 @@ func (d *AuthenticatedGossiper) processChanPolicyUpdate(
// we'll re-sign and update the backing ChannelGraphSource, and // we'll re-sign and update the backing ChannelGraphSource, and
// retrieve our ChannelUpdate to broadcast. // retrieve our ChannelUpdate to broadcast.
_, chanUpdate, err := d.updateChannel( _, chanUpdate, err := d.updateChannel(
edgeInfo.Info, edgeInfo.Edge, ctx, edgeInfo.Info, edgeInfo.Edge,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@@ -1875,7 +1889,7 @@ func (d *AuthenticatedGossiper) processChanPolicyUpdate(
edgeInfo.Info, chanUpdate.ChannelFlags, edgeInfo.Info, chanUpdate.ChannelFlags,
) )
err := d.reliableSender.sendMessage( err := d.reliableSender.sendMessage(
chanUpdate, remotePubKey, ctx, chanUpdate, remotePubKey,
) )
if err != nil { if err != nil {
log.Errorf("Unable to reliably send %v for "+ log.Errorf("Unable to reliably send %v for "+
@@ -1922,7 +1936,7 @@ func remotePubFromChanInfo(chanInfo *models.ChannelEdgeInfo,
// situation in the case where we create a channel, but for some reason fail // situation in the case where we create a channel, but for some reason fail
// to receive the remote peer's proof, while the remote peer is able to fully // to receive the remote peer's proof, while the remote peer is able to fully
// assemble the proof and craft the ChannelAnnouncement. // assemble the proof and craft the ChannelAnnouncement.
func (d *AuthenticatedGossiper) processRejectedEdge( func (d *AuthenticatedGossiper) processRejectedEdge(_ context.Context,
chanAnnMsg *lnwire.ChannelAnnouncement1, chanAnnMsg *lnwire.ChannelAnnouncement1,
proof *models.ChannelAuthProof) ([]networkMsg, error) { proof *models.ChannelAuthProof) ([]networkMsg, error) {
@@ -2010,8 +2024,8 @@ func (d *AuthenticatedGossiper) fetchPKScript(chanID *lnwire.ShortChannelID) (
// addNode processes the given node announcement, and adds it to our channel // addNode processes the given node announcement, and adds it to our channel
// graph. // graph.
func (d *AuthenticatedGossiper) addNode(msg *lnwire.NodeAnnouncement, func (d *AuthenticatedGossiper) addNode(_ context.Context,
op ...batch.SchedulerOption) error { msg *lnwire.NodeAnnouncement, op ...batch.SchedulerOption) error {
if err := netann.ValidateNodeAnn(msg); err != nil { if err := netann.ValidateNodeAnn(msg); err != nil {
return fmt.Errorf("unable to validate node announcement: %w", return fmt.Errorf("unable to validate node announcement: %w",
@@ -2086,7 +2100,7 @@ func (d *AuthenticatedGossiper) isPremature(chanID lnwire.ShortChannelID,
// be returned which should be broadcasted to the rest of the network. The // be returned which should be broadcasted to the rest of the network. The
// boolean returned indicates whether any dependents of the announcement should // boolean returned indicates whether any dependents of the announcement should
// attempt to be processed as well. // attempt to be processed as well.
func (d *AuthenticatedGossiper) processNetworkAnnouncement( func (d *AuthenticatedGossiper) processNetworkAnnouncement(ctx context.Context,
nMsg *networkMsg) ([]networkMsg, bool) { nMsg *networkMsg) ([]networkMsg, bool) {
// If this is a remote update, we set the scheduler option to lazily // If this is a remote update, we set the scheduler option to lazily
@@ -2101,26 +2115,26 @@ func (d *AuthenticatedGossiper) processNetworkAnnouncement(
// information about a node in one of the channels we know about, or a // information about a node in one of the channels we know about, or a
// updating previously advertised information. // updating previously advertised information.
case *lnwire.NodeAnnouncement: case *lnwire.NodeAnnouncement:
return d.handleNodeAnnouncement(nMsg, msg, schedulerOp) return d.handleNodeAnnouncement(ctx, nMsg, msg, schedulerOp)
// A new channel announcement has arrived, this indicates the // A new channel announcement has arrived, this indicates the
// *creation* of a new channel within the network. This only advertises // *creation* of a new channel within the network. This only advertises
// the existence of a channel and not yet the routing policies in // the existence of a channel and not yet the routing policies in
// either direction of the channel. // either direction of the channel.
case *lnwire.ChannelAnnouncement1: case *lnwire.ChannelAnnouncement1:
return d.handleChanAnnouncement(nMsg, msg, schedulerOp...) return d.handleChanAnnouncement(ctx, nMsg, msg, schedulerOp...)
// A new authenticated channel edge update has arrived. This indicates // A new authenticated channel edge update has arrived. This indicates
// that the directional information for an already known channel has // that the directional information for an already known channel has
// been updated. // been updated.
case *lnwire.ChannelUpdate1: case *lnwire.ChannelUpdate1:
return d.handleChanUpdate(nMsg, msg, schedulerOp) return d.handleChanUpdate(ctx, nMsg, msg, schedulerOp)
// A new signature announcement has been received. This indicates // A new signature announcement has been received. This indicates
// willingness of nodes involved in the funding of a channel to // willingness of nodes involved in the funding of a channel to
// announce this new channel to the rest of the world. // announce this new channel to the rest of the world.
case *lnwire.AnnounceSignatures1: case *lnwire.AnnounceSignatures1:
return d.handleAnnSig(nMsg, msg) return d.handleAnnSig(ctx, nMsg, msg)
default: default:
err := errors.New("wrong type of the announcement") err := errors.New("wrong type of the announcement")
@@ -2134,7 +2148,7 @@ func (d *AuthenticatedGossiper) processNetworkAnnouncement(
// //
// NOTE: only the NodeKey1Bytes and NodeKey2Bytes members of the ChannelEdgeInfo // NOTE: only the NodeKey1Bytes and NodeKey2Bytes members of the ChannelEdgeInfo
// should be inspected. // should be inspected.
func (d *AuthenticatedGossiper) processZombieUpdate( func (d *AuthenticatedGossiper) processZombieUpdate(_ context.Context,
chanInfo *models.ChannelEdgeInfo, scid lnwire.ShortChannelID, chanInfo *models.ChannelEdgeInfo, scid lnwire.ShortChannelID,
msg *lnwire.ChannelUpdate1) error { msg *lnwire.ChannelUpdate1) error {
@@ -2192,7 +2206,7 @@ func (d *AuthenticatedGossiper) processZombieUpdate(
// fetchNodeAnn fetches the latest signed node announcement from our point of // fetchNodeAnn fetches the latest signed node announcement from our point of
// view for the node with the given public key. // view for the node with the given public key.
func (d *AuthenticatedGossiper) fetchNodeAnn( func (d *AuthenticatedGossiper) fetchNodeAnn(_ context.Context,
pubKey [33]byte) (*lnwire.NodeAnnouncement, error) { pubKey [33]byte) (*lnwire.NodeAnnouncement, error) {
node, err := d.cfg.Graph.FetchLightningNode(pubKey) node, err := d.cfg.Graph.FetchLightningNode(pubKey)
@@ -2205,7 +2219,9 @@ func (d *AuthenticatedGossiper) fetchNodeAnn(
// isMsgStale determines whether a message retrieved from the backing // isMsgStale determines whether a message retrieved from the backing
// MessageStore is seen as stale by the current graph. // MessageStore is seen as stale by the current graph.
func (d *AuthenticatedGossiper) isMsgStale(msg lnwire.Message) bool { func (d *AuthenticatedGossiper) isMsgStale(_ context.Context,
msg lnwire.Message) bool {
switch msg := msg.(type) { switch msg := msg.(type) {
case *lnwire.AnnounceSignatures1: case *lnwire.AnnounceSignatures1:
chanInfo, _, _, err := d.cfg.Graph.GetChannelByID( chanInfo, _, _, err := d.cfg.Graph.GetChannelByID(
@@ -2272,7 +2288,8 @@ func (d *AuthenticatedGossiper) isMsgStale(msg lnwire.Message) bool {
// updateChannel creates a new fully signed update for the channel, and updates // updateChannel creates a new fully signed update for the channel, and updates
// the underlying graph with the new state. // the underlying graph with the new state.
func (d *AuthenticatedGossiper) updateChannel(info *models.ChannelEdgeInfo, func (d *AuthenticatedGossiper) updateChannel(_ context.Context,
info *models.ChannelEdgeInfo,
edge *models.ChannelEdgePolicy) (*lnwire.ChannelAnnouncement1, edge *models.ChannelEdgePolicy) (*lnwire.ChannelAnnouncement1,
*lnwire.ChannelUpdate1, error) { *lnwire.ChannelUpdate1, error) {
@@ -2414,8 +2431,8 @@ func (d *AuthenticatedGossiper) latestHeight() uint32 {
} }
// handleNodeAnnouncement processes a new node announcement. // handleNodeAnnouncement processes a new node announcement.
func (d *AuthenticatedGossiper) handleNodeAnnouncement(nMsg *networkMsg, func (d *AuthenticatedGossiper) handleNodeAnnouncement(ctx context.Context,
nodeAnn *lnwire.NodeAnnouncement, nMsg *networkMsg, nodeAnn *lnwire.NodeAnnouncement,
ops []batch.SchedulerOption) ([]networkMsg, bool) { ops []batch.SchedulerOption) ([]networkMsg, bool) {
timestamp := time.Unix(int64(nodeAnn.Timestamp), 0) timestamp := time.Unix(int64(nodeAnn.Timestamp), 0)
@@ -2432,7 +2449,7 @@ func (d *AuthenticatedGossiper) handleNodeAnnouncement(nMsg *networkMsg,
return nil, true return nil, true
} }
if err := d.addNode(nodeAnn, ops...); err != nil { if err := d.addNode(ctx, nodeAnn, ops...); err != nil {
log.Debugf("Adding node: %x got error: %v", nodeAnn.NodeID, log.Debugf("Adding node: %x got error: %v", nodeAnn.NodeID,
err) err)
@@ -2487,8 +2504,10 @@ func (d *AuthenticatedGossiper) handleNodeAnnouncement(nMsg *networkMsg,
} }
// handleChanAnnouncement processes a new channel announcement. // handleChanAnnouncement processes a new channel announcement.
func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg, //
ann *lnwire.ChannelAnnouncement1, //nolint:funlen
func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context,
nMsg *networkMsg, ann *lnwire.ChannelAnnouncement1,
ops ...batch.SchedulerOption) ([]networkMsg, bool) { ops ...batch.SchedulerOption) ([]networkMsg, bool) {
scid := ann.ShortChannelID scid := ann.ShortChannelID
@@ -2680,7 +2699,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
// add an alias ChannelAnnouncement from the gossiper. // add an alias ChannelAnnouncement from the gossiper.
if !(d.cfg.AssumeChannelValid || d.cfg.IsAlias(scid)) { //nolint:nestif if !(d.cfg.AssumeChannelValid || d.cfg.IsAlias(scid)) { //nolint:nestif
op, capacity, script, err := d.validateFundingTransaction( op, capacity, script, err := d.validateFundingTransaction(
ann, tapscriptRoot, ctx, ann, tapscriptRoot,
) )
if err != nil { if err != nil {
defer d.channelMtx.Unlock(scid.ToUint64()) defer d.channelMtx.Unlock(scid.ToUint64())
@@ -2802,7 +2821,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
if graph.IsError(err, graph.ErrIgnored) { if graph.IsError(err, graph.ErrIgnored) {
// Attempt to process the rejected message to see if we // Attempt to process the rejected message to see if we
// get any new announcements. // get any new announcements.
anns, rErr := d.processRejectedEdge(ann, proof) anns, rErr := d.processRejectedEdge(ctx, ann, proof)
if rErr != nil { if rErr != nil {
key := newRejectCacheKey( key := newRejectCacheKey(
scid.ToUint64(), scid.ToUint64(),
@@ -2945,8 +2964,10 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
} }
// handleChanUpdate processes a new channel update. // handleChanUpdate processes a new channel update.
func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg, //
upd *lnwire.ChannelUpdate1, //nolint:funlen
func (d *AuthenticatedGossiper) handleChanUpdate(ctx context.Context,
nMsg *networkMsg, upd *lnwire.ChannelUpdate1,
ops []batch.SchedulerOption) ([]networkMsg, bool) { ops []batch.SchedulerOption) ([]networkMsg, bool) {
log.Debugf("Processing ChannelUpdate: peer=%v, short_chan_id=%v, ", log.Debugf("Processing ChannelUpdate: peer=%v, short_chan_id=%v, ",
@@ -3052,7 +3073,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
break break
case errors.Is(err, graphdb.ErrZombieEdge): case errors.Is(err, graphdb.ErrZombieEdge):
err = d.processZombieUpdate(chanInfo, graphScid, upd) err = d.processZombieUpdate(ctx, chanInfo, graphScid, upd)
if err != nil { if err != nil {
log.Debug(err) log.Debug(err)
nMsg.err <- err nMsg.err <- err
@@ -3312,7 +3333,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
// Now we'll attempt to send the channel update message // Now we'll attempt to send the channel update message
// reliably to the remote peer in the background, so that we // reliably to the remote peer in the background, so that we
// don't block if the peer happens to be offline at the moment. // don't block if the peer happens to be offline at the moment.
err := d.reliableSender.sendMessage(upd, remotePubKey) err := d.reliableSender.sendMessage(ctx, upd, remotePubKey)
if err != nil { if err != nil {
err := fmt.Errorf("unable to reliably send %v for "+ err := fmt.Errorf("unable to reliably send %v for "+
"channel=%v to peer=%x: %v", upd.MsgType(), "channel=%v to peer=%x: %v", upd.MsgType(),
@@ -3346,8 +3367,11 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg,
} }
// handleAnnSig processes a new announcement signatures message. // handleAnnSig processes a new announcement signatures message.
func (d *AuthenticatedGossiper) handleAnnSig(nMsg *networkMsg, //
ann *lnwire.AnnounceSignatures1) ([]networkMsg, bool) { //nolint:funlen
func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context,
nMsg *networkMsg, ann *lnwire.AnnounceSignatures1) ([]networkMsg,
bool) {
needBlockHeight := ann.ShortChannelID.BlockHeight + needBlockHeight := ann.ShortChannelID.BlockHeight +
d.cfg.ProofMatureDelta d.cfg.ProofMatureDelta
@@ -3446,7 +3470,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(nMsg *networkMsg,
// Since the remote peer might not be online we'll call a // Since the remote peer might not be online we'll call a
// method that will attempt to deliver the proof when it comes // method that will attempt to deliver the proof when it comes
// online. // online.
err := d.reliableSender.sendMessage(ann, remotePubKey) err := d.reliableSender.sendMessage(ctx, ann, remotePubKey)
if err != nil { if err != nil {
err := fmt.Errorf("unable to reliably send %v for "+ err := fmt.Errorf("unable to reliably send %v for "+
"channel=%v to peer=%x: %v", ann.MsgType(), "channel=%v to peer=%x: %v", ann.MsgType(),
@@ -3631,7 +3655,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(nMsg *networkMsg,
// it since the source gets skipped. This isn't necessary for channel // it since the source gets skipped. This isn't necessary for channel
// updates and announcement signatures since we send those directly to // updates and announcement signatures since we send those directly to
// our channel counterparty through the gossiper's reliable sender. // our channel counterparty through the gossiper's reliable sender.
node1Ann, err := d.fetchNodeAnn(chanInfo.NodeKey1Bytes) node1Ann, err := d.fetchNodeAnn(ctx, chanInfo.NodeKey1Bytes)
if err != nil { if err != nil {
log.Debugf("Unable to fetch node announcement for %x: %v", log.Debugf("Unable to fetch node announcement for %x: %v",
chanInfo.NodeKey1Bytes, err) chanInfo.NodeKey1Bytes, err)
@@ -3645,7 +3669,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(nMsg *networkMsg,
} }
} }
node2Ann, err := d.fetchNodeAnn(chanInfo.NodeKey2Bytes) node2Ann, err := d.fetchNodeAnn(ctx, chanInfo.NodeKey2Bytes)
if err != nil { if err != nil {
log.Debugf("Unable to fetch node announcement for %x: %v", log.Debugf("Unable to fetch node announcement for %x: %v",
chanInfo.NodeKey2Bytes, err) chanInfo.NodeKey2Bytes, err)
@@ -3700,7 +3724,7 @@ func (d *AuthenticatedGossiper) ShouldDisconnect(pubkey *btcec.PublicKey) (
// transaction from chain to ensure that it exists, is not spent and matches // transaction from chain to ensure that it exists, is not spent and matches
// the channel announcement proof. The transaction's outpoint and value are // the channel announcement proof. The transaction's outpoint and value are
// returned if we can glean them from the work done in this method. // returned if we can glean them from the work done in this method.
func (d *AuthenticatedGossiper) validateFundingTransaction( func (d *AuthenticatedGossiper) validateFundingTransaction(_ context.Context,
ann *lnwire.ChannelAnnouncement1, ann *lnwire.ChannelAnnouncement1,
tapscriptRoot fn.Option[chainhash.Hash]) (wire.OutPoint, btcutil.Amount, tapscriptRoot fn.Option[chainhash.Hash]) (wire.OutPoint, btcutil.Amount,
[]byte, error) { []byte, error) {
+423 -348
View File
File diff suppressed because it is too large. Load diff
+23 -12
View File
@@ -1,8 +1,10 @@
package discovery package discovery
import ( import (
"context"
"sync" "sync"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
) )
@@ -28,7 +30,7 @@ type reliableSenderCfg struct {
// IsMsgStale determines whether a message retrieved from the backing // IsMsgStale determines whether a message retrieved from the backing
// MessageStore is seen as stale by the current graph. // MessageStore is seen as stale by the current graph.
IsMsgStale func(lnwire.Message) bool IsMsgStale func(context.Context, lnwire.Message) bool
} }
// peerManager contains the set of channels required for the peerHandler to // peerManager contains the set of channels required for the peerHandler to
@@ -59,8 +61,9 @@ type reliableSender struct {
activePeers map[[33]byte]peerManager activePeers map[[33]byte]peerManager
activePeersMtx sync.Mutex activePeersMtx sync.Mutex
wg sync.WaitGroup wg sync.WaitGroup
quit chan struct{} quit chan struct{}
cancel fn.Option[context.CancelFunc]
} }
// newReliableSender returns a new reliableSender backed by the given config. // newReliableSender returns a new reliableSender backed by the given config.
@@ -76,7 +79,10 @@ func newReliableSender(cfg *reliableSenderCfg) *reliableSender {
func (s *reliableSender) Start() error { func (s *reliableSender) Start() error {
var err error var err error
s.start.Do(func() { s.start.Do(func() {
err = s.resendPendingMsgs() ctx, cancel := context.WithCancel(context.Background())
s.cancel = fn.Some(cancel)
err = s.resendPendingMsgs(ctx)
}) })
return err return err
} }
@@ -87,6 +93,7 @@ func (s *reliableSender) Stop() {
log.Debugf("reliableSender is stopping") log.Debugf("reliableSender is stopping")
defer log.Debugf("reliableSender stopped") defer log.Debugf("reliableSender stopped")
s.cancel.WhenSome(func(fn context.CancelFunc) { fn() })
close(s.quit) close(s.quit)
s.wg.Wait() s.wg.Wait()
}) })
@@ -96,7 +103,9 @@ func (s *reliableSender) Stop() {
// event that the peer is currently offline, this will only write the message to // event that the peer is currently offline, this will only write the message to
// disk. Once the peer reconnects, this message, along with any others pending, // disk. Once the peer reconnects, this message, along with any others pending,
// will be sent to the peer. // will be sent to the peer.
func (s *reliableSender) sendMessage(msg lnwire.Message, peerPubKey [33]byte) error { func (s *reliableSender) sendMessage(ctx context.Context, msg lnwire.Message,
peerPubKey [33]byte) error {
// We'll start by persisting the message to disk. This allows us to // We'll start by persisting the message to disk. This allows us to
// resend the message upon restarts and peer reconnections. // resend the message upon restarts and peer reconnections.
if err := s.cfg.MessageStore.AddMessage(msg, peerPubKey); err != nil { if err := s.cfg.MessageStore.AddMessage(msg, peerPubKey); err != nil {
@@ -106,7 +115,7 @@ func (s *reliableSender) sendMessage(msg lnwire.Message, peerPubKey [33]byte) er
// Then, we'll spawn a peerHandler for this peer to handle resending its // Then, we'll spawn a peerHandler for this peer to handle resending its
// pending messages while taking into account its connection lifecycle. // pending messages while taking into account its connection lifecycle.
spawnHandler: spawnHandler:
msgHandler, ok := s.spawnPeerHandler(peerPubKey) msgHandler, ok := s.spawnPeerHandler(ctx, peerPubKey)
// If the handler wasn't previously active, we can exit now as we know // If the handler wasn't previously active, we can exit now as we know
// that the message will be sent once the peer online notification is // that the message will be sent once the peer online notification is
@@ -134,7 +143,7 @@ spawnHandler:
// spawnPeerMsgHandler spawns a peerHandler for the given peer if there isn't // spawnPeerMsgHandler spawns a peerHandler for the given peer if there isn't
// one already active. The boolean returned signals whether there was already // one already active. The boolean returned signals whether there was already
// one active or not. // one active or not.
func (s *reliableSender) spawnPeerHandler( func (s *reliableSender) spawnPeerHandler(ctx context.Context,
peerPubKey [33]byte) (peerManager, bool) { peerPubKey [33]byte) (peerManager, bool) {
s.activePeersMtx.Lock() s.activePeersMtx.Lock()
@@ -152,7 +161,7 @@ func (s *reliableSender) spawnPeerHandler(
// peerHandler. // peerHandler.
if !ok { if !ok {
s.wg.Add(1) s.wg.Add(1)
go s.peerHandler(msgHandler, peerPubKey) go s.peerHandler(ctx, msgHandler, peerPubKey)
} }
return msgHandler, ok return msgHandler, ok
@@ -164,7 +173,9 @@ func (s *reliableSender) spawnPeerHandler(
// offline will be queued and sent once the peer reconnects. // offline will be queued and sent once the peer reconnects.
// //
// NOTE: This must be run as a goroutine. // NOTE: This must be run as a goroutine.
func (s *reliableSender) peerHandler(peerMgr peerManager, peerPubKey [33]byte) { func (s *reliableSender) peerHandler(ctx context.Context, peerMgr peerManager,
peerPubKey [33]byte) {
defer s.wg.Done() defer s.wg.Done()
// We'll start by requesting a notification for when the peer // We'll start by requesting a notification for when the peer
@@ -252,7 +263,7 @@ out:
// check whether it's stale. This guarantees that // check whether it's stale. This guarantees that
// AnnounceSignatures are sent at least once if we happen to // AnnounceSignatures are sent at least once if we happen to
// already have signatures for both parties. // already have signatures for both parties.
if s.cfg.IsMsgStale(msg) { if s.cfg.IsMsgStale(ctx, msg) {
err := s.cfg.MessageStore.DeleteMessage(msg, peerPubKey) err := s.cfg.MessageStore.DeleteMessage(msg, peerPubKey)
if err != nil { if err != nil {
log.Errorf("Unable to remove stale %v message "+ log.Errorf("Unable to remove stale %v message "+
@@ -321,7 +332,7 @@ out:
// resendPendingMsgs retrieves and sends all of the messages within the message // resendPendingMsgs retrieves and sends all of the messages within the message
// store that should be reliably sent to their respective peers. // store that should be reliably sent to their respective peers.
func (s *reliableSender) resendPendingMsgs() error { func (s *reliableSender) resendPendingMsgs(ctx context.Context) error {
// Fetch all of the peers for which we have pending messages for and // Fetch all of the peers for which we have pending messages for and
// spawn a peerMsgHandler for each. Once the peer is seen as online, all // spawn a peerMsgHandler for each. Once the peer is seen as online, all
// of the pending messages will be sent. // of the pending messages will be sent.
@@ -331,7 +342,7 @@ func (s *reliableSender) resendPendingMsgs() error {
} }
for peer := range peers { for peer := range peers {
s.spawnPeerHandler(peer) s.spawnPeerHandler(ctx, peer)
} }
return nil return nil
+22 -19
View File
@@ -1,6 +1,7 @@
package discovery package discovery
import ( import (
"context"
"fmt" "fmt"
"sync/atomic" "sync/atomic"
"testing" "testing"
@@ -11,6 +12,7 @@ import (
"github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
) )
// newTestReliableSender creates a new reliable sender instance used for // newTestReliableSender creates a new reliable sender instance used for
@@ -32,7 +34,7 @@ func newTestReliableSender(t *testing.T) *reliableSender {
return c return c
}, },
MessageStore: newMockMessageStore(), MessageStore: newMockMessageStore(),
IsMsgStale: func(lnwire.Message) bool { IsMsgStale: func(context.Context, lnwire.Message) bool {
return false return false
}, },
} }
@@ -69,6 +71,7 @@ func assertMsgsSent(t *testing.T, msgChan chan lnwire.Message,
// a peer while taking into account its connection lifecycle works as expected. // a peer while taking into account its connection lifecycle works as expected.
func TestReliableSenderFlow(t *testing.T) { func TestReliableSenderFlow(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
reliableSender := newTestReliableSender(t) reliableSender := newTestReliableSender(t)
@@ -98,9 +101,8 @@ func TestReliableSenderFlow(t *testing.T) {
msg1 := randChannelUpdate() msg1 := randChannelUpdate()
var peerPubKey [33]byte var peerPubKey [33]byte
copy(peerPubKey[:], pubKey.SerializeCompressed()) copy(peerPubKey[:], pubKey.SerializeCompressed())
if err := reliableSender.sendMessage(msg1, peerPubKey); err != nil { err := reliableSender.sendMessage(ctx, msg1, peerPubKey)
t.Fatalf("unable to reliably send message: %v", err) require.NoError(t, err)
}
// Since there isn't a peerHandler for this peer currently active due to // Since there isn't a peerHandler for this peer currently active due to
// this being the first message being sent reliably, we should expect to // this being the first message being sent reliably, we should expect to
@@ -114,9 +116,8 @@ func TestReliableSenderFlow(t *testing.T) {
// We'll then attempt to send another additional message reliably. // We'll then attempt to send another additional message reliably.
msg2 := randAnnounceSignatures() msg2 := randAnnounceSignatures()
if err := reliableSender.sendMessage(msg2, peerPubKey); err != nil { err = reliableSender.sendMessage(ctx, msg2, peerPubKey)
t.Fatalf("unable to reliably send message: %v", err) require.NoError(t, err)
}
// This should not however request another peer online notification as // This should not however request another peer online notification as
// the peerHandler has already been started and is waiting for the // the peerHandler has already been started and is waiting for the
@@ -145,9 +146,8 @@ func TestReliableSenderFlow(t *testing.T) {
// Then, we'll send one more message reliably. // Then, we'll send one more message reliably.
msg3 := randChannelUpdate() msg3 := randChannelUpdate()
if err := reliableSender.sendMessage(msg3, peerPubKey); err != nil { err = reliableSender.sendMessage(ctx, msg3, peerPubKey)
t.Fatalf("unable to reliably send message: %v", err) require.NoError(t, err)
}
// Again, this should not request another peer online notification // Again, this should not request another peer online notification
// request since we are currently waiting for the peer to be offline. // request since we are currently waiting for the peer to be offline.
@@ -188,6 +188,7 @@ func TestReliableSenderFlow(t *testing.T) {
// them as stale. // them as stale.
func TestReliableSenderStaleMessages(t *testing.T) { func TestReliableSenderStaleMessages(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
reliableSender := newTestReliableSender(t) reliableSender := newTestReliableSender(t)
@@ -206,7 +207,9 @@ func TestReliableSenderStaleMessages(t *testing.T) {
// We'll also override IsMsgStale to mark all messages as stale as we're // We'll also override IsMsgStale to mark all messages as stale as we're
// interested in testing the stale message behavior. // interested in testing the stale message behavior.
reliableSender.cfg.IsMsgStale = func(_ lnwire.Message) bool { reliableSender.cfg.IsMsgStale = func(_ context.Context,
_ lnwire.Message) bool {
return true return true
} }
@@ -215,9 +218,8 @@ func TestReliableSenderStaleMessages(t *testing.T) {
msg1 := randAnnounceSignatures() msg1 := randAnnounceSignatures()
var peerPubKey [33]byte var peerPubKey [33]byte
copy(peerPubKey[:], pubKey.SerializeCompressed()) copy(peerPubKey[:], pubKey.SerializeCompressed())
if err := reliableSender.sendMessage(msg1, peerPubKey); err != nil { err := reliableSender.sendMessage(ctx, msg1, peerPubKey)
t.Fatalf("unable to reliably send message: %v", err) require.NoError(t, err)
}
// Since there isn't a peerHandler for this peer currently active due to // Since there isn't a peerHandler for this peer currently active due to
// this being the first message being sent reliably, we should expect to // this being the first message being sent reliably, we should expect to
@@ -245,7 +247,7 @@ func TestReliableSenderStaleMessages(t *testing.T) {
// message store since it is seen as stale and has been sent at least // message store since it is seen as stale and has been sent at least
// once. Once the message is removed, the peerHandler should be torn // once. Once the message is removed, the peerHandler should be torn
// down as there are no longer any pending messages within the store. // down as there are no longer any pending messages within the store.
err := wait.NoError(func() error { err = wait.NoError(func() error {
msgs, err := reliableSender.cfg.MessageStore.MessagesForPeer( msgs, err := reliableSender.cfg.MessageStore.MessagesForPeer(
peerPubKey, peerPubKey,
) )
@@ -265,14 +267,15 @@ func TestReliableSenderStaleMessages(t *testing.T) {
} }
// Override IsMsgStale to no longer mark messages as stale. // Override IsMsgStale to no longer mark messages as stale.
reliableSender.cfg.IsMsgStale = func(_ lnwire.Message) bool { reliableSender.cfg.IsMsgStale = func(_ context.Context,
_ lnwire.Message) bool {
return false return false
} }
// We'll request the message to be sent reliably. // We'll request the message to be sent reliably.
if err := reliableSender.sendMessage(msg2, peerPubKey); err != nil { err = reliableSender.sendMessage(ctx, msg2, peerPubKey)
t.Fatalf("unable to reliably send message: %v", err) require.NoError(t, err)
}
// We should see an online notification request indicating that a new // We should see an online notification request indicating that a new
// peerHandler has been spawned since it was previously torn down. // peerHandler has been spawned since it was previously torn down.
+62 -40
View File
@@ -409,16 +409,18 @@ func (g *GossipSyncer) Start() {
g.started.Do(func() { g.started.Do(func() {
log.Debugf("Starting GossipSyncer(%x)", g.cfg.peerPub[:]) log.Debugf("Starting GossipSyncer(%x)", g.cfg.peerPub[:])
ctx, _ := g.cg.Create(context.Background())
// TODO(conner): only spawn channelGraphSyncer if remote // TODO(conner): only spawn channelGraphSyncer if remote
// supports gossip queries, and only spawn replyHandler if we // supports gossip queries, and only spawn replyHandler if we
// advertise support // advertise support
if !g.cfg.noSyncChannels { if !g.cfg.noSyncChannels {
g.cg.WgAdd(1) g.cg.WgAdd(1)
go g.channelGraphSyncer() go g.channelGraphSyncer(ctx)
} }
if !g.cfg.noReplyQueries { if !g.cfg.noReplyQueries {
g.cg.WgAdd(1) g.cg.WgAdd(1)
go g.replyHandler() go g.replyHandler(ctx)
} }
}) })
} }
@@ -437,9 +439,11 @@ func (g *GossipSyncer) Stop() {
// handleSyncingChans handles the state syncingChans for the GossipSyncer. When // handleSyncingChans handles the state syncingChans for the GossipSyncer. When
// in this state, we will send a QueryChannelRange msg to our peer and advance // in this state, we will send a QueryChannelRange msg to our peer and advance
// the syncer's state to waitingQueryRangeReply. // the syncer's state to waitingQueryRangeReply.
func (g *GossipSyncer) handleSyncingChans() { func (g *GossipSyncer) handleSyncingChans(ctx context.Context) {
// Prepare the query msg. // Prepare the query msg.
queryRangeMsg, err := g.genChanRangeQuery(g.genHistoricalChanRangeQuery) queryRangeMsg, err := g.genChanRangeQuery(
ctx, g.genHistoricalChanRangeQuery,
)
if err != nil { if err != nil {
log.Errorf("Unable to gen chan range query: %v", err) log.Errorf("Unable to gen chan range query: %v", err)
return return
@@ -456,7 +460,6 @@ func (g *GossipSyncer) handleSyncingChans() {
// Send the msg to the remote peer, which is non-blocking as // Send the msg to the remote peer, which is non-blocking as
// `sendToPeer` only queues the msg in Brontide. // `sendToPeer` only queues the msg in Brontide.
ctx, _ := g.cg.Create(context.Background())
err = g.cfg.sendToPeer(ctx, queryRangeMsg) err = g.cfg.sendToPeer(ctx, queryRangeMsg)
if err != nil { if err != nil {
log.Errorf("Unable to send chan range query: %v", err) log.Errorf("Unable to send chan range query: %v", err)
@@ -471,7 +474,7 @@ func (g *GossipSyncer) handleSyncingChans() {
// channelGraphSyncer is the main goroutine responsible for ensuring that we // channelGraphSyncer is the main goroutine responsible for ensuring that we
// properly channel graph state with the remote peer, and also that we only // properly channel graph state with the remote peer, and also that we only
// send them messages which actually pass their defined update horizon. // send them messages which actually pass their defined update horizon.
func (g *GossipSyncer) channelGraphSyncer() { func (g *GossipSyncer) channelGraphSyncer(ctx context.Context) {
defer g.cg.WgDone() defer g.cg.WgDone()
for { for {
@@ -488,7 +491,7 @@ func (g *GossipSyncer) channelGraphSyncer() {
// understand, as we'll as responding to any other queries by // understand, as we'll as responding to any other queries by
// them. // them.
case syncingChans: case syncingChans:
g.handleSyncingChans() g.handleSyncingChans(ctx)
// In this state, we've sent out our initial channel range // In this state, we've sent out our initial channel range
// query and are waiting for the final response from the remote // query and are waiting for the final response from the remote
@@ -507,7 +510,9 @@ func (g *GossipSyncer) channelGraphSyncer() {
// for the new channels. // for the new channels.
queryReply, ok := msg.(*lnwire.ReplyChannelRange) queryReply, ok := msg.(*lnwire.ReplyChannelRange)
if ok { if ok {
err := g.processChanRangeReply(queryReply) err := g.processChanRangeReply(
ctx, queryReply,
)
if err != nil { if err != nil {
log.Errorf("Unable to "+ log.Errorf("Unable to "+
"process chan range "+ "process chan range "+
@@ -522,6 +527,9 @@ func (g *GossipSyncer) channelGraphSyncer() {
case <-g.cg.Done(): case <-g.cg.Done():
return return
case <-ctx.Done():
return
} }
// We'll enter this state once we've discovered which channels // We'll enter this state once we've discovered which channels
@@ -531,7 +539,7 @@ func (g *GossipSyncer) channelGraphSyncer() {
// First, we'll attempt to continue our channel // First, we'll attempt to continue our channel
// synchronization by continuing to send off another // synchronization by continuing to send off another
// query chunk. // query chunk.
done := g.synchronizeChanIDs() done := g.synchronizeChanIDs(ctx)
// If this wasn't our last query, then we'll need to // If this wasn't our last query, then we'll need to
// transition to our waiting state. // transition to our waiting state.
@@ -572,6 +580,9 @@ func (g *GossipSyncer) channelGraphSyncer() {
case <-g.cg.Done(): case <-g.cg.Done():
return return
case <-ctx.Done():
return
} }
// This is our final terminal state where we'll only reply to // This is our final terminal state where we'll only reply to
@@ -591,7 +602,7 @@ func (g *GossipSyncer) channelGraphSyncer() {
syncType.IsActiveSync() { syncType.IsActiveSync() {
err := g.sendGossipTimestampRange( err := g.sendGossipTimestampRange(
time.Now(), math.MaxUint32, ctx, time.Now(), math.MaxUint32,
) )
if err != nil { if err != nil {
log.Errorf("Unable to send update "+ log.Errorf("Unable to send update "+
@@ -611,13 +622,16 @@ func (g *GossipSyncer) channelGraphSyncer() {
case syncerIdle: case syncerIdle:
select { select {
case req := <-g.syncTransitionReqs: case req := <-g.syncTransitionReqs:
req.errChan <- g.handleSyncTransition(req) req.errChan <- g.handleSyncTransition(ctx, req)
case req := <-g.historicalSyncReqs: case req := <-g.historicalSyncReqs:
g.handleHistoricalSync(req) g.handleHistoricalSync(req)
case <-g.cg.Done(): case <-g.cg.Done():
return return
case <-ctx.Done():
return
} }
} }
} }
@@ -630,13 +644,13 @@ func (g *GossipSyncer) channelGraphSyncer() {
// from the state machine maintained on the same node. // from the state machine maintained on the same node.
// //
// NOTE: This method MUST be run as a goroutine. // NOTE: This method MUST be run as a goroutine.
func (g *GossipSyncer) replyHandler() { func (g *GossipSyncer) replyHandler(ctx context.Context) {
defer g.cg.WgDone() defer g.cg.WgDone()
for { for {
select { select {
case msg := <-g.queryMsgs: case msg := <-g.queryMsgs:
err := g.replyPeerQueries(msg) err := g.replyPeerQueries(ctx, msg)
switch { switch {
case err == ErrGossipSyncerExiting: case err == ErrGossipSyncerExiting:
return return
@@ -651,14 +665,17 @@ func (g *GossipSyncer) replyHandler() {
case <-g.cg.Done(): case <-g.cg.Done():
return return
case <-ctx.Done():
return
} }
} }
} }
// sendGossipTimestampRange constructs and sets a GossipTimestampRange for the // sendGossipTimestampRange constructs and sets a GossipTimestampRange for the
// syncer and sends it to the remote peer. // syncer and sends it to the remote peer.
func (g *GossipSyncer) sendGossipTimestampRange(firstTimestamp time.Time, func (g *GossipSyncer) sendGossipTimestampRange(ctx context.Context,
timestampRange uint32) error { firstTimestamp time.Time, timestampRange uint32) error {
endTimestamp := firstTimestamp.Add( endTimestamp := firstTimestamp.Add(
time.Duration(timestampRange) * time.Second, time.Duration(timestampRange) * time.Second,
@@ -673,7 +690,6 @@ func (g *GossipSyncer) sendGossipTimestampRange(firstTimestamp time.Time,
TimestampRange: timestampRange, TimestampRange: timestampRange,
} }
ctx, _ := g.cg.Create(context.Background())
if err := g.cfg.sendToPeer(ctx, localUpdateHorizon); err != nil { if err := g.cfg.sendToPeer(ctx, localUpdateHorizon); err != nil {
return err return err
} }
@@ -693,7 +709,7 @@ func (g *GossipSyncer) sendGossipTimestampRange(firstTimestamp time.Time,
// been queried for with a response received. We'll chunk our requests as // been queried for with a response received. We'll chunk our requests as
// required to ensure they fit into a single message. We may re-renter this // required to ensure they fit into a single message. We may re-renter this
// state in the case that chunking is required. // state in the case that chunking is required.
func (g *GossipSyncer) synchronizeChanIDs() bool { func (g *GossipSyncer) synchronizeChanIDs(ctx context.Context) bool {
// If we're in this state yet there are no more new channels to query // If we're in this state yet there are no more new channels to query
// for, then we'll transition to our final synced state and return true // for, then we'll transition to our final synced state and return true
// to signal that we're fully synchronized. // to signal that we're fully synchronized.
@@ -730,7 +746,6 @@ func (g *GossipSyncer) synchronizeChanIDs() bool {
// With our chunk obtained, we'll send over our next query, then return // With our chunk obtained, we'll send over our next query, then return
// false indicating that we're net yet fully synced. // false indicating that we're net yet fully synced.
ctx, _ := g.cg.Create(context.Background())
err := g.cfg.sendToPeer(ctx, &lnwire.QueryShortChanIDs{ err := g.cfg.sendToPeer(ctx, &lnwire.QueryShortChanIDs{
ChainHash: g.cfg.chainHash, ChainHash: g.cfg.chainHash,
EncodingType: lnwire.EncodingSortedPlain, EncodingType: lnwire.EncodingSortedPlain,
@@ -759,7 +774,9 @@ func isLegacyReplyChannelRange(query *lnwire.QueryChannelRange,
// processChanRangeReply is called each time the GossipSyncer receives a new // processChanRangeReply is called each time the GossipSyncer receives a new
// reply to the initial range query to discover new channels that it didn't // reply to the initial range query to discover new channels that it didn't
// previously know of. // previously know of.
func (g *GossipSyncer) processChanRangeReply(msg *lnwire.ReplyChannelRange) error { func (g *GossipSyncer) processChanRangeReply(_ context.Context,
msg *lnwire.ReplyChannelRange) error {
// isStale returns whether the timestamp is too far into the past. // isStale returns whether the timestamp is too far into the past.
isStale := func(timestamp time.Time) bool { isStale := func(timestamp time.Time) bool {
return time.Since(timestamp) > graph.DefaultChannelPruneExpiry return time.Since(timestamp) > graph.DefaultChannelPruneExpiry
@@ -948,7 +965,7 @@ func (g *GossipSyncer) processChanRangeReply(msg *lnwire.ReplyChannelRange) erro
// party when we're kicking off the channel graph synchronization upon // party when we're kicking off the channel graph synchronization upon
// connection. The historicalQuery boolean can be used to generate a query from // connection. The historicalQuery boolean can be used to generate a query from
// the genesis block of the chain. // the genesis block of the chain.
func (g *GossipSyncer) genChanRangeQuery( func (g *GossipSyncer) genChanRangeQuery(_ context.Context,
historicalQuery bool) (*lnwire.QueryChannelRange, error) { historicalQuery bool) (*lnwire.QueryChannelRange, error) {
// First, we'll query our channel graph time series for its highest // First, we'll query our channel graph time series for its highest
@@ -1005,18 +1022,20 @@ func (g *GossipSyncer) genChanRangeQuery(
// replyPeerQueries is called in response to any query by the remote peer. // replyPeerQueries is called in response to any query by the remote peer.
// We'll examine our state and send back our best response. // We'll examine our state and send back our best response.
func (g *GossipSyncer) replyPeerQueries(msg lnwire.Message) error { func (g *GossipSyncer) replyPeerQueries(ctx context.Context,
msg lnwire.Message) error {
switch msg := msg.(type) { switch msg := msg.(type) {
// In this state, we'll also handle any incoming channel range queries // In this state, we'll also handle any incoming channel range queries
// from the remote peer as they're trying to sync their state as well. // from the remote peer as they're trying to sync their state as well.
case *lnwire.QueryChannelRange: case *lnwire.QueryChannelRange:
return g.replyChanRangeQuery(msg) return g.replyChanRangeQuery(ctx, msg)
// If the remote peer skips straight to requesting new channels that // If the remote peer skips straight to requesting new channels that
// they don't know of, then we'll ensure that we also handle this case. // they don't know of, then we'll ensure that we also handle this case.
case *lnwire.QueryShortChanIDs: case *lnwire.QueryShortChanIDs:
return g.replyShortChanIDs(msg) return g.replyShortChanIDs(ctx, msg)
default: default:
return fmt.Errorf("unknown message: %T", msg) return fmt.Errorf("unknown message: %T", msg)
@@ -1028,7 +1047,9 @@ func (g *GossipSyncer) replyPeerQueries(msg lnwire.Message) error {
// meet the channel range, then chunk our responses to the remote node. We also // meet the channel range, then chunk our responses to the remote node. We also
// ensure that our final fragment carries the "complete" bit to indicate the // ensure that our final fragment carries the "complete" bit to indicate the
// end of our streaming response. // end of our streaming response.
func (g *GossipSyncer) replyChanRangeQuery(query *lnwire.QueryChannelRange) error { func (g *GossipSyncer) replyChanRangeQuery(ctx context.Context,
query *lnwire.QueryChannelRange) error {
// Before responding, we'll check to ensure that the remote peer is // Before responding, we'll check to ensure that the remote peer is
// querying for the same chain that we're on. If not, we'll send back a // querying for the same chain that we're on. If not, we'll send back a
// response with a complete value of zero to indicate we're on a // response with a complete value of zero to indicate we're on a
@@ -1038,8 +1059,6 @@ func (g *GossipSyncer) replyChanRangeQuery(query *lnwire.QueryChannelRange) erro
"chain=%v, we're on chain=%v", query.ChainHash, "chain=%v, we're on chain=%v", query.ChainHash,
g.cfg.chainHash) g.cfg.chainHash)
ctx, _ := g.cg.Create(context.Background())
return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyChannelRange{ return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyChannelRange{
ChainHash: query.ChainHash, ChainHash: query.ChainHash,
FirstBlockHeight: query.FirstBlockHeight, FirstBlockHeight: query.FirstBlockHeight,
@@ -1113,8 +1132,6 @@ func (g *GossipSyncer) replyChanRangeQuery(query *lnwire.QueryChannelRange) erro
) )
} }
ctx, _ := g.cg.Create(context.Background())
return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyChannelRange{ return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyChannelRange{
ChainHash: query.ChainHash, ChainHash: query.ChainHash,
NumBlocks: numBlocks, NumBlocks: numBlocks,
@@ -1209,7 +1226,9 @@ func (g *GossipSyncer) replyChanRangeQuery(query *lnwire.QueryChannelRange) erro
// node for information concerning a set of short channel ID's. Our response // node for information concerning a set of short channel ID's. Our response
// will be sent in a streaming chunked manner to ensure that we remain below // will be sent in a streaming chunked manner to ensure that we remain below
// the current transport level message size. // the current transport level message size.
func (g *GossipSyncer) replyShortChanIDs(query *lnwire.QueryShortChanIDs) error { func (g *GossipSyncer) replyShortChanIDs(ctx context.Context,
query *lnwire.QueryShortChanIDs) error {
// Before responding, we'll check to ensure that the remote peer is // Before responding, we'll check to ensure that the remote peer is
// querying for the same chain that we're on. If not, we'll send back a // querying for the same chain that we're on. If not, we'll send back a
// response with a complete value of zero to indicate we're on a // response with a complete value of zero to indicate we're on a
@@ -1219,8 +1238,6 @@ func (g *GossipSyncer) replyShortChanIDs(query *lnwire.QueryShortChanIDs) error
"chain=%v, we're on chain=%v", query.ChainHash, "chain=%v, we're on chain=%v", query.ChainHash,
g.cfg.chainHash) g.cfg.chainHash)
ctx, _ := g.cg.Create(context.Background())
return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyShortChanIDsEnd{ return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyShortChanIDsEnd{
ChainHash: query.ChainHash, ChainHash: query.ChainHash,
Complete: 0, Complete: 0,
@@ -1252,7 +1269,6 @@ func (g *GossipSyncer) replyShortChanIDs(query *lnwire.QueryShortChanIDs) error
// each one individually and synchronously to throttle the sends and // each one individually and synchronously to throttle the sends and
// perform buffering of responses in the syncer as opposed to the peer. // perform buffering of responses in the syncer as opposed to the peer.
for _, msg := range replyMsgs { for _, msg := range replyMsgs {
ctx, _ := g.cg.Create(context.Background())
err := g.cfg.sendToPeerSync(ctx, msg) err := g.cfg.sendToPeerSync(ctx, msg)
if err != nil { if err != nil {
return err return err
@@ -1261,8 +1277,6 @@ func (g *GossipSyncer) replyShortChanIDs(query *lnwire.QueryShortChanIDs) error
// Regardless of whether we had any messages to reply with, send over // Regardless of whether we had any messages to reply with, send over
// the sentinel message to signal that the stream has terminated. // the sentinel message to signal that the stream has terminated.
ctx, _ := g.cg.Create(context.Background())
return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyShortChanIDsEnd{ return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyShortChanIDsEnd{
ChainHash: query.ChainHash, ChainHash: query.ChainHash,
Complete: 1, Complete: 1,
@@ -1272,7 +1286,9 @@ func (g *GossipSyncer) replyShortChanIDs(query *lnwire.QueryShortChanIDs) error
// ApplyGossipFilter applies a gossiper filter sent by the remote node to the // ApplyGossipFilter applies a gossiper filter sent by the remote node to the
// state machine. Once applied, we'll ensure that we don't forward any messages // state machine. Once applied, we'll ensure that we don't forward any messages
// to the peer that aren't within the time range of the filter. // to the peer that aren't within the time range of the filter.
func (g *GossipSyncer) ApplyGossipFilter(filter *lnwire.GossipTimestampRange) error { func (g *GossipSyncer) ApplyGossipFilter(ctx context.Context,
filter *lnwire.GossipTimestampRange) error {
g.Lock() g.Lock()
g.remoteUpdateHorizon = filter g.remoteUpdateHorizon = filter
@@ -1294,6 +1310,8 @@ func (g *GossipSyncer) ApplyGossipFilter(filter *lnwire.GossipTimestampRange) er
case <-g.syncerSema: case <-g.syncerSema:
case <-g.cg.Done(): case <-g.cg.Done():
return ErrGossipSyncerExiting return ErrGossipSyncerExiting
case <-ctx.Done():
return ctx.Err()
} }
// We don't put this in a defer because if the goroutine is launched, // We don't put this in a defer because if the goroutine is launched,
@@ -1329,7 +1347,6 @@ func (g *GossipSyncer) ApplyGossipFilter(filter *lnwire.GossipTimestampRange) er
defer returnSema() defer returnSema()
for _, msg := range newUpdatestoSend { for _, msg := range newUpdatestoSend {
ctx, _ := g.cg.Create(context.Background())
err := g.cfg.sendToPeerSync(ctx, msg) err := g.cfg.sendToPeerSync(ctx, msg)
switch { switch {
case err == ErrGossipSyncerExiting: case err == ErrGossipSyncerExiting:
@@ -1351,7 +1368,9 @@ func (g *GossipSyncer) ApplyGossipFilter(filter *lnwire.GossipTimestampRange) er
// FilterGossipMsgs takes a set of gossip messages, and only send it to a peer // FilterGossipMsgs takes a set of gossip messages, and only send it to a peer
// iff the message is within the bounds of their set gossip filter. If the peer // iff the message is within the bounds of their set gossip filter. If the peer
// doesn't have a gossip filter set, then no messages will be forwarded. // doesn't have a gossip filter set, then no messages will be forwarded.
func (g *GossipSyncer) FilterGossipMsgs(msgs ...msgWithSenders) { func (g *GossipSyncer) FilterGossipMsgs(ctx context.Context,
msgs ...msgWithSenders) {
// If the peer doesn't have an update horizon set, then we won't send // If the peer doesn't have an update horizon set, then we won't send
// it any new update messages. // it any new update messages.
if g.remoteUpdateHorizon == nil { if g.remoteUpdateHorizon == nil {
@@ -1365,6 +1384,8 @@ func (g *GossipSyncer) FilterGossipMsgs(msgs ...msgWithSenders) {
select { select {
case <-g.cg.Done(): case <-g.cg.Done():
return return
case <-ctx.Done():
return
default: default:
} }
@@ -1472,7 +1493,6 @@ func (g *GossipSyncer) FilterGossipMsgs(msgs ...msgWithSenders) {
return return
} }
ctx, _ := g.cg.Create(context.Background())
if err = g.cfg.sendToPeer(ctx, msgsToSend...); err != nil { if err = g.cfg.sendToPeer(ctx, msgsToSend...); err != nil {
log.Errorf("unable to send gossip msgs: %v", err) log.Errorf("unable to send gossip msgs: %v", err)
} }
@@ -1573,7 +1593,9 @@ func (g *GossipSyncer) ProcessSyncTransition(newSyncType SyncerType) error {
// //
// NOTE: The gossip syncer might have another sync state as a result of this // NOTE: The gossip syncer might have another sync state as a result of this
// transition. // transition.
func (g *GossipSyncer) handleSyncTransition(req *syncTransitionReq) error { func (g *GossipSyncer) handleSyncTransition(ctx context.Context,
req *syncTransitionReq) error {
// Return early from any NOP sync transitions. // Return early from any NOP sync transitions.
syncType := g.SyncType() syncType := g.SyncType()
if syncType == req.newSyncType { if syncType == req.newSyncType {
@@ -1608,7 +1630,7 @@ func (g *GossipSyncer) handleSyncTransition(req *syncTransitionReq) error {
req.newSyncType) req.newSyncType)
} }
err := g.sendGossipTimestampRange(firstTimestamp, timestampRange) err := g.sendGossipTimestampRange(ctx, firstTimestamp, timestampRange)
if err != nil { if err != nil {
return fmt.Errorf("unable to send local update horizon: %w", return fmt.Errorf("unable to send local update horizon: %w",
err) err)
+33 -20
View File
@@ -228,6 +228,7 @@ func newTestSyncer(hID lnwire.ShortChannelID,
// doesn't have a horizon set, then we won't send any incoming messages to it. // doesn't have a horizon set, then we won't send any incoming messages to it.
func TestGossipSyncerFilterGossipMsgsNoHorizon(t *testing.T) { func TestGossipSyncerFilterGossipMsgsNoHorizon(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
// First, we'll create a GossipSyncer instance with a canned sendToPeer // First, we'll create a GossipSyncer instance with a canned sendToPeer
// message to allow us to intercept their potential sends. // message to allow us to intercept their potential sends.
@@ -249,7 +250,7 @@ func TestGossipSyncerFilterGossipMsgsNoHorizon(t *testing.T) {
// We'll then attempt to filter the set of messages through the target // We'll then attempt to filter the set of messages through the target
// peer. // peer.
syncer.FilterGossipMsgs(msgs...) syncer.FilterGossipMsgs(ctx, msgs...)
// As the remote peer doesn't yet have a gossip timestamp set, we // As the remote peer doesn't yet have a gossip timestamp set, we
// shouldn't receive any outbound messages. // shouldn't receive any outbound messages.
@@ -273,6 +274,7 @@ func unixStamp(a int64) uint32 {
// channel ann that already has a channel update on disk. // channel ann that already has a channel update on disk.
func TestGossipSyncerFilterGossipMsgsAllInMemory(t *testing.T) { func TestGossipSyncerFilterGossipMsgsAllInMemory(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
// First, we'll create a GossipSyncer instance with a canned sendToPeer // First, we'll create a GossipSyncer instance with a canned sendToPeer
// message to allow us to intercept their potential sends. // message to allow us to intercept their potential sends.
@@ -384,7 +386,7 @@ func TestGossipSyncerFilterGossipMsgsAllInMemory(t *testing.T) {
}() }()
// We'll then instruct the gossiper to filter this set of messages. // We'll then instruct the gossiper to filter this set of messages.
syncer.FilterGossipMsgs(msgs...) syncer.FilterGossipMsgs(ctx, msgs...)
// Out of all the messages we sent in, we should only get 2 of them // Out of all the messages we sent in, we should only get 2 of them
// back. // back.
@@ -415,6 +417,7 @@ func TestGossipSyncerFilterGossipMsgsAllInMemory(t *testing.T) {
// messages which are within their desired time horizon. // messages which are within their desired time horizon.
func TestGossipSyncerApplyNoHistoricalGossipFilter(t *testing.T) { func TestGossipSyncerApplyNoHistoricalGossipFilter(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
// First, we'll create a GossipSyncer instance with a canned sendToPeer // First, we'll create a GossipSyncer instance with a canned sendToPeer
// message to allow us to intercept their potential sends. // message to allow us to intercept their potential sends.
@@ -451,7 +454,7 @@ func TestGossipSyncerApplyNoHistoricalGossipFilter(t *testing.T) {
}() }()
// We'll now attempt to apply the gossip filter for the remote peer. // We'll now attempt to apply the gossip filter for the remote peer.
syncer.ApplyGossipFilter(remoteHorizon) require.NoError(t, syncer.ApplyGossipFilter(ctx, remoteHorizon))
// Ensure that the syncer's remote horizon was properly updated. // Ensure that the syncer's remote horizon was properly updated.
if !reflect.DeepEqual(syncer.remoteUpdateHorizon, remoteHorizon) { if !reflect.DeepEqual(syncer.remoteUpdateHorizon, remoteHorizon) {
@@ -475,6 +478,7 @@ func TestGossipSyncerApplyNoHistoricalGossipFilter(t *testing.T) {
// within their desired time horizon. // within their desired time horizon.
func TestGossipSyncerApplyGossipFilter(t *testing.T) { func TestGossipSyncerApplyGossipFilter(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
// First, we'll create a GossipSyncer instance with a canned sendToPeer // First, we'll create a GossipSyncer instance with a canned sendToPeer
// message to allow us to intercept their potential sends. // message to allow us to intercept their potential sends.
@@ -515,7 +519,7 @@ func TestGossipSyncerApplyGossipFilter(t *testing.T) {
}() }()
// We'll now attempt to apply the gossip filter for the remote peer. // We'll now attempt to apply the gossip filter for the remote peer.
err := syncer.ApplyGossipFilter(remoteHorizon) err := syncer.ApplyGossipFilter(ctx, remoteHorizon)
require.NoError(t, err, "unable to apply filter") require.NoError(t, err, "unable to apply filter")
// There should be no messages in the message queue as we didn't send // There should be no messages in the message queue as we didn't send
@@ -563,7 +567,7 @@ func TestGossipSyncerApplyGossipFilter(t *testing.T) {
errCh <- nil errCh <- nil
} }
}() }()
err = syncer.ApplyGossipFilter(remoteHorizon) err = syncer.ApplyGossipFilter(ctx, remoteHorizon)
require.NoError(t, err, "unable to apply filter") require.NoError(t, err, "unable to apply filter")
// We should get back the exact same message. // We should get back the exact same message.
@@ -594,6 +598,7 @@ func TestGossipSyncerApplyGossipFilter(t *testing.T) {
// channels and complete=0. // channels and complete=0.
func TestGossipSyncerQueryChannelRangeWrongChainHash(t *testing.T) { func TestGossipSyncerQueryChannelRangeWrongChainHash(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
// First, we'll create a GossipSyncer instance with a canned sendToPeer // First, we'll create a GossipSyncer instance with a canned sendToPeer
// message to allow us to intercept their potential sends. // message to allow us to intercept their potential sends.
@@ -609,7 +614,7 @@ func TestGossipSyncerQueryChannelRangeWrongChainHash(t *testing.T) {
FirstBlockHeight: 0, FirstBlockHeight: 0,
NumBlocks: math.MaxUint32, NumBlocks: math.MaxUint32,
} }
err := syncer.replyChanRangeQuery(query) err := syncer.replyChanRangeQuery(ctx, query)
require.NoError(t, err, "unable to process short chan ID's") require.NoError(t, err, "unable to process short chan ID's")
select { select {
@@ -646,6 +651,7 @@ func TestGossipSyncerQueryChannelRangeWrongChainHash(t *testing.T) {
// complete=0. // complete=0.
func TestGossipSyncerReplyShortChanIDsWrongChainHash(t *testing.T) { func TestGossipSyncerReplyShortChanIDsWrongChainHash(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
// First, we'll create a GossipSyncer instance with a canned sendToPeer // First, we'll create a GossipSyncer instance with a canned sendToPeer
// message to allow us to intercept their potential sends. // message to allow us to intercept their potential sends.
@@ -656,7 +662,7 @@ func TestGossipSyncerReplyShortChanIDsWrongChainHash(t *testing.T) {
// We'll now ask the syncer to reply to a chan ID query, but for a // We'll now ask the syncer to reply to a chan ID query, but for a
// chain that it isn't aware of. // chain that it isn't aware of.
err := syncer.replyShortChanIDs(&lnwire.QueryShortChanIDs{ err := syncer.replyShortChanIDs(ctx, &lnwire.QueryShortChanIDs{
ChainHash: *chaincfg.SimNetParams.GenesisHash, ChainHash: *chaincfg.SimNetParams.GenesisHash,
}) })
require.NoError(t, err, "unable to process short chan ID's") require.NoError(t, err, "unable to process short chan ID's")
@@ -695,6 +701,7 @@ func TestGossipSyncerReplyShortChanIDsWrongChainHash(t *testing.T) {
// announcements, as well as an ending ReplyShortChanIDsEnd message. // announcements, as well as an ending ReplyShortChanIDsEnd message.
func TestGossipSyncerReplyShortChanIDs(t *testing.T) { func TestGossipSyncerReplyShortChanIDs(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
// First, we'll create a GossipSyncer instance with a canned sendToPeer // First, we'll create a GossipSyncer instance with a canned sendToPeer
// message to allow us to intercept their potential sends. // message to allow us to intercept their potential sends.
@@ -745,7 +752,7 @@ func TestGossipSyncerReplyShortChanIDs(t *testing.T) {
// With our set up above complete, we'll now attempt to obtain a reply // With our set up above complete, we'll now attempt to obtain a reply
// from the channel syncer for our target chan ID query. // from the channel syncer for our target chan ID query.
err := syncer.replyShortChanIDs(&lnwire.QueryShortChanIDs{ err := syncer.replyShortChanIDs(ctx, &lnwire.QueryShortChanIDs{
ShortChanIDs: queryChanIDs, ShortChanIDs: queryChanIDs,
}) })
require.NoError(t, err, "unable to query for chan IDs") require.NoError(t, err, "unable to query for chan IDs")
@@ -800,6 +807,7 @@ func TestGossipSyncerReplyShortChanIDs(t *testing.T) {
// the remote peer. // the remote peer.
func TestGossipSyncerReplyChanRangeQuery(t *testing.T) { func TestGossipSyncerReplyChanRangeQuery(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
// We'll use a smaller chunk size so we can easily test all the edge // We'll use a smaller chunk size so we can easily test all the edge
// cases. // cases.
@@ -866,7 +874,7 @@ func TestGossipSyncerReplyChanRangeQuery(t *testing.T) {
}() }()
// With our goroutine active, we'll now issue the query. // With our goroutine active, we'll now issue the query.
if err := syncer.replyChanRangeQuery(query); err != nil { if err := syncer.replyChanRangeQuery(ctx, query); err != nil {
t.Fatalf("unable to issue query: %v", err) t.Fatalf("unable to issue query: %v", err)
} }
@@ -971,6 +979,7 @@ func TestGossipSyncerReplyChanRangeQuery(t *testing.T) {
// executed with the correct block range. // executed with the correct block range.
func TestGossipSyncerReplyChanRangeQueryBlockRange(t *testing.T) { func TestGossipSyncerReplyChanRangeQueryBlockRange(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
// First create our test gossip syncer that will handle and // First create our test gossip syncer that will handle and
// respond to the test queries // respond to the test queries
@@ -1052,7 +1061,8 @@ func TestGossipSyncerReplyChanRangeQueryBlockRange(t *testing.T) {
// will be reached // will be reached
go func() { go func() {
for _, query := range queryReqs { for _, query := range queryReqs {
if err := syncer.replyChanRangeQuery(query); err != nil { err := syncer.replyChanRangeQuery(ctx, query)
if err != nil {
errCh <- fmt.Errorf("unable to issue query: %w", errCh <- fmt.Errorf("unable to issue query: %w",
err) err)
return return
@@ -1083,6 +1093,7 @@ func TestGossipSyncerReplyChanRangeQueryBlockRange(t *testing.T) {
// back a single response that signals completion. // back a single response that signals completion.
func TestGossipSyncerReplyChanRangeQueryNoNewChans(t *testing.T) { func TestGossipSyncerReplyChanRangeQueryNoNewChans(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
// We'll now create our test gossip syncer that will shortly respond to // We'll now create our test gossip syncer that will shortly respond to
// our canned query. // our canned query.
@@ -1121,7 +1132,7 @@ func TestGossipSyncerReplyChanRangeQueryNoNewChans(t *testing.T) {
}() }()
// With our goroutine active, we'll now issue the query. // With our goroutine active, we'll now issue the query.
if err := syncer.replyChanRangeQuery(query); err != nil { if err := syncer.replyChanRangeQuery(ctx, query); err != nil {
t.Fatalf("unable to issue query: %v", err) t.Fatalf("unable to issue query: %v", err)
} }
@@ -1162,6 +1173,7 @@ func TestGossipSyncerReplyChanRangeQueryNoNewChans(t *testing.T) {
// channel ID, we properly generate an correct initial channel range response. // channel ID, we properly generate an correct initial channel range response.
func TestGossipSyncerGenChanRangeQuery(t *testing.T) { func TestGossipSyncerGenChanRangeQuery(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
// First, we'll create a GossipSyncer instance with a canned sendToPeer // First, we'll create a GossipSyncer instance with a canned sendToPeer
// message to allow us to intercept their potential sends. // message to allow us to intercept their potential sends.
@@ -1174,7 +1186,7 @@ func TestGossipSyncerGenChanRangeQuery(t *testing.T) {
// If we now ask the syncer to generate an initial range query, it // If we now ask the syncer to generate an initial range query, it
// should return a start height that's back chanRangeQueryBuffer // should return a start height that's back chanRangeQueryBuffer
// blocks. // blocks.
rangeQuery, err := syncer.genChanRangeQuery(false) rangeQuery, err := syncer.genChanRangeQuery(ctx, false)
require.NoError(t, err, "unable to resp") require.NoError(t, err, "unable to resp")
firstHeight := uint32(startingHeight - chanRangeQueryBuffer) firstHeight := uint32(startingHeight - chanRangeQueryBuffer)
@@ -1190,7 +1202,7 @@ func TestGossipSyncerGenChanRangeQuery(t *testing.T) {
// Generating a historical range query should result in a start height // Generating a historical range query should result in a start height
// of 0. // of 0.
rangeQuery, err = syncer.genChanRangeQuery(true) rangeQuery, err = syncer.genChanRangeQuery(ctx, true)
require.NoError(t, err, "unable to resp") require.NoError(t, err, "unable to resp")
if rangeQuery.FirstBlockHeight != 0 { if rangeQuery.FirstBlockHeight != 0 {
t.Fatalf("incorrect chan range query: expected %v, %v", 0, t.Fatalf("incorrect chan range query: expected %v, %v", 0,
@@ -1222,6 +1234,7 @@ func TestGossipSyncerProcessChanRangeReply(t *testing.T) {
// each reply instead. // each reply instead.
func testGossipSyncerProcessChanRangeReply(t *testing.T, legacy bool) { func testGossipSyncerProcessChanRangeReply(t *testing.T, legacy bool) {
t.Parallel() t.Parallel()
ctx := context.Background()
// First, we'll create a GossipSyncer instance with a canned sendToPeer // First, we'll create a GossipSyncer instance with a canned sendToPeer
// message to allow us to intercept their potential sends. // message to allow us to intercept their potential sends.
@@ -1234,7 +1247,7 @@ func testGossipSyncerProcessChanRangeReply(t *testing.T, legacy bool) {
startingState := syncer.state startingState := syncer.state
query, err := syncer.genChanRangeQuery(true) query, err := syncer.genChanRangeQuery(ctx, true)
require.NoError(t, err, "unable to generate channel range query") require.NoError(t, err, "unable to generate channel range query")
currentTimestamp := time.Now().Unix() currentTimestamp := time.Now().Unix()
@@ -1359,13 +1372,13 @@ func testGossipSyncerProcessChanRangeReply(t *testing.T, legacy bool) {
// We'll begin by sending the syncer a set of non-complete channel // We'll begin by sending the syncer a set of non-complete channel
// range replies. // range replies.
if err := syncer.processChanRangeReply(replies[0]); err != nil { if err := syncer.processChanRangeReply(ctx, replies[0]); err != nil {
t.Fatalf("unable to process reply: %v", err) t.Fatalf("unable to process reply: %v", err)
} }
if err := syncer.processChanRangeReply(replies[1]); err != nil { if err := syncer.processChanRangeReply(ctx, replies[1]); err != nil {
t.Fatalf("unable to process reply: %v", err) t.Fatalf("unable to process reply: %v", err)
} }
if err := syncer.processChanRangeReply(replies[2]); err != nil { if err := syncer.processChanRangeReply(ctx, replies[2]); err != nil {
t.Fatalf("unable to process reply: %v", err) t.Fatalf("unable to process reply: %v", err)
} }
@@ -1427,7 +1440,7 @@ func testGossipSyncerProcessChanRangeReply(t *testing.T, legacy bool) {
// If we send the final message, then we should transition to // If we send the final message, then we should transition to
// queryNewChannels as we've sent a non-empty set of new channels. // queryNewChannels as we've sent a non-empty set of new channels.
if err := syncer.processChanRangeReply(replies[3]); err != nil { if err := syncer.processChanRangeReply(ctx, replies[3]); err != nil {
t.Fatalf("unable to process reply: %v", err) t.Fatalf("unable to process reply: %v", err)
} }
@@ -1482,7 +1495,7 @@ func TestGossipSyncerSynchronizeChanIDs(t *testing.T) {
for i := 0; i < chunkSize*2; i += 2 { for i := 0; i < chunkSize*2; i += 2 {
// With our set up complete, we'll request a sync of chan ID's. // With our set up complete, we'll request a sync of chan ID's.
done := syncer.synchronizeChanIDs() done := syncer.synchronizeChanIDs(context.Background())
// At this point, we shouldn't yet be done as only 2 items // At this point, we shouldn't yet be done as only 2 items
// should have been queried for. // should have been queried for.
@@ -1529,7 +1542,7 @@ func TestGossipSyncerSynchronizeChanIDs(t *testing.T) {
} }
// If we issue another query, the syncer should tell us that it's done. // If we issue another query, the syncer should tell us that it's done.
done := syncer.synchronizeChanIDs() done := syncer.synchronizeChanIDs(context.Background())
if done { if done {
t.Fatalf("syncer should be finished!") t.Fatalf("syncer should be finished!")
} }
@@ -31,6 +31,14 @@
# Improvements # Improvements
## Functional Updates ## Functional Updates
* Graph Store SQL implementation and migration project:
* Introduce an [abstract graph
store](https://github.com/lightningnetwork/lnd/pull/9791) interface.
* Start [validating](https://github.com/lightningnetwork/lnd/pull/9787) that
byte blobs at the end of gossip messages are valid TLV streams.
* Various [preparations](https://github.com/lightningnetwork/lnd/pull/9692)
of the graph code before the SQL implementation is added.
## RPC Updates ## RPC Updates
## lncli Updates ## lncli Updates
+4
View File
@@ -199,6 +199,10 @@ require (
sigs.k8s.io/yaml v1.2.0 // indirect sigs.k8s.io/yaml v1.2.0 // indirect
) )
// TODO(elle): remove once all the schemas and queries for the graph
// store have been included in a tagged sqldb version.
replace github.com/lightningnetwork/lnd/sqldb => ./sqldb
// This replace is for https://github.com/advisories/GHSA-25xm-hr59-7c27 // This replace is for https://github.com/advisories/GHSA-25xm-hr59-7c27
replace github.com/ulikunitz/xz => github.com/ulikunitz/xz v0.5.11 replace github.com/ulikunitz/xz => github.com/ulikunitz/xz v0.5.11
-2
View File
@@ -375,8 +375,6 @@ github.com/lightningnetwork/lnd/kvdb v1.4.16 h1:9BZgWdDfjmHRHLS97cz39bVuBAqMc4/p
github.com/lightningnetwork/lnd/kvdb v1.4.16/go.mod h1:HW+bvwkxNaopkz3oIgBV6NEnV4jCEZCACFUcNg4xSjM= github.com/lightningnetwork/lnd/kvdb v1.4.16/go.mod h1:HW+bvwkxNaopkz3oIgBV6NEnV4jCEZCACFUcNg4xSjM=
github.com/lightningnetwork/lnd/queue v1.1.1 h1:99ovBlpM9B0FRCGYJo6RSFDlt8/vOkQQZznVb18iNMI= github.com/lightningnetwork/lnd/queue v1.1.1 h1:99ovBlpM9B0FRCGYJo6RSFDlt8/vOkQQZznVb18iNMI=
github.com/lightningnetwork/lnd/queue v1.1.1/go.mod h1:7A6nC1Qrm32FHuhx/mi1cieAiBZo5O6l8IBIoQxvkz4= github.com/lightningnetwork/lnd/queue v1.1.1/go.mod h1:7A6nC1Qrm32FHuhx/mi1cieAiBZo5O6l8IBIoQxvkz4=
github.com/lightningnetwork/lnd/sqldb v1.0.9 h1:7OHi+Hui823mB/U9NzCdlZTAGSVdDCbjp33+6d/Q+G0=
github.com/lightningnetwork/lnd/sqldb v1.0.9/go.mod h1:OG09zL/PHPaBJefp4HsPz2YLUJ+zIQHbpgCtLnOx8I4=
github.com/lightningnetwork/lnd/ticker v1.1.1 h1:J/b6N2hibFtC7JLV77ULQp++QLtCwT6ijJlbdiZFbSM= github.com/lightningnetwork/lnd/ticker v1.1.1 h1:J/b6N2hibFtC7JLV77ULQp++QLtCwT6ijJlbdiZFbSM=
github.com/lightningnetwork/lnd/ticker v1.1.1/go.mod h1:waPTRAAcwtu7Ji3+3k+u/xH5GHovTsCoSVpho0KDvdA= github.com/lightningnetwork/lnd/ticker v1.1.1/go.mod h1:waPTRAAcwtu7Ji3+3k+u/xH5GHovTsCoSVpho0KDvdA=
github.com/lightningnetwork/lnd/tlv v1.3.1 h1:o7CZg06y+rJZfUMAo0WzBLr0pgBWCzrt0f9gpujYUzk= github.com/lightningnetwork/lnd/tlv v1.3.1 h1:o7CZg06y+rJZfUMAo0WzBLr0pgBWCzrt0f9gpujYUzk=
+1 -3
View File
@@ -13,7 +13,6 @@ import (
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
graphdb "github.com/lightningnetwork/lnd/graph/db" graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
@@ -1276,8 +1275,7 @@ func (b *Builder) ForAllOutgoingChannels(cb func(*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy) error) error { *models.ChannelEdgePolicy) error) error {
return b.cfg.Graph.ForEachNodeChannel(b.cfg.SelfNode, return b.cfg.Graph.ForEachNodeChannel(b.cfg.SelfNode,
func(_ kvdb.RTx, c *models.ChannelEdgeInfo, func(c *models.ChannelEdgeInfo, e *models.ChannelEdgePolicy,
e *models.ChannelEdgePolicy,
_ *models.ChannelEdgePolicy) error { _ *models.ChannelEdgePolicy) error {
if e == nil { if e == nil {
+11 -19
View File
@@ -1352,10 +1352,7 @@ func parseTestGraph(t *testing.T, useCache bool, path string) (
testAddrs = append(testAddrs, testAddr) testAddrs = append(testAddrs, testAddr)
// Next, create a temporary graph database for usage within the test. // Next, create a temporary graph database for usage within the test.
graph, graphBackend, err := makeTestGraph(t, useCache) graph := graphdb.MakeTestGraph(t, graphdb.WithUseGraphCache(useCache))
if err != nil {
return nil, err
}
aliasMap := make(map[string]route.Vertex) aliasMap := make(map[string]route.Vertex)
privKeyMap := make(map[string]*btcec.PrivateKey) privKeyMap := make(map[string]*btcec.PrivateKey)
@@ -1562,12 +1559,11 @@ func parseTestGraph(t *testing.T, useCache bool, path string) (
} }
return &testGraphInstance{ return &testGraphInstance{
graph: graph, graph: graph,
graphBackend: graphBackend, aliasMap: aliasMap,
aliasMap: aliasMap, privKeyMap: privKeyMap,
privKeyMap: privKeyMap, channelIDs: channelIDs,
channelIDs: channelIDs, links: links,
links: links,
}, nil }, nil
} }
@@ -1730,10 +1726,7 @@ func createTestGraphFromChannels(t *testing.T, useCache bool,
testAddrs = append(testAddrs, testAddr) testAddrs = append(testAddrs, testAddr)
// Next, create a temporary graph database for usage within the test. // Next, create a temporary graph database for usage within the test.
graph, graphBackend, err := makeTestGraph(t, useCache) graph := graphdb.MakeTestGraph(t, graphdb.WithUseGraphCache(useCache))
if err != nil {
return nil, err
}
aliasMap := make(map[string]route.Vertex) aliasMap := make(map[string]route.Vertex)
privKeyMap := make(map[string]*btcec.PrivateKey) privKeyMap := make(map[string]*btcec.PrivateKey)
@@ -1947,11 +1940,10 @@ func createTestGraphFromChannels(t *testing.T, useCache bool,
} }
return &testGraphInstance{ return &testGraphInstance{
graph: graph, graph: graph,
graphBackend: graphBackend, aliasMap: aliasMap,
aliasMap: aliasMap, privKeyMap: privKeyMap,
privKeyMap: privKeyMap, links: links,
links: links,
}, nil }, nil
} }
+29 -21
View File
@@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/lightningnetwork/lnd/tor" "github.com/lightningnetwork/lnd/tor"
"github.com/stretchr/testify/require"
) )
type unknownAddrType struct{} type unknownAddrType struct{}
@@ -14,37 +15,47 @@ type unknownAddrType struct{}
func (t unknownAddrType) Network() string { return "unknown" } func (t unknownAddrType) Network() string { return "unknown" }
func (t unknownAddrType) String() string { return "unknown" } func (t unknownAddrType) String() string { return "unknown" }
var testIP4 = net.ParseIP("192.168.1.1") var testIP4 = net.ParseIP("192.168.1.1").To4()
var testIP6 = net.ParseIP("2001:0db8:0000:0000:0000:ff00:0042:8329") var testIP6 = net.ParseIP("2001:0db8:0000:0000:0000:ff00:0042:8329")
var (
testIPV4Addr = &net.TCPAddr{
IP: testIP4,
Port: 12345,
}
testIPV6Addr = &net.TCPAddr{
IP: testIP6,
Port: 65535,
}
testOnionV2Addr = &tor.OnionAddr{
OnionService: "3g2upl4pq6kufc4m.onion",
Port: 9735,
}
testOnionV3Addr = &tor.OnionAddr{
OnionService: "vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion", //nolint:ll
Port: 80,
}
)
var addrTests = []struct { var addrTests = []struct {
expAddr net.Addr expAddr net.Addr
serErr string serErr string
}{ }{
// Valid addresses. // Valid addresses.
{ {
expAddr: &net.TCPAddr{ expAddr: testIPV4Addr,
IP: testIP4,
Port: 12345,
},
}, },
{ {
expAddr: &net.TCPAddr{ expAddr: testIPV6Addr,
IP: testIP6,
Port: 65535,
},
}, },
{ {
expAddr: &tor.OnionAddr{ expAddr: testOnionV2Addr,
OnionService: "3g2upl4pq6kufc4m.onion",
Port: 9735,
},
}, },
{ {
expAddr: &tor.OnionAddr{ expAddr: testOnionV3Addr,
OnionService: "vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion",
Port: 80,
},
}, },
// Invalid addresses. // Invalid addresses.
@@ -141,9 +152,6 @@ func TestAddrSerialization(t *testing.T) {
t.Fatalf("unable to deserialize address: %v", err) t.Fatalf("unable to deserialize address: %v", err)
} }
if addr.String() != test.expAddr.String() { require.Equal(t, test.expAddr, addr)
t.Fatalf("expected address %v after serialization, "+
"got %v", addr, test.expAddr)
}
} }
} }
+52 -41
View File
@@ -5,33 +5,22 @@ import (
"fmt" "fmt"
"sync" "sync"
"sync/atomic" "sync/atomic"
"testing"
"time" "time"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/batch" "github.com/lightningnetwork/lnd/batch"
"github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/route"
"github.com/stretchr/testify/require"
) )
// ErrChanGraphShuttingDown indicates that the ChannelGraph has shutdown or is // ErrChanGraphShuttingDown indicates that the ChannelGraph has shutdown or is
// busy shutting down. // busy shutting down.
var ErrChanGraphShuttingDown = fmt.Errorf("ChannelGraph shutting down") var ErrChanGraphShuttingDown = fmt.Errorf("ChannelGraph shutting down")
// Config is a struct that holds all the necessary dependencies for a
// ChannelGraph.
type Config struct {
// KVDB is the kvdb.Backend that will be used for initializing the
// KVStore CRUD layer.
KVDB kvdb.Backend
// KVStoreOpts is a list of functional options that will be used when
// initializing the KVStore.
KVStoreOpts []KVStoreOptionModifier
}
// ChannelGraph is a layer above the graph's CRUD layer. // ChannelGraph is a layer above the graph's CRUD layer.
// //
// NOTE: currently, this is purely a pass-through layer directly to the backing // NOTE: currently, this is purely a pass-through layer directly to the backing
@@ -43,7 +32,7 @@ type ChannelGraph struct {
graphCache *GraphCache graphCache *GraphCache
*KVStore V1Store
*topologyManager *topologyManager
quit chan struct{} quit chan struct{}
@@ -51,21 +40,16 @@ type ChannelGraph struct {
} }
// NewChannelGraph creates a new ChannelGraph instance with the given backend. // NewChannelGraph creates a new ChannelGraph instance with the given backend.
func NewChannelGraph(cfg *Config, options ...ChanGraphOption) (*ChannelGraph, func NewChannelGraph(v1Store V1Store,
error) { options ...ChanGraphOption) (*ChannelGraph, error) {
opts := defaultChanGraphOptions() opts := defaultChanGraphOptions()
for _, o := range options { for _, o := range options {
o(opts) o(opts)
} }
store, err := NewKVStore(cfg.KVDB, cfg.KVStoreOpts...)
if err != nil {
return nil, err
}
g := &ChannelGraph{ g := &ChannelGraph{
KVStore: store, V1Store: v1Store,
topologyManager: newTopologyManager(), topologyManager: newTopologyManager(),
quit: make(chan struct{}), quit: make(chan struct{}),
} }
@@ -179,7 +163,7 @@ func (c *ChannelGraph) populateCache() error {
log.Info("Populating in-memory channel graph, this might take a " + log.Info("Populating in-memory channel graph, this might take a " +
"while...") "while...")
err := c.KVStore.ForEachNodeCacheable(func(node route.Vertex, err := c.V1Store.ForEachNodeCacheable(func(node route.Vertex,
features *lnwire.FeatureVector) error { features *lnwire.FeatureVector) error {
c.graphCache.AddNodeFeatures(node, features) c.graphCache.AddNodeFeatures(node, features)
@@ -190,7 +174,7 @@ func (c *ChannelGraph) populateCache() error {
return err return err
} }
err = c.KVStore.ForEachChannel(func(info *models.ChannelEdgeInfo, err = c.V1Store.ForEachChannel(func(info *models.ChannelEdgeInfo,
policy1, policy2 *models.ChannelEdgePolicy) error { policy1, policy2 *models.ChannelEdgePolicy) error {
c.graphCache.AddChannel(info, policy1, policy2) c.graphCache.AddChannel(info, policy1, policy2)
@@ -224,7 +208,7 @@ func (c *ChannelGraph) ForEachNodeDirectedChannel(node route.Vertex,
return c.graphCache.ForEachChannel(node, cb) return c.graphCache.ForEachChannel(node, cb)
} }
return c.KVStore.ForEachNodeDirectedChannel(node, cb) return c.V1Store.ForEachNodeDirectedChannel(node, cb)
} }
// FetchNodeFeatures returns the features of the given node. If no features are // FetchNodeFeatures returns the features of the given node. If no features are
@@ -240,7 +224,7 @@ func (c *ChannelGraph) FetchNodeFeatures(node route.Vertex) (
return c.graphCache.GetFeatures(node), nil return c.graphCache.GetFeatures(node), nil
} }
return c.KVStore.FetchNodeFeatures(node) return c.V1Store.FetchNodeFeatures(node)
} }
// GraphSession will provide the call-back with access to a NodeTraverser // GraphSession will provide the call-back with access to a NodeTraverser
@@ -252,7 +236,7 @@ func (c *ChannelGraph) GraphSession(cb func(graph NodeTraverser) error) error {
return cb(c) return cb(c)
} }
return c.KVStore.GraphSession(cb) return c.V1Store.GraphSession(cb)
} }
// ForEachNodeCached iterates through all the stored vertices/nodes in the // ForEachNodeCached iterates through all the stored vertices/nodes in the
@@ -266,7 +250,7 @@ func (c *ChannelGraph) ForEachNodeCached(cb func(node route.Vertex,
return c.graphCache.ForEachNode(cb) return c.graphCache.ForEachNode(cb)
} }
return c.KVStore.ForEachNodeCached(cb) return c.V1Store.ForEachNodeCached(cb)
} }
// AddLightningNode adds a vertex/node to the graph database. If the node is not // AddLightningNode adds a vertex/node to the graph database. If the node is not
@@ -278,7 +262,7 @@ func (c *ChannelGraph) ForEachNodeCached(cb func(node route.Vertex,
func (c *ChannelGraph) AddLightningNode(node *models.LightningNode, func (c *ChannelGraph) AddLightningNode(node *models.LightningNode,
op ...batch.SchedulerOption) error { op ...batch.SchedulerOption) error {
err := c.KVStore.AddLightningNode(node, op...) err := c.V1Store.AddLightningNode(node, op...)
if err != nil { if err != nil {
return err return err
} }
@@ -301,7 +285,7 @@ func (c *ChannelGraph) AddLightningNode(node *models.LightningNode,
// DeleteLightningNode starts a new database transaction to remove a vertex/node // DeleteLightningNode starts a new database transaction to remove a vertex/node
// from the database according to the node's public key. // from the database according to the node's public key.
func (c *ChannelGraph) DeleteLightningNode(nodePub route.Vertex) error { func (c *ChannelGraph) DeleteLightningNode(nodePub route.Vertex) error {
err := c.KVStore.DeleteLightningNode(nodePub) err := c.V1Store.DeleteLightningNode(nodePub)
if err != nil { if err != nil {
return err return err
} }
@@ -322,7 +306,7 @@ func (c *ChannelGraph) DeleteLightningNode(nodePub route.Vertex) error {
func (c *ChannelGraph) AddChannelEdge(edge *models.ChannelEdgeInfo, func (c *ChannelGraph) AddChannelEdge(edge *models.ChannelEdgeInfo,
op ...batch.SchedulerOption) error { op ...batch.SchedulerOption) error {
err := c.KVStore.AddChannelEdge(edge, op...) err := c.V1Store.AddChannelEdge(edge, op...)
if err != nil { if err != nil {
return err return err
} }
@@ -344,7 +328,7 @@ func (c *ChannelGraph) AddChannelEdge(edge *models.ChannelEdgeInfo,
// If the cache is enabled, the edge will be added back to the graph cache if // If the cache is enabled, the edge will be added back to the graph cache if
// we still have a record of this channel in the DB. // we still have a record of this channel in the DB.
func (c *ChannelGraph) MarkEdgeLive(chanID uint64) error { func (c *ChannelGraph) MarkEdgeLive(chanID uint64) error {
err := c.KVStore.MarkEdgeLive(chanID) err := c.V1Store.MarkEdgeLive(chanID)
if err != nil { if err != nil {
return err return err
} }
@@ -352,7 +336,7 @@ func (c *ChannelGraph) MarkEdgeLive(chanID uint64) error {
if c.graphCache != nil { if c.graphCache != nil {
// We need to add the channel back into our graph cache, // We need to add the channel back into our graph cache,
// otherwise we won't use it for path finding. // otherwise we won't use it for path finding.
infos, err := c.KVStore.FetchChanInfos([]uint64{chanID}) infos, err := c.V1Store.FetchChanInfos([]uint64{chanID})
if err != nil { if err != nil {
return err return err
} }
@@ -380,7 +364,7 @@ func (c *ChannelGraph) MarkEdgeLive(chanID uint64) error {
func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning, markZombie bool, func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning, markZombie bool,
chanIDs ...uint64) error { chanIDs ...uint64) error {
infos, err := c.KVStore.DeleteChannelEdges( infos, err := c.V1Store.DeleteChannelEdges(
strictZombiePruning, markZombie, chanIDs..., strictZombiePruning, markZombie, chanIDs...,
) )
if err != nil { if err != nil {
@@ -409,7 +393,7 @@ func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning, markZombie bool,
func (c *ChannelGraph) DisconnectBlockAtHeight(height uint32) ( func (c *ChannelGraph) DisconnectBlockAtHeight(height uint32) (
[]*models.ChannelEdgeInfo, error) { []*models.ChannelEdgeInfo, error) {
edges, err := c.KVStore.DisconnectBlockAtHeight(height) edges, err := c.V1Store.DisconnectBlockAtHeight(height)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -437,7 +421,7 @@ func (c *ChannelGraph) PruneGraph(spentOutputs []*wire.OutPoint,
blockHash *chainhash.Hash, blockHeight uint32) ( blockHash *chainhash.Hash, blockHeight uint32) (
[]*models.ChannelEdgeInfo, error) { []*models.ChannelEdgeInfo, error) {
edges, nodes, err := c.KVStore.PruneGraph( edges, nodes, err := c.V1Store.PruneGraph(
spentOutputs, blockHash, blockHeight, spentOutputs, blockHash, blockHeight,
) )
if err != nil { if err != nil {
@@ -482,7 +466,7 @@ func (c *ChannelGraph) PruneGraph(spentOutputs []*wire.OutPoint,
// that we only maintain a graph of reachable nodes. In the event that a pruned // 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. // node gains more channels, it will be re-added back to the graph.
func (c *ChannelGraph) PruneGraphNodes() error { func (c *ChannelGraph) PruneGraphNodes() error {
nodes, err := c.KVStore.PruneGraphNodes() nodes, err := c.V1Store.PruneGraphNodes()
if err != nil { if err != nil {
return err return err
} }
@@ -504,7 +488,7 @@ func (c *ChannelGraph) PruneGraphNodes() error {
func (c *ChannelGraph) FilterKnownChanIDs(chansInfo []ChannelUpdateInfo, func (c *ChannelGraph) FilterKnownChanIDs(chansInfo []ChannelUpdateInfo,
isZombieChan func(time.Time, time.Time) bool) ([]uint64, error) { isZombieChan func(time.Time, time.Time) bool) ([]uint64, error) {
unknown, knownZombies, err := c.KVStore.FilterKnownChanIDs(chansInfo) unknown, knownZombies, err := c.V1Store.FilterKnownChanIDs(chansInfo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -533,7 +517,7 @@ func (c *ChannelGraph) FilterKnownChanIDs(chansInfo []ChannelUpdateInfo,
// timestamps could bring it back from the dead, then we mark it // timestamps could bring it back from the dead, then we mark it
// alive, and we let it be added to the set of IDs to query our // alive, and we let it be added to the set of IDs to query our
// peer for. // peer for.
err := c.KVStore.MarkEdgeLive( err := c.V1Store.MarkEdgeLive(
info.ShortChannelID.ToUint64(), info.ShortChannelID.ToUint64(),
) )
// Since there is a chance that the edge could have been marked // Since there is a chance that the edge could have been marked
@@ -554,7 +538,7 @@ func (c *ChannelGraph) FilterKnownChanIDs(chansInfo []ChannelUpdateInfo,
func (c *ChannelGraph) MarkEdgeZombie(chanID uint64, func (c *ChannelGraph) MarkEdgeZombie(chanID uint64,
pubKey1, pubKey2 [33]byte) error { pubKey1, pubKey2 [33]byte) error {
err := c.KVStore.MarkEdgeZombie(chanID, pubKey1, pubKey2) err := c.V1Store.MarkEdgeZombie(chanID, pubKey1, pubKey2)
if err != nil { if err != nil {
return err return err
} }
@@ -576,7 +560,7 @@ func (c *ChannelGraph) MarkEdgeZombie(chanID uint64,
func (c *ChannelGraph) UpdateEdgePolicy(edge *models.ChannelEdgePolicy, func (c *ChannelGraph) UpdateEdgePolicy(edge *models.ChannelEdgePolicy,
op ...batch.SchedulerOption) error { op ...batch.SchedulerOption) error {
from, to, err := c.KVStore.UpdateEdgePolicy(edge, op...) from, to, err := c.V1Store.UpdateEdgePolicy(edge, op...)
if err != nil { if err != nil {
return err return err
} }
@@ -598,3 +582,30 @@ func (c *ChannelGraph) UpdateEdgePolicy(edge *models.ChannelEdgePolicy,
return nil return nil
} }
// MakeTestGraphNew creates a new instance of the ChannelGraph for testing
// purposes. The backing V1Store implementation depends on the version of
// NewTestDB included in the current build.
//
// NOTE: this is currently unused, but is left here for future use to show how
// NewTestDB can be used. As the SQL implementation of the V1Store is
// implemented, unit tests will be switched to use this function instead of
// the existing MakeTestGraph helper. Once only this function is used, the
// existing MakeTestGraph function will be removed and this one will be renamed.
func MakeTestGraphNew(t testing.TB,
opts ...ChanGraphOption) *ChannelGraph {
t.Helper()
store := NewTestDB(t)
graph, err := NewChannelGraph(store, opts...)
require.NoError(t, err)
require.NoError(t, graph.Start())
t.Cleanup(func() {
require.NoError(t, graph.Stop())
})
return graph
}
+584 -457
View File
File diff suppressed because it is too large. Load diff
+327
View File
@@ -1,6 +1,13 @@
package graphdb package graphdb
import ( import (
"net"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/batch"
"github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/route"
@@ -37,3 +44,323 @@ type NodeTraverser interface {
// FetchNodeFeatures returns the features of the given node. // FetchNodeFeatures returns the features of the given node.
FetchNodeFeatures(nodePub route.Vertex) (*lnwire.FeatureVector, error) FetchNodeFeatures(nodePub route.Vertex) (*lnwire.FeatureVector, error)
} }
// V1Store represents the main interface for the channel graph database for all
// channels and nodes gossiped via the V1 gossip protocol as defined in BOLT 7.
type V1Store interface { //nolint:interfacebloat
NodeTraverser
// 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 *models.LightningNode,
op ...batch.SchedulerOption) error
// AddrsForNode returns all known addresses for the target node public
// key that the graph DB is aware of. The returned boolean indicates if
// the given node is unknown to the graph DB or not.
AddrsForNode(nodePub *btcec.PublicKey) (bool, []net.Addr, error)
// ForEachSourceNodeChannel iterates through all channels of the source
// node, executing the passed callback on each. The call-back is
// provided with the channel's outpoint, whether we have a policy for
// the channel and the channel peer's node information.
ForEachSourceNodeChannel(cb func(chanPoint wire.OutPoint,
havePolicy bool, otherNode *models.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(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
*models.ChannelEdgePolicy) error) error
// ForEachNodeCached is similar to forEachNode, but it returns
// DirectedChannel data to the call-back.
//
// NOTE: The callback contents MUST not be modified.
ForEachNodeCached(cb func(node route.Vertex,
chans map[uint64]*DirectedChannel) error) 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. Any operations performed on the NodeTx
// passed to the call-back are executed under the same read transaction
// and so, methods on the NodeTx object _MUST_ only be called from
// within the call-back.
ForEachNode(cb func(tx NodeRTx) error) error
// ForEachNodeCacheable 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.
ForEachNodeCacheable(cb func(route.Vertex,
*lnwire.FeatureVector) error) error
// LookupAlias attempts to return the alias as advertised by the target
// node.
LookupAlias(pub *btcec.PublicKey) (string, error)
// DeleteLightningNode starts a new database transaction to remove a
// vertex/node from the database according to the node's public key.
DeleteLightningNode(nodePub route.Vertex) error
// NodeUpdatesInHorizon returns all the known lightning node which have
// an update timestamp within the passed range. This method can be used
// by two nodes to quickly determine if they have the same set of up to
// date node announcements.
NodeUpdatesInHorizon(startTime,
endTime time.Time) ([]models.LightningNode, 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) (
*models.LightningNode, 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)
// 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)
// GraphSession will provide the call-back with access to a
// NodeTraverser instance which can be used to perform queries against
// the channel graph.
GraphSession(cb func(graph NodeTraverser) error) error
// ForEachChannel iterates through all the channel edges stored within
// the graph and invokes the passed callback for each edge. The callback
// takes two edges as since this is a directed graph, both the in/out
// edges are visited. If the callback returns an error, then the
// transaction is aborted and the iteration stops early.
//
// NOTE: If an edge can't be found, or wasn't advertised, then a nil
// pointer for that particular channel edge routing policy will be
// passed into the callback.
ForEachChannel(cb func(*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy,
*models.ChannelEdgePolicy) error) 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)
// 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
// 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)
// 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) ([]*models.ChannelEdgeInfo, error)
// AddEdgeProof sets the proof of an existing edge in the graph
// database.
AddEdgeProof(chanID lnwire.ShortChannelID,
proof *models.ChannelAuthProof) error
// ChannelID attempt to lookup the 8-byte compact channel ID which maps
// to the passed channel point (outpoint). If the passed channel doesn't
// exist within the database, then ErrEdgeNotFound is returned.
ChannelID(chanPoint *wire.OutPoint) (uint64, error)
// HighestChanID returns the "highest" known channel ID in the channel
// graph. This represents the "newest" channel from the PoV of the
// chain. This method can be used by peers to quickly determine if
// they're graphs are in sync.
HighestChanID() (uint64, 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) ([]ChannelEdge,
error)
// FilterKnownChanIDs takes a set of channel IDs and return the subset
// of chan ID's that we don't know and are not known zombies of the
// passed set. In other words, we perform a set difference of our set
// of chan ID's and the ones passed in. This method can be used by
// callers to determine the set of channels another peer knows of that
// we don't. The ChannelUpdateInfos for the known zombies is also
// returned.
FilterKnownChanIDs(chansInfo []ChannelUpdateInfo) ([]uint64,
[]ChannelUpdateInfo, error)
// FilterChannelRange returns the channel ID's of all known channels
// which were mined in a block height within the passed range. The
// channel IDs are grouped by their common block height. This method can
// be used to quickly share with a peer the set of channels we know of
// within a particular range to catch them up after a period of time
// offline. If withTimestamps is true then the timestamp info of the
// latest received channel update messages of the channel will be
// included in the response.
FilterChannelRange(startHeight, endHeight uint32, withTimestamps bool) (
[]BlockChannelRange, 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) ([]ChannelEdge, error)
// FetchChannelEdgesByOutpoint attempts to lookup the two directed edges
// for the channel identified by the funding outpoint. 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.
FetchChannelEdgesByOutpoint(op *wire.OutPoint) (
*models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
*models.ChannelEdgePolicy, 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 can 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)
// 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() ([]EdgePoint, 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
// MarkEdgeLive clears an edge from our zombie index, deeming it as
// live.
MarkEdgeLive(chanID uint64) error
// IsZombieEdge returns whether the edge is considered zombie. If it is
// a zombie, then the two node public keys corresponding to this edge
// are also returned.
IsZombieEdge(chanID uint64) (bool, [33]byte, [33]byte)
// NumZombies returns the current number of zombie channels in the
// graph.
NumZombies() (uint64, error)
// PutClosedScid stores a SCID for a closed channel in the database.
// This is so that we can ignore channel announcements that we know to
// be closed without having to validate them and fetch a block.
PutClosedScid(scid lnwire.ShortChannelID) error
// IsClosedScid checks whether a channel identified by the passed in
// scid is closed. This helps avoid having to perform expensive
// validation checks.
IsClosedScid(scid lnwire.ShortChannelID) (bool, 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) (route.Vertex, route.Vertex, 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() (*models.LightningNode, error)
// SetSourceNode sets the source node within the graph database. The
// source node is to be used as the center of a star-graph within path
// finding algorithms.
SetSourceNode(node *models.LightningNode) error
// 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)
// PruneGraphNodes is a garbage collection method which attempts to
// prune out any nodes from the channel graph that are currently
// unconnected. This ensures 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() ([]route.Vertex, 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 he 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 along
// with any pruned nodes are returned if the function succeeds without
// error.
PruneGraph(spentOutputs []*wire.OutPoint,
blockHash *chainhash.Hash, blockHeight uint32) (
[]*models.ChannelEdgeInfo, []route.Vertex, 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)
}
+102 -92
View File
@@ -2,6 +2,7 @@ package graphdb
import ( import (
"bytes" "bytes"
"context"
"crypto/sha256" "crypto/sha256"
"encoding/binary" "encoding/binary"
"errors" "errors"
@@ -192,10 +193,14 @@ type KVStore struct {
rejectCache *rejectCache rejectCache *rejectCache
chanCache *channelCache chanCache *channelCache
chanScheduler batch.Scheduler chanScheduler batch.Scheduler[kvdb.RwTx]
nodeScheduler batch.Scheduler nodeScheduler batch.Scheduler[kvdb.RwTx]
} }
// A compile-time assertion to ensure that the KVStore struct implements the
// V1Store interface.
var _ V1Store = (*KVStore)(nil)
// NewKVStore allocates a new KVStore backed by a DB instance. The // NewKVStore allocates a new KVStore backed by a DB instance. The
// returned instance has its own unique reject cache and channel cache. // returned instance has its own unique reject cache and channel cache.
func NewKVStore(db kvdb.Backend, options ...KVStoreOptionModifier) (*KVStore, func NewKVStore(db kvdb.Backend, options ...KVStoreOptionModifier) (*KVStore,
@@ -218,10 +223,12 @@ func NewKVStore(db kvdb.Backend, options ...KVStoreOptionModifier) (*KVStore,
chanCache: newChannelCache(opts.ChannelCacheSize), chanCache: newChannelCache(opts.ChannelCacheSize),
} }
g.chanScheduler = batch.NewTimeScheduler( g.chanScheduler = batch.NewTimeScheduler(
db, &g.cacheMu, opts.BatchCommitInterval, batch.NewBoltBackend[kvdb.RwTx](db), &g.cacheMu,
opts.BatchCommitInterval,
) )
g.nodeScheduler = batch.NewTimeScheduler( g.nodeScheduler = batch.NewTimeScheduler(
db, nil, opts.BatchCommitInterval, batch.NewBoltBackend[kvdb.RwTx](db), nil,
opts.BatchCommitInterval,
) )
return g, nil return g, nil
@@ -299,29 +306,6 @@ var graphTopLevelBuckets = [][]byte{
closedScidBucket, closedScidBucket,
} }
// Wipe completely deletes all saved state within all used buckets within the
// database. The deletion is done in a single transaction, therefore this
// operation is fully atomic.
func (c *KVStore) Wipe() error {
err := kvdb.Update(c.db, func(tx kvdb.RwTx) error {
for _, tlb := range graphTopLevelBuckets {
err := tx.DeleteTopLevelBucket(tlb)
if err != nil &&
!errors.Is(err, kvdb.ErrBucketNotFound) {
return err
}
}
return nil
}, func() {})
if err != nil {
return err
}
return initKVStore(c.db)
}
// createChannelDB creates and initializes a fresh version of In // createChannelDB creates and initializes a fresh version of In
// the case that the target path has not yet been created or doesn't yet exist, // the case that the target path has not yet been created or doesn't yet exist,
// then the path is created. Additionally, all required top-level buckets used // then the path is created. Additionally, all required top-level buckets used
@@ -584,7 +568,7 @@ func (c *KVStore) ForEachNodeCached(cb func(node route.Vertex,
channels := make(map[uint64]*DirectedChannel) channels := make(map[uint64]*DirectedChannel)
err := c.ForEachNodeChannelTx(tx, node.PubKeyBytes, err := c.forEachNodeChannelTx(tx, node.PubKeyBytes,
func(tx kvdb.RTx, e *models.ChannelEdgeInfo, func(tx kvdb.RTx, e *models.ChannelEdgeInfo,
p1 *models.ChannelEdgePolicy, p1 *models.ChannelEdgePolicy,
p2 *models.ChannelEdgePolicy) error { p2 *models.ChannelEdgePolicy) error {
@@ -871,19 +855,18 @@ func (c *KVStore) SetSourceNode(node *models.LightningNode) error {
// //
// TODO(roasbeef): also need sig of announcement. // TODO(roasbeef): also need sig of announcement.
func (c *KVStore) AddLightningNode(node *models.LightningNode, func (c *KVStore) AddLightningNode(node *models.LightningNode,
op ...batch.SchedulerOption) error { opts ...batch.SchedulerOption) error {
r := &batch.Request{ ctx := context.TODO()
Update: func(tx kvdb.RwTx) error {
r := &batch.Request[kvdb.RwTx]{
Opts: batch.NewSchedulerOptions(opts...),
Do: func(tx kvdb.RwTx) error {
return addLightningNode(tx, node) return addLightningNode(tx, node)
}, },
} }
for _, f := range op { return c.nodeScheduler.Execute(ctx, r)
f(r)
}
return c.nodeScheduler.Execute(r)
} }
func addLightningNode(tx kvdb.RwTx, node *models.LightningNode) error { func addLightningNode(tx kvdb.RwTx, node *models.LightningNode) error {
@@ -1009,14 +992,17 @@ func (c *KVStore) deleteLightningNode(nodes kvdb.RwBucket,
// supports. The chanPoint and chanID are used to uniquely identify the edge // supports. The chanPoint and chanID are used to uniquely identify the edge
// globally within the database. // globally within the database.
func (c *KVStore) AddChannelEdge(edge *models.ChannelEdgeInfo, func (c *KVStore) AddChannelEdge(edge *models.ChannelEdgeInfo,
op ...batch.SchedulerOption) error { opts ...batch.SchedulerOption) error {
ctx := context.TODO()
var alreadyExists bool var alreadyExists bool
r := &batch.Request{ r := &batch.Request[kvdb.RwTx]{
Opts: batch.NewSchedulerOptions(opts...),
Reset: func() { Reset: func() {
alreadyExists = false alreadyExists = false
}, },
Update: func(tx kvdb.RwTx) error { Do: func(tx kvdb.RwTx) error {
err := c.addChannelEdge(tx, edge) err := c.addChannelEdge(tx, edge)
// Silence ErrEdgeAlreadyExist so that the batch can // Silence ErrEdgeAlreadyExist so that the batch can
@@ -1042,15 +1028,7 @@ func (c *KVStore) AddChannelEdge(edge *models.ChannelEdgeInfo,
}, },
} }
for _, f := range op { return c.chanScheduler.Execute(ctx, r)
if f == nil {
return fmt.Errorf("nil scheduler option was used")
}
f(r)
}
return c.chanScheduler.Execute(r)
} }
// addChannelEdge is the private form of AddChannelEdge that allows callers to // addChannelEdge is the private form of AddChannelEdge that allows callers to
@@ -2719,20 +2697,22 @@ func makeZombiePubkeys(info *models.ChannelEdgeInfo,
// determined by the lexicographical ordering of the identity public keys of the // determined by the lexicographical ordering of the identity public keys of the
// nodes on either side of the channel. // nodes on either side of the channel.
func (c *KVStore) UpdateEdgePolicy(edge *models.ChannelEdgePolicy, func (c *KVStore) UpdateEdgePolicy(edge *models.ChannelEdgePolicy,
op ...batch.SchedulerOption) (route.Vertex, route.Vertex, error) { opts ...batch.SchedulerOption) (route.Vertex, route.Vertex, error) {
var ( var (
ctx = context.TODO()
isUpdate1 bool isUpdate1 bool
edgeNotFound bool edgeNotFound bool
from, to route.Vertex from, to route.Vertex
) )
r := &batch.Request{ r := &batch.Request[kvdb.RwTx]{
Opts: batch.NewSchedulerOptions(opts...),
Reset: func() { Reset: func() {
isUpdate1 = false isUpdate1 = false
edgeNotFound = false edgeNotFound = false
}, },
Update: func(tx kvdb.RwTx) error { Do: func(tx kvdb.RwTx) error {
var err error var err error
from, to, isUpdate1, err = updateEdgePolicy(tx, edge) from, to, isUpdate1, err = updateEdgePolicy(tx, edge)
if err != nil { if err != nil {
@@ -2761,11 +2741,7 @@ func (c *KVStore) UpdateEdgePolicy(edge *models.ChannelEdgePolicy,
}, },
} }
for _, f := range op { err := c.chanScheduler.Execute(ctx, r)
f(r)
}
err := c.chanScheduler.Execute(r)
return from, to, err return from, to, err
} }
@@ -2873,7 +2849,7 @@ func (c *KVStore) isPublic(tx kvdb.RTx, nodePub route.Vertex,
// used to terminate the check early. // used to terminate the check early.
nodeIsPublic := false nodeIsPublic := false
errDone := errors.New("done") errDone := errors.New("done")
err := c.ForEachNodeChannelTx(tx, nodePub, func(tx kvdb.RTx, err := c.forEachNodeChannelTx(tx, nodePub, func(tx kvdb.RTx,
info *models.ChannelEdgeInfo, _ *models.ChannelEdgePolicy, info *models.ChannelEdgeInfo, _ *models.ChannelEdgePolicy,
_ *models.ChannelEdgePolicy) error { _ *models.ChannelEdgePolicy) error {
@@ -3126,13 +3102,56 @@ func nodeTraversal(tx kvdb.RTx, nodePub []byte, db kvdb.Backend,
// //
// Unknown policies are passed into the callback as nil values. // Unknown policies are passed into the callback as nil values.
func (c *KVStore) ForEachNodeChannel(nodePub route.Vertex, func (c *KVStore) ForEachNodeChannel(nodePub route.Vertex,
cb func(kvdb.RTx, *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
*models.ChannelEdgePolicy) error) error { *models.ChannelEdgePolicy) error) error {
return nodeTraversal(nil, nodePub[:], c.db, cb) return nodeTraversal(nil, nodePub[:], c.db, func(_ kvdb.RTx,
info *models.ChannelEdgeInfo, policy,
policy2 *models.ChannelEdgePolicy) error {
return cb(info, policy, policy2)
})
} }
// ForEachNodeChannelTx iterates through all channels of the given node, // ForEachSourceNodeChannel iterates through all channels of the source node,
// executing the passed callback on each. The callback is provided with the
// channel's outpoint, whether we have a policy for the channel and the channel
// peer's node information.
func (c *KVStore) ForEachSourceNodeChannel(cb func(chanPoint wire.OutPoint,
havePolicy bool, otherNode *models.LightningNode) error) error {
return kvdb.View(c.db, func(tx kvdb.RTx) error {
nodes := tx.ReadBucket(nodeBucket)
if nodes == nil {
return ErrGraphNotFound
}
node, err := c.sourceNode(nodes)
if err != nil {
return err
}
return nodeTraversal(
tx, node.PubKeyBytes[:], c.db, func(tx kvdb.RTx,
info *models.ChannelEdgeInfo,
policy, _ *models.ChannelEdgePolicy) error {
peer, err := c.fetchOtherNode(
tx, info, node.PubKeyBytes[:],
)
if err != nil {
return err
}
return cb(
info.ChannelPoint, policy != nil, peer,
)
},
)
}, func() {})
}
// forEachNodeChannelTx iterates through all channels of the given node,
// executing the passed callback with an edge info structure and the policies // 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* // 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 // the connecting node, while the second is the incoming edge *from* the
@@ -3145,7 +3164,7 @@ func (c *KVStore) ForEachNodeChannel(nodePub route.Vertex,
// 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 // be nil and a fresh transaction will be created to execute the graph
// traversal. // traversal.
func (c *KVStore) ForEachNodeChannelTx(tx kvdb.RTx, func (c *KVStore) forEachNodeChannelTx(tx kvdb.RTx,
nodePub route.Vertex, cb func(kvdb.RTx, *models.ChannelEdgeInfo, nodePub route.Vertex, cb func(kvdb.RTx, *models.ChannelEdgeInfo,
*models.ChannelEdgePolicy, *models.ChannelEdgePolicy,
*models.ChannelEdgePolicy) error) error { *models.ChannelEdgePolicy) error) error {
@@ -3153,11 +3172,11 @@ func (c *KVStore) ForEachNodeChannelTx(tx kvdb.RTx,
return nodeTraversal(tx, nodePub[:], c.db, cb) return nodeTraversal(tx, nodePub[:], c.db, cb)
} }
// FetchOtherNode attempts to fetch the full LightningNode that's opposite of // fetchOtherNode attempts to fetch the full LightningNode that's opposite of
// the target node in the channel. This is useful when one knows the pubkey of // the target node in the channel. This is useful when one knows the pubkey of
// one of the nodes, and wishes to obtain the full LightningNode for the other // one of the nodes, and wishes to obtain the full LightningNode for the other
// end of the channel. // end of the channel.
func (c *KVStore) FetchOtherNode(tx kvdb.RTx, func (c *KVStore) fetchOtherNode(tx kvdb.RTx,
channel *models.ChannelEdgeInfo, thisNodeKey []byte) ( channel *models.ChannelEdgeInfo, thisNodeKey []byte) (
*models.LightningNode, error) { *models.LightningNode, error) {
@@ -4687,7 +4706,7 @@ func (c *chanGraphNodeTx) FetchNode(nodePub route.Vertex) (NodeRTx, error) {
func (c *chanGraphNodeTx) ForEachChannel(f func(*models.ChannelEdgeInfo, func (c *chanGraphNodeTx) ForEachChannel(f func(*models.ChannelEdgeInfo,
*models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error) error { *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error) error {
return c.db.ForEachNodeChannelTx(c.tx, c.node.PubKeyBytes, return c.db.forEachNodeChannelTx(c.tx, c.node.PubKeyBytes,
func(_ kvdb.RTx, info *models.ChannelEdgeInfo, policy1, func(_ kvdb.RTx, info *models.ChannelEdgeInfo, policy1,
policy2 *models.ChannelEdgePolicy) error { policy2 *models.ChannelEdgePolicy) error {
@@ -4696,40 +4715,31 @@ func (c *chanGraphNodeTx) ForEachChannel(f func(*models.ChannelEdgeInfo,
) )
} }
// MakeTestGraph creates a new instance of the KVStore for testing // MakeTestGraph creates a new instance of the ChannelGraph for testing
// purposes. // purposes.
func MakeTestGraph(t testing.TB, modifiers ...KVStoreOptionModifier) ( //
*ChannelGraph, error) { // NOTE: this helper currently creates a ChannelGraph that is only ever backed
// by the `KVStore` of the `V1Store` interface.
opts := DefaultOptions() func MakeTestGraph(t testing.TB, opts ...ChanGraphOption) *ChannelGraph {
for _, modifier := range modifiers { t.Helper()
modifier(opts)
}
// Next, create KVStore for the first time. // Next, create KVStore for the first time.
backend, backendCleanup, err := kvdb.GetTestBackend(t.TempDir(), "cgr") backend, backendCleanup, err := kvdb.GetTestBackend(t.TempDir(), "cgr")
if err != nil { t.Cleanup(backendCleanup)
backendCleanup() require.NoError(t, err)
t.Cleanup(func() {
return nil, err require.NoError(t, backend.Close())
} })
graph, err := NewChannelGraph(&Config{ graphStore, err := NewKVStore(backend)
KVDB: backend, require.NoError(t, err)
KVStoreOpts: modifiers,
}) graph, err := NewChannelGraph(graphStore, opts...)
if err != nil { require.NoError(t, err)
backendCleanup() require.NoError(t, graph.Start())
return nil, err
}
require.NoError(t, graph.Start())
t.Cleanup(func() { t.Cleanup(func() {
_ = backend.Close()
backendCleanup()
require.NoError(t, graph.Stop()) require.NoError(t, graph.Stop())
}) })
return graph, nil return graph
} }
+45
View File
@@ -0,0 +1,45 @@
package graphdb
import (
"github.com/lightningnetwork/lnd/sqldb"
)
// SQLQueries is a subset of the sqlc.Querier interface that can be used to
// execute queries against the SQL graph tables.
type SQLQueries interface {
}
// BatchedSQLQueries is a version of SQLQueries that's capable of batched
// database operations.
type BatchedSQLQueries interface {
SQLQueries
sqldb.BatchedTx[SQLQueries]
}
// SQLStore is an implementation of the V1Store interface that uses a SQL
// database as the backend.
//
// NOTE: currently, this temporarily embeds the KVStore struct so that we can
// implement the V1Store interface incrementally. For any method not
// implemented, things will fall back to the KVStore. This is ONLY the case
// for the time being while this struct is purely used in unit tests only.
type SQLStore struct {
db BatchedSQLQueries
// Temporary fall-back to the KVStore so that we can implement the
// interface incrementally.
*KVStore
}
// A compile-time assertion to ensure that SQLStore implements the V1Store
// interface.
var _ V1Store = (*SQLStore)(nil)
// NewSQLStore creates a new SQLStore instance given an open BatchedSQLQueries
// storage backend.
func NewSQLStore(db BatchedSQLQueries, kvStore *KVStore) *SQLStore {
return &SQLStore{
db: db,
KVStore: kvStore,
}
}
+23
View File
@@ -0,0 +1,23 @@
//go:build !test_db_sqlite && !test_db_postgres
package graphdb
import (
"testing"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/stretchr/testify/require"
)
// NewTestDB is a helper function that creates an BBolt database for testing.
func NewTestDB(t testing.TB) *KVStore {
backend, backendCleanup, err := kvdb.GetTestBackend(t.TempDir(), "cgr")
require.NoError(t, err)
t.Cleanup(backendCleanup)
graphStore, err := NewKVStore(backend)
require.NoError(t, err)
return graphStore
}
+42
View File
@@ -0,0 +1,42 @@
//go:build test_db_postgres && !test_db_sqlite
package graphdb
import (
"database/sql"
"testing"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/sqldb"
"github.com/stretchr/testify/require"
)
// NewTestDB is a helper function that creates a SQLStore backed by a postgres
// database for testing. At the moment, it embeds a KVStore but once the
// SQLStore fully implements the V1Store interface, the KVStore will be removed.
func NewTestDB(t testing.TB) *SQLStore {
backend, backendCleanup, err := kvdb.GetTestBackend(t.TempDir(), "cgr")
require.NoError(t, err)
t.Cleanup(backendCleanup)
graphStore, err := NewKVStore(backend)
require.NoError(t, err)
pgFixture := sqldb.NewTestPgFixture(
t, sqldb.DefaultPostgresFixtureLifetime,
)
t.Cleanup(func() {
pgFixture.TearDown(t)
})
db := sqldb.NewTestPostgresDB(t, pgFixture).BaseDB
executor := sqldb.NewTransactionExecutor(
db, func(tx *sql.Tx) SQLQueries {
return db.WithTx(tx)
},
)
return NewSQLStore(executor, graphStore)
}
+35
View File
@@ -0,0 +1,35 @@
//go:build !test_db_posgres && test_db_sqlite
package graphdb
import (
"database/sql"
"testing"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/sqldb"
"github.com/stretchr/testify/require"
)
// NewTestDB is a helper function that creates a SQLStore backed by a sqlite
// database for testing. At the moment, it embeds a KVStore but once the
// SQLStore fully implements the V1Store interface, the KVStore will be removed.
func NewTestDB(t testing.TB) *SQLStore {
backend, backendCleanup, err := kvdb.GetTestBackend(t.TempDir(), "cgr")
require.NoError(t, err)
t.Cleanup(backendCleanup)
graphStore, err := NewKVStore(backend)
require.NoError(t, err)
db := sqldb.NewTestSqliteDB(t).BaseDB
executor := sqldb.NewTransactionExecutor(
db, func(tx *sql.Tx) SQLQueries {
return db.WithTx(tx)
},
)
return NewSQLStore(executor, graphStore)
}
+3 -5
View File
@@ -8,7 +8,6 @@ import (
"github.com/lightningnetwork/lnd/batch" "github.com/lightningnetwork/lnd/batch"
graphdb "github.com/lightningnetwork/lnd/graph/db" graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/route"
) )
@@ -254,10 +253,9 @@ type DB interface {
// to the caller. // to the caller.
// //
// Unknown policies are passed into the callback as nil values. // Unknown policies are passed into the callback as nil values.
ForEachNodeChannel(nodePub route.Vertex, cb func(kvdb.RTx, ForEachNodeChannel(nodePub route.Vertex,
*models.ChannelEdgeInfo, cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
*models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error) error
*models.ChannelEdgePolicy) error) error
// AddEdgeProof sets the proof of an existing edge in the graph // AddEdgeProof sets the proof of an existing edge in the graph
// database. // database.
+4 -37
View File
@@ -22,7 +22,6 @@ import (
"github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/kvdb"
lnmock "github.com/lightningnetwork/lnd/lntest/mock" lnmock "github.com/lightningnetwork/lnd/lntest/mock"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
@@ -89,7 +88,7 @@ func createTestNode(t *testing.T) *models.LightningNode {
LastUpdate: time.Unix(updateTime, 0), LastUpdate: time.Unix(updateTime, 0),
Addresses: testAddrs, Addresses: testAddrs,
Color: color.RGBA{1, 2, 3, 0}, Color: color.RGBA{1, 2, 3, 0},
Alias: "kek" + string(pub[:]), Alias: "kek" + hex.EncodeToString(pub),
AuthSigBytes: testSig.Serialize(), AuthSigBytes: testSig.Serialize(),
Features: testFeatures, Features: testFeatures,
} }
@@ -1034,9 +1033,7 @@ type testCtx struct {
func createTestCtxSingleNode(t *testing.T, func createTestCtxSingleNode(t *testing.T,
startingHeight uint32) *testCtx { startingHeight uint32) *testCtx {
graph, graphBackend, err := makeTestGraph(t, true) graph := graphdb.MakeTestGraph(t)
require.NoError(t, err, "failed to make test graph")
sourceNode := createTestNode(t) sourceNode := createTestNode(t)
require.NoError(t, require.NoError(t,
@@ -1044,8 +1041,7 @@ func createTestCtxSingleNode(t *testing.T,
) )
graphInstance := &testGraphInstance{ graphInstance := &testGraphInstance{
graph: graph, graph: graph,
graphBackend: graphBackend,
} }
return createTestCtxFromGraphInstance( return createTestCtxFromGraphInstance(
@@ -1084,37 +1080,8 @@ func (c *testCtx) RestartBuilder(t *testing.T) {
c.builder = builder c.builder = builder
} }
// makeTestGraph creates a new instance of a channeldb.ChannelGraph for testing
// purposes.
func makeTestGraph(t *testing.T, useCache bool) (*graphdb.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)
graph, err := graphdb.NewChannelGraph(
&graphdb.Config{KVDB: backend},
graphdb.WithUseGraphCache(useCache),
)
if err != nil {
return nil, nil, err
}
require.NoError(t, graph.Start())
t.Cleanup(func() {
require.NoError(t, graph.Stop())
})
return graph, backend, nil
}
type testGraphInstance struct { type testGraphInstance struct {
graph *graphdb.ChannelGraph graph *graphdb.ChannelGraph
graphBackend kvdb.Backend
// aliasMap is a map from a node's alias to its public key. This type is // 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 // provided in order to allow easily look up from the human memorable
+1 -1
View File
@@ -274,7 +274,7 @@ func GetTestBackend(path, name string) (Backend, func(), error) {
return nil, empty, err return nil, empty, err
} }
backend, err := Open( backend, err := Open(
EtcdBackendName, context.TODO(), etcdConfig, EtcdBackendName, context.Background(), etcdConfig,
) )
return backend, cancel, err return backend, cancel, err
+1 -1
View File
@@ -19,7 +19,7 @@ func TestDump(t *testing.T) {
f := NewEtcdTestFixture(t) f := NewEtcdTestFixture(t)
db, err := newEtcdBackend(context.TODO(), f.BackendConfig()) db, err := newEtcdBackend(context.Background(), f.BackendConfig())
require.NoError(t, err) require.NoError(t, err)
err = db.Update(func(tx walletdb.ReadWriteTx) error { err = db.Update(func(tx walletdb.ReadWriteTx) error {
+10 -4
View File
@@ -76,7 +76,7 @@ func (f *EtcdTestFixture) NewBackend(singleWriter bool) walletdb.DB {
cfg.SingleWriter = true cfg.SingleWriter = true
} }
db, err := newEtcdBackend(context.TODO(), cfg) db, err := newEtcdBackend(context.Background(), cfg)
require.NoError(f.t, err) require.NoError(f.t, err)
return db return db
@@ -84,7 +84,9 @@ func (f *EtcdTestFixture) NewBackend(singleWriter bool) walletdb.DB {
// Put puts a string key/value into the test etcd database. // Put puts a string key/value into the test etcd database.
func (f *EtcdTestFixture) Put(key, value string) { func (f *EtcdTestFixture) Put(key, value string) {
ctx, cancel := context.WithTimeout(context.TODO(), testEtcdTimeout) ctx, cancel := context.WithTimeout(
context.Background(), testEtcdTimeout,
)
defer cancel() defer cancel()
_, err := f.cli.Put(ctx, key, value) _, err := f.cli.Put(ctx, key, value)
@@ -95,7 +97,9 @@ func (f *EtcdTestFixture) Put(key, value string) {
// Get queries a key and returns the stored value from the test etcd database. // Get queries a key and returns the stored value from the test etcd database.
func (f *EtcdTestFixture) Get(key string) string { func (f *EtcdTestFixture) Get(key string) string {
ctx, cancel := context.WithTimeout(context.TODO(), testEtcdTimeout) ctx, cancel := context.WithTimeout(
context.Background(), testEtcdTimeout,
)
defer cancel() defer cancel()
resp, err := f.cli.Get(ctx, key) resp, err := f.cli.Get(ctx, key)
@@ -112,7 +116,9 @@ func (f *EtcdTestFixture) Get(key string) string {
// Dump scans and returns all key/values from the test etcd database. // Dump scans and returns all key/values from the test etcd database.
func (f *EtcdTestFixture) Dump() map[string]string { func (f *EtcdTestFixture) Dump() map[string]string {
ctx, cancel := context.WithTimeout(context.TODO(), testEtcdTimeout) ctx, cancel := context.WithTimeout(
context.Background(), testEtcdTimeout,
)
defer cancel() defer cancel()
resp, err := f.cli.Get(ctx, "\x00", clientv3.WithFromKey()) resp, err := f.cli.Get(ctx, "\x00", clientv3.WithFromKey())
+2 -2
View File
@@ -16,7 +16,7 @@ func TestChangeDuringManualTx(t *testing.T) {
f := NewEtcdTestFixture(t) f := NewEtcdTestFixture(t)
db, err := newEtcdBackend(context.TODO(), f.BackendConfig()) db, err := newEtcdBackend(context.Background(), f.BackendConfig())
require.NoError(t, err) require.NoError(t, err)
tx, err := db.BeginReadWriteTx() tx, err := db.BeginReadWriteTx()
@@ -44,7 +44,7 @@ func TestChangeDuringUpdate(t *testing.T) {
f := NewEtcdTestFixture(t) f := NewEtcdTestFixture(t)
db, err := newEtcdBackend(context.TODO(), f.BackendConfig()) db, err := newEtcdBackend(context.Background(), f.BackendConfig())
require.NoError(t, err) require.NoError(t, err)
count := 0 count := 0
+1 -1
View File
@@ -15,5 +15,5 @@ import (
func TestWalletDBInterface(t *testing.T) { func TestWalletDBInterface(t *testing.T) {
f := NewEtcdTestFixture(t) f := NewEtcdTestFixture(t)
cfg := f.BackendConfig() cfg := f.BackendConfig()
walletdbtest.TestInterface(t, dbType, context.TODO(), &cfg) walletdbtest.TestInterface(t, dbType, context.Background(), &cfg)
} }
+2 -2
View File
@@ -626,7 +626,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg,
// Set up the core server which will listen for incoming peer // Set up the core server which will listen for incoming peer
// connections. // connections.
server, err := newServer( server, err := newServer(
cfg, cfg.Listeners, dbs, activeChainControl, &idKeyDesc, ctx, cfg, cfg.Listeners, dbs, activeChainControl, &idKeyDesc,
activeChainControl.Cfg.WalletUnlockParams.ChansToRestore, activeChainControl.Cfg.WalletUnlockParams.ChansToRestore,
multiAcceptor, torController, tlsManager, leaderElector, multiAcceptor, torController, tlsManager, leaderElector,
implCfg, implCfg,
@@ -758,7 +758,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg,
// case the startup of the subservers do not behave as expected. // case the startup of the subservers do not behave as expected.
errChan := make(chan error) errChan := make(chan error)
go func() { go func() {
errChan <- server.Start() errChan <- server.Start(ctx)
}() }()
defer func() { defer func() {
+2 -2
View File
@@ -198,7 +198,7 @@ func (s *Server) Status(ctx context.Context,
// ModifyStatus activates the current autopilot agent, if active. // ModifyStatus activates the current autopilot agent, if active.
// //
// NOTE: Part of the AutopilotServer interface. // NOTE: Part of the AutopilotServer interface.
func (s *Server) ModifyStatus(ctx context.Context, func (s *Server) ModifyStatus(_ context.Context,
in *ModifyStatusRequest) (*ModifyStatusResponse, error) { in *ModifyStatusRequest) (*ModifyStatusResponse, error) {
log.Debugf("Setting agent enabled=%v", in.Enable) log.Debugf("Setting agent enabled=%v", in.Enable)
@@ -236,7 +236,7 @@ func (s *Server) QueryScores(ctx context.Context, in *QueryScoresRequest) (
// Query the heuristics. // Query the heuristics.
heuristicScores, err := s.manager.QueryHeuristics( heuristicScores, err := s.manager.QueryHeuristics(
nodes, !in.IgnoreLocalState, ctx, nodes, !in.IgnoreLocalState,
) )
if err != nil { if err != nil {
return nil, err return nil, err
+7 -2
View File
@@ -70,8 +70,8 @@ var _ SizeableMessage = (*ChannelAnnouncement1)(nil)
// io.Reader observing the specified protocol version. // io.Reader observing the specified protocol version.
// //
// This is part of the lnwire.Message interface. // This is part of the lnwire.Message interface.
func (a *ChannelAnnouncement1) Decode(r io.Reader, pver uint32) error { func (a *ChannelAnnouncement1) Decode(r io.Reader, _ uint32) error {
return ReadElements(r, err := ReadElements(r,
&a.NodeSig1, &a.NodeSig1,
&a.NodeSig2, &a.NodeSig2,
&a.BitcoinSig1, &a.BitcoinSig1,
@@ -85,6 +85,11 @@ func (a *ChannelAnnouncement1) Decode(r io.Reader, pver uint32) error {
&a.BitcoinKey2, &a.BitcoinKey2,
&a.ExtraOpaqueData, &a.ExtraOpaqueData,
) )
if err != nil {
return err
}
return a.ExtraOpaqueData.ValidateTLV()
} }
// Encode serializes the target ChannelAnnouncement into the passed io.Writer // Encode serializes the target ChannelAnnouncement into the passed io.Writer
+1 -1
View File
@@ -126,7 +126,7 @@ func (c *ChannelAnnouncement2) DecodeTLVRecords(r io.Reader) error {
c.ExtraOpaqueData = tlvRecords c.ExtraOpaqueData = tlvRecords
} }
return nil return c.ExtraOpaqueData.ValidateTLV()
} }
// Encode serializes the target AnnounceSignatures1 into the passed io.Writer // Encode serializes the target AnnounceSignatures1 into the passed io.Writer
+7 -2
View File
@@ -132,7 +132,7 @@ var _ SizeableMessage = (*ChannelUpdate1)(nil)
// io.Reader observing the specified protocol version. // io.Reader observing the specified protocol version.
// //
// This is part of the lnwire.Message interface. // This is part of the lnwire.Message interface.
func (a *ChannelUpdate1) Decode(r io.Reader, pver uint32) error { func (a *ChannelUpdate1) Decode(r io.Reader, _ uint32) error {
err := ReadElements(r, err := ReadElements(r,
&a.Signature, &a.Signature,
a.ChainHash[:], a.ChainHash[:],
@@ -156,7 +156,12 @@ func (a *ChannelUpdate1) Decode(r io.Reader, pver uint32) error {
} }
} }
return a.ExtraOpaqueData.Decode(r) err = a.ExtraOpaqueData.Decode(r)
if err != nil {
return err
}
return a.ExtraOpaqueData.ValidateTLV()
} }
// Encode serializes the target ChannelUpdate into the passed io.Writer // Encode serializes the target ChannelUpdate into the passed io.Writer
+1 -1
View File
@@ -154,7 +154,7 @@ func (c *ChannelUpdate2) DecodeTLVRecords(r io.Reader) error {
c.ExtraOpaqueData = tlvRecords c.ExtraOpaqueData = tlvRecords
} }
return nil return c.ExtraOpaqueData.ValidateTLV()
} }
// Encode serializes the target ChannelUpdate2 into the passed io.Writer // Encode serializes the target ChannelUpdate2 into the passed io.Writer
+22
View File
@@ -63,6 +63,28 @@ func (e *ExtraOpaqueData) Decode(r io.Reader) error {
return nil return nil
} }
// ValidateTLV checks that the raw bytes that make up the ExtraOpaqueData
// instance are a valid TLV stream.
func (e *ExtraOpaqueData) ValidateTLV() error {
// There is nothing to validate if the ExtraOpaqueData is nil or empty.
if e == nil || len(*e) == 0 {
return nil
}
tlvStream, err := tlv.NewStream()
if err != nil {
return err
}
// Ensure that the TLV stream is valid by attempting to decode it.
_, err = tlvStream.DecodeWithParsedTypesP2P(bytes.NewReader(*e))
if err != nil {
return fmt.Errorf("invalid TLV stream: %w: %v", err, *e)
}
return nil
}
// PackRecords attempts to encode the set of tlv records into the target // PackRecords attempts to encode the set of tlv records into the target
// ExtraOpaqueData instance. The records will be encoded as a raw TLV stream // ExtraOpaqueData instance. The records will be encoded as a raw TLV stream
// and stored within the backing slice pointer. // and stored within the backing slice pointer.
+7 -2
View File
@@ -112,8 +112,8 @@ var _ SizeableMessage = (*NodeAnnouncement)(nil)
// io.Reader observing the specified protocol version. // io.Reader observing the specified protocol version.
// //
// This is part of the lnwire.Message interface. // This is part of the lnwire.Message interface.
func (a *NodeAnnouncement) Decode(r io.Reader, pver uint32) error { func (a *NodeAnnouncement) Decode(r io.Reader, _ uint32) error {
return ReadElements(r, err := ReadElements(r,
&a.Signature, &a.Signature,
&a.Features, &a.Features,
&a.Timestamp, &a.Timestamp,
@@ -123,6 +123,11 @@ func (a *NodeAnnouncement) Decode(r io.Reader, pver uint32) error {
&a.Addresses, &a.Addresses,
&a.ExtraOpaqueData, &a.ExtraOpaqueData,
) )
if err != nil {
return err
}
return a.ExtraOpaqueData.ValidateTLV()
} }
// Encode serializes the target NodeAnnouncement into the passed io.Writer // Encode serializes the target NodeAnnouncement into the passed io.Writer
+9 -6
View File
@@ -55,6 +55,7 @@ func setupTestRootKeyStorage(t *testing.T) kvdb.Backend {
// TestNewService tests the creation of the macaroon service. // TestNewService tests the creation of the macaroon service.
func TestNewService(t *testing.T) { func TestNewService(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
// First, initialize a dummy DB file with a store that the service // First, initialize a dummy DB file with a store that the service
// can read from. Make sure the file is removed in the end. // can read from. Make sure the file is removed in the end.
@@ -74,13 +75,13 @@ func TestNewService(t *testing.T) {
require.NoError(t, err, "Error unlocking root key storage") require.NoError(t, err, "Error unlocking root key storage")
// Third, check if the created service can bake macaroons. // Third, check if the created service can bake macaroons.
_, err = service.NewMacaroon(context.TODO(), nil, testOperation) _, err = service.NewMacaroon(ctx, nil, testOperation)
if err != macaroons.ErrMissingRootKeyID { if err != macaroons.ErrMissingRootKeyID {
t.Fatalf("Received %v instead of ErrMissingRootKeyID", err) t.Fatalf("Received %v instead of ErrMissingRootKeyID", err)
} }
macaroon, err := service.NewMacaroon( macaroon, err := service.NewMacaroon(
context.TODO(), macaroons.DefaultRootKeyID, testOperation, ctx, macaroons.DefaultRootKeyID, testOperation,
) )
require.NoError(t, err, "Error creating macaroon from service") require.NoError(t, err, "Error creating macaroon from service")
if macaroon.Namespace().String() != "std:" { if macaroon.Namespace().String() != "std:" {
@@ -108,6 +109,7 @@ func TestNewService(t *testing.T) {
// incoming context. // incoming context.
func TestValidateMacaroon(t *testing.T) { func TestValidateMacaroon(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
// First, initialize the service and unlock it. // First, initialize the service and unlock it.
db := setupTestRootKeyStorage(t) db := setupTestRootKeyStorage(t)
@@ -124,7 +126,7 @@ func TestValidateMacaroon(t *testing.T) {
// Then, create a new macaroon that we can serialize. // Then, create a new macaroon that we can serialize.
macaroon, err := service.NewMacaroon( macaroon, err := service.NewMacaroon(
context.TODO(), macaroons.DefaultRootKeyID, testOperation, ctx, macaroons.DefaultRootKeyID, testOperation,
testOperationURI, testOperationURI,
) )
require.NoError(t, err, "Error creating macaroon from service") require.NoError(t, err, "Error creating macaroon from service")
@@ -136,7 +138,7 @@ func TestValidateMacaroon(t *testing.T) {
md := metadata.New(map[string]string{ md := metadata.New(map[string]string{
"macaroon": hex.EncodeToString(macaroonBinary), "macaroon": hex.EncodeToString(macaroonBinary),
}) })
mockContext := metadata.NewIncomingContext(context.Background(), md) mockContext := metadata.NewIncomingContext(ctx, md)
// Finally, validate the macaroon against the required permissions. // Finally, validate the macaroon against the required permissions.
err = service.ValidateMacaroon( err = service.ValidateMacaroon(
@@ -155,6 +157,7 @@ func TestValidateMacaroon(t *testing.T) {
// TestListMacaroonIDs checks that ListMacaroonIDs returns the expected result. // TestListMacaroonIDs checks that ListMacaroonIDs returns the expected result.
func TestListMacaroonIDs(t *testing.T) { func TestListMacaroonIDs(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
// First, initialize a dummy DB file with a store that the service // First, initialize a dummy DB file with a store that the service
// can read from. Make sure the file is removed in the end. // can read from. Make sure the file is removed in the end.
@@ -176,12 +179,12 @@ func TestListMacaroonIDs(t *testing.T) {
// Third, make 3 new macaroons with different root key IDs. // Third, make 3 new macaroons with different root key IDs.
expectedIDs := [][]byte{{1}, {2}, {3}} expectedIDs := [][]byte{{1}, {2}, {3}}
for _, v := range expectedIDs { for _, v := range expectedIDs {
_, err := service.NewMacaroon(context.TODO(), v, testOperation) _, err := service.NewMacaroon(ctx, v, testOperation)
require.NoError(t, err, "Error creating macaroon from service") require.NoError(t, err, "Error creating macaroon from service")
} }
// Finally, check that calling List return the expected values. // Finally, check that calling List return the expected values.
ids, _ := service.ListMacaroonIDs(context.TODO()) ids, _ := service.ListMacaroonIDs(ctx)
require.Equal(t, expectedIDs, ids, "root key IDs mismatch") require.Equal(t, expectedIDs, ids, "root key IDs mismatch")
} }
+8 -5
View File
@@ -58,12 +58,15 @@ func openTestStore(t *testing.T, tempDir string) *macaroons.RootKeyStorage {
// TestStore tests the normal use cases of the store like creating, unlocking, // TestStore tests the normal use cases of the store like creating, unlocking,
// reading keys and closing it. // reading keys and closing it.
func TestStore(t *testing.T) { func TestStore(t *testing.T) {
t.Parallel()
ctx := context.Background()
tempDir, store := newTestStore(t) tempDir, store := newTestStore(t)
_, _, err := store.RootKey(context.TODO()) _, _, err := store.RootKey(ctx)
require.Equal(t, macaroons.ErrStoreLocked, err) require.Equal(t, macaroons.ErrStoreLocked, err)
_, err = store.Get(context.TODO(), nil) _, err = store.Get(ctx, nil)
require.Equal(t, macaroons.ErrStoreLocked, err) require.Equal(t, macaroons.ErrStoreLocked, err)
pw := []byte("weks") pw := []byte("weks")
@@ -72,18 +75,18 @@ func TestStore(t *testing.T) {
// Check ErrContextRootKeyID is returned when no root key ID found in // Check ErrContextRootKeyID is returned when no root key ID found in
// context. // context.
_, _, err = store.RootKey(context.TODO()) _, _, err = store.RootKey(ctx)
require.Equal(t, macaroons.ErrContextRootKeyID, err) require.Equal(t, macaroons.ErrContextRootKeyID, err)
// Check ErrMissingRootKeyID is returned when empty root key ID is used. // Check ErrMissingRootKeyID is returned when empty root key ID is used.
emptyKeyID := make([]byte, 0) emptyKeyID := make([]byte, 0)
badCtx := macaroons.ContextWithRootKeyID(context.TODO(), emptyKeyID) badCtx := macaroons.ContextWithRootKeyID(ctx, emptyKeyID)
_, _, err = store.RootKey(badCtx) _, _, err = store.RootKey(badCtx)
require.Equal(t, macaroons.ErrMissingRootKeyID, err) require.Equal(t, macaroons.ErrMissingRootKeyID, err)
// Create a context with illegal root key ID value. // Create a context with illegal root key ID value.
encryptedKeyID := []byte("enckey") encryptedKeyID := []byte("enckey")
badCtx = macaroons.ContextWithRootKeyID(context.TODO(), encryptedKeyID) badCtx = macaroons.ContextWithRootKeyID(ctx, encryptedKeyID)
_, _, err = store.RootKey(badCtx) _, _, err = store.RootKey(badCtx)
require.Equal(t, macaroons.ErrKeyValueForbidden, err) require.Equal(t, macaroons.ErrKeyValueForbidden, err)
+5 -1
View File
@@ -1989,9 +1989,13 @@ func newChanMsgStream(p *Brontide, cid lnwire.ChannelID) *msgStream {
// channel announcements. // channel announcements.
func newDiscMsgStream(p *Brontide) *msgStream { func newDiscMsgStream(p *Brontide) *msgStream {
apply := func(msg lnwire.Message) { apply := func(msg lnwire.Message) {
// TODO(elle): thread contexts through the peer system properly
// so that a parent context can be passed in here.
ctx := context.TODO()
// TODO(yy): `ProcessRemoteAnnouncement` returns an error chan // TODO(yy): `ProcessRemoteAnnouncement` returns an error chan
// and we need to process it. // and we need to process it.
p.cfg.AuthGossiper.ProcessRemoteAnnouncement(msg, p) p.cfg.AuthGossiper.ProcessRemoteAnnouncement(ctx, msg, p)
} }
return newMsgStream( return newMsgStream(
+11
View File
@@ -0,0 +1,11 @@
package peer
import (
"testing"
"github.com/lightningnetwork/lnd/kvdb"
)
func TestMain(m *testing.M) {
kvdb.RunTests(m)
}
+2 -18
View File
@@ -23,7 +23,6 @@ import (
"github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntest/channels" "github.com/lightningnetwork/lnd/lntest/channels"
"github.com/lightningnetwork/lnd/lntest/mock" "github.com/lightningnetwork/lnd/lntest/mock"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
@@ -603,28 +602,13 @@ func createTestPeer(t *testing.T) *peerTestCtx {
const chanActiveTimeout = time.Minute const chanActiveTimeout = time.Minute
dbPath := t.TempDir() dbAliceGraph := graphdb.MakeTestGraph(t)
graphBackend, err := kvdb.GetBoltBackend(&kvdb.BoltBackendConfig{
DBPath: dbPath,
DBFileName: "graph.db",
NoFreelistSync: true,
AutoCompact: false,
AutoCompactMinAge: kvdb.DefaultBoltAutoCompactMinAge,
DBTimeout: kvdb.DefaultDBTimeout,
})
require.NoError(t, err)
dbAliceGraph, err := graphdb.NewChannelGraph(&graphdb.Config{
KVDB: graphBackend,
})
require.NoError(t, err)
require.NoError(t, dbAliceGraph.Start()) require.NoError(t, dbAliceGraph.Start())
t.Cleanup(func() { t.Cleanup(func() {
require.NoError(t, dbAliceGraph.Stop()) require.NoError(t, dbAliceGraph.Stop())
}) })
dbAliceChannel := channeldb.OpenForTesting(t, dbPath) dbAliceChannel := channeldb.OpenForTesting(t, t.TempDir())
nodeSignerAlice := netann.NewNodeSigner(aliceKeySigner) nodeSignerAlice := netann.NewNodeSigner(aliceKeySigner)
+24 -29
View File
@@ -159,26 +159,21 @@ func makeTestGraph(t *testing.T, useCache bool) (*graphdb.ChannelGraph,
kvdb.Backend, error) { kvdb.Backend, error) {
// Create channelgraph for the first time. // Create channelgraph for the first time.
backend, backendCleanup, err := kvdb.GetTestBackend(t.TempDir(), "cgr") graph := graphdb.MakeTestGraph(t, graphdb.WithUseGraphCache(useCache))
if err != nil {
return nil, nil, err
}
t.Cleanup(backendCleanup)
graph, err := graphdb.NewChannelGraph(
&graphdb.Config{KVDB: backend},
graphdb.WithUseGraphCache(useCache),
)
if err != nil {
return nil, nil, err
}
require.NoError(t, graph.Start()) require.NoError(t, graph.Start())
t.Cleanup(func() { t.Cleanup(func() {
require.NoError(t, graph.Stop()) require.NoError(t, graph.Stop())
}) })
return graph, backend, nil mcBackend, backendCleanup, err := kvdb.GetTestBackend(
t.TempDir(), "mission_control",
)
if err != nil {
return nil, nil, err
}
t.Cleanup(backendCleanup)
return graph, mcBackend, nil
} }
// parseTestGraph returns a fully populated ChannelGraph given a path to a JSON // parseTestGraph returns a fully populated ChannelGraph given a path to a JSON
@@ -210,7 +205,7 @@ func parseTestGraph(t *testing.T, useCache bool, path string) (
testAddrs = append(testAddrs, testAddr) testAddrs = append(testAddrs, testAddr)
// Next, create a temporary graph database for usage within the test. // Next, create a temporary graph database for usage within the test.
graph, graphBackend, err := makeTestGraph(t, useCache) graph, mcBackend, err := makeTestGraph(t, useCache)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -410,12 +405,12 @@ func parseTestGraph(t *testing.T, useCache bool, path string) (
} }
return &testGraphInstance{ return &testGraphInstance{
graph: graph, graph: graph,
graphBackend: graphBackend, mcBackend: mcBackend,
aliasMap: aliasMap, aliasMap: aliasMap,
privKeyMap: privKeyMap, privKeyMap: privKeyMap,
channelIDs: channelIDs, channelIDs: channelIDs,
links: links, links: links,
}, nil }, nil
} }
@@ -479,8 +474,8 @@ type testChannel struct {
} }
type testGraphInstance struct { type testGraphInstance struct {
graph *graphdb.ChannelGraph graph *graphdb.ChannelGraph
graphBackend kvdb.Backend mcBackend kvdb.Backend
// aliasMap is a map from a node's alias to its public key. This type is // 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 // provided in order to allow easily look up from the human memorable alias
@@ -747,11 +742,11 @@ func createTestGraphFromChannels(t *testing.T, useCache bool,
} }
return &testGraphInstance{ return &testGraphInstance{
graph: graph, graph: graph,
graphBackend: graphBackend, mcBackend: graphBackend,
aliasMap: aliasMap, aliasMap: aliasMap,
privKeyMap: privKeyMap, privKeyMap: privKeyMap,
links: links, links: links,
}, nil }, nil
} }
+1 -1
View File
@@ -123,7 +123,7 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T,
mcConfig := &MissionControlConfig{Estimator: estimator} mcConfig := &MissionControlConfig{Estimator: estimator}
mcController, err := NewMissionController( mcController, err := NewMissionController(
graphInstance.graphBackend, route.Vertex{}, mcConfig, graphInstance.mcBackend, route.Vertex{}, mcConfig,
) )
require.NoError(t, err, "failed to create missioncontrol") require.NoError(t, err, "failed to create missioncontrol")
+4 -5
View File
@@ -56,7 +56,6 @@ import (
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/labels" "github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
@@ -6857,7 +6856,7 @@ func (r *rpcServer) GetNodeMetrics(ctx context.Context,
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := centralityMetric.Refresh(channelGraph); err != nil { if err := centralityMetric.Refresh(ctx, channelGraph); err != nil {
return nil, err return nil, err
} }
@@ -6959,7 +6958,7 @@ func (r *rpcServer) GetNodeInfo(ctx context.Context,
) )
err = graph.ForEachNodeChannel(node.PubKeyBytes, err = graph.ForEachNodeChannel(node.PubKeyBytes,
func(_ kvdb.RTx, edge *models.ChannelEdgeInfo, func(edge *models.ChannelEdgeInfo,
c1, c2 *models.ChannelEdgePolicy) error { c1, c2 *models.ChannelEdgePolicy) error {
numChannels++ numChannels++
@@ -7140,7 +7139,7 @@ func (r *rpcServer) GetNetworkInfo(ctx context.Context,
// Graph diameter. // Graph diameter.
channelGraph := autopilot.ChannelGraphFromCachedDatabase(graph) channelGraph := autopilot.ChannelGraphFromCachedDatabase(graph)
simpleGraph, err := autopilot.NewSimpleGraph(channelGraph) simpleGraph, err := autopilot.NewSimpleGraph(ctx, channelGraph)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -7641,7 +7640,7 @@ func (r *rpcServer) FeeReport(ctx context.Context,
var feeReports []*lnrpc.ChannelFeeReport var feeReports []*lnrpc.ChannelFeeReport
err = channelGraph.ForEachNodeChannel(selfNode.PubKeyBytes, err = channelGraph.ForEachNodeChannel(selfNode.PubKeyBytes,
func(_ kvdb.RTx, chanInfo *models.ChannelEdgeInfo, func(chanInfo *models.ChannelEdgeInfo,
edgePolicy, _ *models.ChannelEdgePolicy) error { edgePolicy, _ *models.ChannelEdgePolicy) error {
// Self node should always have policies for its // Self node should always have policies for its
+20 -36
View File
@@ -51,7 +51,6 @@ import (
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnencrypt" "github.com/lightningnetwork/lnd/lnencrypt"
"github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnpeer"
@@ -554,7 +553,9 @@ func noiseDial(idKey keychain.SingleKeyECDH,
// newServer creates a new instance of the server which is to listen using the // newServer creates a new instance of the server which is to listen using the
// passed listener address. // passed listener address.
func newServer(cfg *Config, listenAddrs []net.Addr, //
//nolint:funlen
func newServer(_ context.Context, cfg *Config, listenAddrs []net.Addr,
dbs *DatabaseInstances, cc *chainreg.ChainControl, dbs *DatabaseInstances, cc *chainreg.ChainControl,
nodeKeyDesc *keychain.KeyDescriptor, nodeKeyDesc *keychain.KeyDescriptor,
chansToRestore walletunlocker.ChannelsToRecover, chansToRestore walletunlocker.ChannelsToRecover,
@@ -1210,7 +1211,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
*models.ChannelEdgePolicy) error) error { *models.ChannelEdgePolicy) error) error {
return s.graphDB.ForEachNodeChannel(selfVertex, return s.graphDB.ForEachNodeChannel(selfVertex,
func(_ kvdb.RTx, c *models.ChannelEdgeInfo, func(c *models.ChannelEdgeInfo,
e *models.ChannelEdgePolicy, e *models.ChannelEdgePolicy,
_ *models.ChannelEdgePolicy) error { _ *models.ChannelEdgePolicy) error {
@@ -2217,7 +2218,7 @@ func (s *server) startLowLevelServices() error {
// NOTE: This function is safe for concurrent access. // NOTE: This function is safe for concurrent access.
// //
//nolint:funlen //nolint:funlen
func (s *server) Start() error { func (s *server) Start(ctx context.Context) error {
// Get the current blockbeat. // Get the current blockbeat.
beat, err := s.getStartingBeat() beat, err := s.getStartingBeat()
if err != nil { if err != nil {
@@ -2626,7 +2627,9 @@ func (s *server) Start() error {
} }
s.wg.Add(1) s.wg.Add(1)
go s.peerBootstrapper(defaultMinPeers, bootstrappers) go s.peerBootstrapper(
ctx, defaultMinPeers, bootstrappers,
)
} else { } else {
srvrLog.Infof("Auto peer bootstrapping is disabled") srvrLog.Infof("Auto peer bootstrapping is disabled")
} }
@@ -3073,7 +3076,7 @@ func (s *server) createBootstrapIgnorePeers() map[autopilot.NodeID]struct{} {
// invariant, we ensure that our node is connected to a diverse set of peers // invariant, we ensure that our node is connected to a diverse set of peers
// and that nodes newly joining the network receive an up to date network view // and that nodes newly joining the network receive an up to date network view
// as soon as possible. // as soon as possible.
func (s *server) peerBootstrapper(numTargetPeers uint32, func (s *server) peerBootstrapper(ctx context.Context, numTargetPeers uint32,
bootstrappers []discovery.NetworkPeerBootstrapper) { bootstrappers []discovery.NetworkPeerBootstrapper) {
defer s.wg.Done() defer s.wg.Done()
@@ -3083,7 +3086,7 @@ func (s *server) peerBootstrapper(numTargetPeers uint32,
// We'll start off by aggressively attempting connections to peers in // We'll start off by aggressively attempting connections to peers in
// order to be a part of the network as soon as possible. // order to be a part of the network as soon as possible.
s.initialPeerBootstrap(ignoreList, numTargetPeers, bootstrappers) s.initialPeerBootstrap(ctx, ignoreList, numTargetPeers, bootstrappers)
// Once done, we'll attempt to maintain our target minimum number of // Once done, we'll attempt to maintain our target minimum number of
// peers. // peers.
@@ -3161,7 +3164,7 @@ func (s *server) peerBootstrapper(numTargetPeers uint32,
ignoreList = s.createBootstrapIgnorePeers() ignoreList = s.createBootstrapIgnorePeers()
peerAddrs, err := discovery.MultiSourceBootstrap( peerAddrs, err := discovery.MultiSourceBootstrap(
ignoreList, numNeeded*2, bootstrappers..., ctx, ignoreList, numNeeded*2, bootstrappers...,
) )
if err != nil { if err != nil {
srvrLog.Errorf("Unable to retrieve bootstrap "+ srvrLog.Errorf("Unable to retrieve bootstrap "+
@@ -3210,8 +3213,8 @@ const bootstrapBackOffCeiling = time.Minute * 5
// initialPeerBootstrap attempts to continuously connect to peers on startup // initialPeerBootstrap attempts to continuously connect to peers on startup
// until the target number of peers has been reached. This ensures that nodes // until the target number of peers has been reached. This ensures that nodes
// receive an up to date network view as soon as possible. // receive an up to date network view as soon as possible.
func (s *server) initialPeerBootstrap(ignore map[autopilot.NodeID]struct{}, func (s *server) initialPeerBootstrap(ctx context.Context,
numTargetPeers uint32, ignore map[autopilot.NodeID]struct{}, numTargetPeers uint32,
bootstrappers []discovery.NetworkPeerBootstrapper) { bootstrappers []discovery.NetworkPeerBootstrapper) {
srvrLog.Debugf("Init bootstrap with targetPeers=%v, bootstrappers=%v, "+ srvrLog.Debugf("Init bootstrap with targetPeers=%v, bootstrappers=%v, "+
@@ -3270,7 +3273,7 @@ func (s *server) initialPeerBootstrap(ignore map[autopilot.NodeID]struct{},
// in order to reach our target. // in order to reach our target.
peersNeeded := numTargetPeers - numActivePeers peersNeeded := numTargetPeers - numActivePeers
bootstrapAddrs, err := discovery.MultiSourceBootstrap( bootstrapAddrs, err := discovery.MultiSourceBootstrap(
ignore, peersNeeded, bootstrappers..., ctx, ignore, peersNeeded, bootstrappers...,
) )
if err != nil { if err != nil {
srvrLog.Errorf("Unable to retrieve initial bootstrap "+ srvrLog.Errorf("Unable to retrieve initial bootstrap "+
@@ -3549,36 +3552,17 @@ func (s *server) establishPersistentConnections() error {
// After checking our previous connections for addresses to connect to, // After checking our previous connections for addresses to connect to,
// iterate through the nodes in our channel graph to find addresses // iterate through the nodes in our channel graph to find addresses
// that have been added via NodeAnnouncement messages. // that have been added via NodeAnnouncement messages.
sourceNode, err := s.graphDB.SourceNode()
if err != nil {
return fmt.Errorf("failed to fetch source node: %w", err)
}
// TODO(roasbeef): instead iterate over link nodes and query graph for // TODO(roasbeef): instead iterate over link nodes and query graph for
// each of the nodes. // each of the nodes.
selfPub := s.identityECDH.PubKey().SerializeCompressed() err = s.graphDB.ForEachSourceNodeChannel(func(chanPoint wire.OutPoint,
err = s.graphDB.ForEachNodeChannel(sourceNode.PubKeyBytes, func( havePolicy bool, channelPeer *models.LightningNode) error {
tx kvdb.RTx,
chanInfo *models.ChannelEdgeInfo,
policy, _ *models.ChannelEdgePolicy) error {
// If the remote party has announced the channel to us, but we // If the remote party has announced the channel to us, but we
// haven't yet, then we won't have a policy. However, we don't // haven't yet, then we won't have a policy. However, we don't
// need this to connect to the peer, so we'll log it and move on. // need this to connect to the peer, so we'll log it and move on.
if policy == nil { if !havePolicy {
srvrLog.Warnf("No channel policy found for "+ srvrLog.Warnf("No channel policy found for "+
"ChannelPoint(%v): ", chanInfo.ChannelPoint) "ChannelPoint(%v): ", chanPoint)
}
// We'll now fetch the peer opposite from us within this
// channel so we can queue up a direct connection to them.
channelPeer, err := s.graphDB.FetchOtherNode(
tx, chanInfo, selfPub,
)
if err != nil {
return fmt.Errorf("unable to fetch channel peer for "+
"ChannelPoint(%v): %v", chanInfo.ChannelPoint,
err)
} }
pubStr := string(channelPeer.PubKeyBytes[:]) pubStr := string(channelPeer.PubKeyBytes[:])
@@ -3638,8 +3622,8 @@ func (s *server) establishPersistentConnections() error {
return nil return nil
}) })
if err != nil { if err != nil {
srvrLog.Errorf("Failed to iterate channels for node %x", srvrLog.Errorf("Failed to iterate over source node channels: "+
sourceNode.PubKeyBytes) "%v", err)
if !errors.Is(err, graphdb.ErrGraphNoEdgesFound) && if !errors.Is(err, graphdb.ErrGraphNoEdgesFound) &&
!errors.Is(err, graphdb.ErrEdgeNotFound) { !errors.Is(err, graphdb.ErrEdgeNotFound) {
+2 -2
View File
@@ -30,7 +30,7 @@ var (
// //
// NOTE: The migration function may have runtime dependencies, which // NOTE: The migration function may have runtime dependencies, which
// must be injected during runtime. // must be injected during runtime.
migrationConfig = []MigrationConfig{ migrationConfig = append([]MigrationConfig{
{ {
Name: "000001_invoices", Name: "000001_invoices",
Version: 1, Version: 1,
@@ -70,7 +70,7 @@ var (
// schema. This is optional and can be disabled by the // schema. This is optional and can be disabled by the
// user if necessary. // user if necessary.
}, },
} }, migrationAdditions...)
) )
// MigrationConfig is a configuration struct that describes SQL migrations. Each // MigrationConfig is a configuration struct that describes SQL migrations. Each
+5
View File
@@ -0,0 +1,5 @@
//go:build test_db_postgres || test_db_sqlite
package sqldb
var migrationAdditions = []MigrationConfig{}
+5
View File
@@ -0,0 +1,5 @@
//go:build !test_db_postgres && !test_db_sqlite
package sqldb
var migrationAdditions []MigrationConfig
+4 -4
View File
@@ -39,7 +39,7 @@ type TestPgFixture struct {
// NewTestPgFixture constructs a new TestPgFixture starting up a docker // NewTestPgFixture constructs a new TestPgFixture starting up a docker
// container running Postgres 11. The started container will expire in after // container running Postgres 11. The started container will expire in after
// the passed duration. // the passed duration.
func NewTestPgFixture(t *testing.T, expiry time.Duration) *TestPgFixture { func NewTestPgFixture(t testing.TB, expiry time.Duration) *TestPgFixture {
// Use a sensible default on Windows (tcp/http) and linux/osx (socket) // Use a sensible default on Windows (tcp/http) and linux/osx (socket)
// by specifying an empty endpoint. // by specifying an empty endpoint.
pool, err := dockertest.NewPool("") pool, err := dockertest.NewPool("")
@@ -119,13 +119,13 @@ func (f *TestPgFixture) GetConfig(dbName string) *PostgresConfig {
} }
// TearDown stops the underlying docker container. // TearDown stops the underlying docker container.
func (f *TestPgFixture) TearDown(t *testing.T) { func (f *TestPgFixture) TearDown(t testing.TB) {
err := f.pool.Purge(f.resource) err := f.pool.Purge(f.resource)
require.NoError(t, err, "Could not purge resource") require.NoError(t, err, "Could not purge resource")
} }
// randomDBName generates a random database name. // randomDBName generates a random database name.
func randomDBName(t *testing.T) string { func randomDBName(t testing.TB) string {
randBytes := make([]byte, 8) randBytes := make([]byte, 8)
_, err := rand.Read(randBytes) _, err := rand.Read(randBytes)
require.NoError(t, err) require.NoError(t, err)
@@ -135,7 +135,7 @@ func randomDBName(t *testing.T) string {
// NewTestPostgresDB is a helper function that creates a Postgres database for // NewTestPostgresDB is a helper function that creates a Postgres database for
// testing using the given fixture. // testing using the given fixture.
func NewTestPostgresDB(t *testing.T, fixture *TestPgFixture) *PostgresStore { func NewTestPostgresDB(t testing.TB, fixture *TestPgFixture) *PostgresStore {
t.Helper() t.Helper()
dbName := randomDBName(t) dbName := randomDBName(t)
+1 -1
View File
@@ -220,7 +220,7 @@ func (s *SqliteStore) SetSchemaVersion(version int, dirty bool) error {
// NewTestSqliteDB is a helper function that creates an SQLite database for // NewTestSqliteDB is a helper function that creates an SQLite database for
// testing. // testing.
func NewTestSqliteDB(t *testing.T) *SqliteStore { func NewTestSqliteDB(t testing.TB) *SqliteStore {
t.Helper() t.Helper()
t.Logf("Creating new SQLite DB for testing") t.Logf("Creating new SQLite DB for testing")