diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 50ae6ea6d..951637def 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -222,6 +222,8 @@ jobs: - unit tags="kvdb_etcd" - unit tags="kvdb_postgres" - unit tags="kvdb_sqlite" + - unit tags="test_db_sqlite" + - unit tags="test_db_postgres" - unit-race - unit-module diff --git a/autopilot/agent.go b/autopilot/agent.go index d9c35a685..1b70580a3 100644 --- a/autopilot/agent.go +++ b/autopilot/agent.go @@ -2,6 +2,7 @@ package autopilot import ( "bytes" + "context" "fmt" "math/rand" "net" @@ -11,6 +12,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/davecgh/go-spew/spew" + "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwire" ) @@ -166,8 +168,9 @@ type Agent struct { pendingOpens map[NodeID]LocalChannel pendingMtx sync.Mutex - quit chan struct{} - wg sync.WaitGroup + quit chan struct{} + wg sync.WaitGroup + cancel fn.Option[context.CancelFunc] } // 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 { var err error a.started.Do(func() { - err = a.start() + ctx, cancel := context.WithCancel(context.Background()) + a.cancel = fn.Some(cancel) + + err = a.start(ctx) }) return err } -func (a *Agent) start() error { +func (a *Agent) start(ctx context.Context) error { rand.Seed(time.Now().Unix()) log.Infof("Autopilot Agent starting") a.wg.Add(1) - go a.controller() + go a.controller(ctx) return nil } @@ -230,6 +236,7 @@ func (a *Agent) Stop() error { func (a *Agent) stop() error { log.Infof("Autopilot Agent stopping") + a.cancel.WhenSome(func(fn context.CancelFunc) { fn() }) close(a.quit) 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 // allocation, or attributes affecting its control loop being updated by the // backing Lightning Node. -func (a *Agent) controller() { +func (a *Agent) controller(ctx context.Context) { defer a.wg.Done() // We'll start off by assigning our starting balance, and injecting @@ -502,6 +509,9 @@ func (a *Agent) controller() { // immediately. case <-a.quit: return + + case <-ctx.Done(): + return } a.pendingMtx.Lock() @@ -539,7 +549,7 @@ func (a *Agent) controller() { log.Infof("Triggering attachment directive dispatch, "+ "total_funds=%v", a.totalBalance) - err := a.openChans(availableFunds, numChans, totalChans) + err := a.openChans(ctx, availableFunds, numChans, totalChans) if err != nil { 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 // attempts to open channels to them. -func (a *Agent) openChans(availableFunds btcutil.Amount, numChans uint32, - totalChans []LocalChannel) error { +func (a *Agent) openChans(ctx context.Context, availableFunds btcutil.Amount, + numChans uint32, totalChans []LocalChannel) error { // As channel size we'll use the maximum channel size available. chanSize := a.cfg.Constraints.MaxChanSize() @@ -598,7 +608,9 @@ func (a *Agent) openChans(availableFunds btcutil.Amount, numChans uint32, selfPubBytes := a.cfg.Self.SerializeCompressed() nodes := make(map[NodeID]struct{}) 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()) // If we come across ourselves, them we'll continue in @@ -636,7 +648,7 @@ func (a *Agent) openChans(availableFunds btcutil.Amount, numChans uint32, // graph. log.Debugf("Scoring %d nodes for chan_size=%v", len(nodes), chanSize) scores, err := a.cfg.Heuristic.NodeScores( - a.cfg.Graph, totalChans, chanSize, nodes, + ctx, a.cfg.Graph, totalChans, chanSize, nodes, ) if err != nil { return fmt.Errorf("unable to calculate node scores : %w", err) diff --git a/autopilot/agent_test.go b/autopilot/agent_test.go index 39e86906e..82c21a2ff 100644 --- a/autopilot/agent_test.go +++ b/autopilot/agent_test.go @@ -1,6 +1,7 @@ package autopilot import ( + "context" "errors" "fmt" "net" @@ -85,9 +86,9 @@ func (m *mockHeuristic) Name() string { return "mock" } -func (m *mockHeuristic) NodeScores(g ChannelGraph, chans []LocalChannel, - chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( - map[NodeID]*NodeScore, error) { +func (m *mockHeuristic) NodeScores(_ context.Context, g ChannelGraph, + chans []LocalChannel, chanSize btcutil.Amount, + nodes map[NodeID]struct{}) (map[NodeID]*NodeScore, error) { if m.nodeScoresArgs != nil { directive := directiveArg{ diff --git a/autopilot/betweenness_centrality.go b/autopilot/betweenness_centrality.go index db45bcf66..c3f0a3d02 100644 --- a/autopilot/betweenness_centrality.go +++ b/autopilot/betweenness_centrality.go @@ -1,6 +1,7 @@ package autopilot import ( + "context" "fmt" "sync" ) @@ -168,8 +169,10 @@ func betweennessCentrality(g *SimpleGraph, s int, centrality []float64) { } // Refresh recalculates and stores centrality values. -func (bc *BetweennessCentrality) Refresh(graph ChannelGraph) error { - cache, err := NewSimpleGraph(graph) +func (bc *BetweennessCentrality) Refresh(ctx context.Context, + graph ChannelGraph) error { + + cache, err := NewSimpleGraph(ctx, graph) if err != nil { return err } diff --git a/autopilot/betweenness_centrality_test.go b/autopilot/betweenness_centrality_test.go index 8e3e07ce2..b1de27834 100644 --- a/autopilot/betweenness_centrality_test.go +++ b/autopilot/betweenness_centrality_test.go @@ -1,6 +1,7 @@ package autopilot import ( + "context" "fmt" "testing" @@ -30,6 +31,9 @@ func TestBetweennessCentralityMetricConstruction(t *testing.T) { // Tests that empty graph results in empty centrality result. func TestBetweennessCentralityEmptyGraph(t *testing.T) { + t.Parallel() + ctx := context.Background() + centralityMetric, err := NewBetweennessCentralityMetric(1) require.NoError( t, err, @@ -42,7 +46,7 @@ func TestBetweennessCentralityEmptyGraph(t *testing.T) { require.NoError(t, err, "unable to create graph") success := t.Run(chanGraph.name, func(t1 *testing.T) { - err = centralityMetric.Refresh(graph) + err = centralityMetric.Refresh(ctx, graph) require.NoError(t1, err) centrality := centralityMetric.GetMetric(false) @@ -59,6 +63,9 @@ func TestBetweennessCentralityEmptyGraph(t *testing.T) { // Test betweenness centrality calculating using an example graph. func TestBetweennessCentralityWithNonEmptyGraph(t *testing.T) { + t.Parallel() + ctx := context.Background() + workers := []int{1, 3, 9, 100} tests := []struct { @@ -100,7 +107,7 @@ func TestBetweennessCentralityWithNonEmptyGraph(t *testing.T) { t1, graph, centralityTestGraph, ) - err = metric.Refresh(graph) + err = metric.Refresh(ctx, graph) require.NoError(t1, err) for _, expected := range tests { diff --git a/autopilot/combinedattach.go b/autopilot/combinedattach.go index b43856d24..9064a8229 100644 --- a/autopilot/combinedattach.go +++ b/autopilot/combinedattach.go @@ -1,6 +1,7 @@ package autopilot import ( + "context" "fmt" "github.com/btcsuite/btcd/btcutil" @@ -70,9 +71,9 @@ func (c *WeightedCombAttachment) Name() string { // is the maximum possible improvement in connectivity. // // NOTE: This is a part of the AttachmentHeuristic interface. -func (c *WeightedCombAttachment) NodeScores(g ChannelGraph, chans []LocalChannel, - chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( - map[NodeID]*NodeScore, error) { +func (c *WeightedCombAttachment) NodeScores(ctx context.Context, g ChannelGraph, + chans []LocalChannel, chanSize btcutil.Amount, + nodes map[NodeID]struct{}) (map[NodeID]*NodeScore, error) { // We now query each heuristic to determine the score they give to the // 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()) s, err := h.NodeScores( - g, chans, chanSize, nodes, + ctx, g, chans, chanSize, nodes, ) if err != nil { return nil, fmt.Errorf("unable to get sub score: %w", diff --git a/autopilot/externalscoreattach.go b/autopilot/externalscoreattach.go index 1144d7d4a..a979e15ee 100644 --- a/autopilot/externalscoreattach.go +++ b/autopilot/externalscoreattach.go @@ -1,6 +1,7 @@ package autopilot import ( + "context" "fmt" "sync" @@ -80,9 +81,9 @@ func (s *ExternalScoreAttachment) SetNodeScores(targetHeuristic string, // not known will get a score of 0. // // NOTE: This is a part of the AttachmentHeuristic interface. -func (s *ExternalScoreAttachment) NodeScores(g ChannelGraph, chans []LocalChannel, - chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( - map[NodeID]*NodeScore, error) { +func (s *ExternalScoreAttachment) NodeScores(_ context.Context, g ChannelGraph, + chans []LocalChannel, chanSize btcutil.Amount, + nodes map[NodeID]struct{}) (map[NodeID]*NodeScore, error) { existingPeers := make(map[NodeID]struct{}) for _, c := range chans { diff --git a/autopilot/externalscoreattach_test.go b/autopilot/externalscoreattach_test.go index aa0f87fb2..bef50a674 100644 --- a/autopilot/externalscoreattach_test.go +++ b/autopilot/externalscoreattach_test.go @@ -1,6 +1,7 @@ package autopilot_test import ( + "context" "testing" "github.com/btcsuite/btcd/btcec/v2" @@ -22,6 +23,7 @@ func randKey() (*btcec.PublicKey, error) { // ExternalScoreAttachment correctly reflects the scores we set last. func TestSetNodeScores(t *testing.T) { t.Parallel() + ctx := context.Background() const name = "externalscore" @@ -62,7 +64,7 @@ func TestSetNodeScores(t *testing.T) { q[nID] = struct{}{} } resp, err := h.NodeScores( - nil, nil, btcutil.Amount(btcutil.SatoshiPerBitcoin), q, + ctx, nil, nil, btcutil.Amount(btcutil.SatoshiPerBitcoin), q, ) if err != nil { t.Fatal(err) diff --git a/autopilot/graph.go b/autopilot/graph.go index c8b54082a..d20c6316e 100644 --- a/autopilot/graph.go +++ b/autopilot/graph.go @@ -1,6 +1,7 @@ package autopilot import ( + "context" "encoding/hex" "net" "sort" @@ -80,7 +81,9 @@ func (d *dbNode) Addrs() []net.Addr { // describes the active channel. // // 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, _ *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. // // 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 { // We'll skip over any node that doesn't have any advertised // 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{ tx: nodeTx, } - return cb(node) + + return cb(ctx, node) }) } @@ -185,7 +191,9 @@ func (nc dbNodeCached) Addrs() []net.Addr { // describes the active channel. // // 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 { edge := ChannelEdge{ 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 } } @@ -208,7 +216,9 @@ func (nc dbNodeCached) ForEachChannel(cb func(ChannelEdge) error) error { // error, then execution should be terminated. // // 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, channels map[uint64]*graphdb.DirectedChannel) error { @@ -217,7 +227,8 @@ func (dc *databaseChannelGraphCached) ForEachNode(cb func(Node) error) error { node: n, channels: channels, } - return cb(node) + + return cb(ctx, node) } return nil }) @@ -262,9 +273,11 @@ func (m memNode) Addrs() []net.Addr { // describes the active channel. // // 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 { - if err := cb(channel); err != nil { + if err := cb(ctx, channel); err != nil { return err } } diff --git a/autopilot/interface.go b/autopilot/interface.go index 35182a760..db25d9bb2 100644 --- a/autopilot/interface.go +++ b/autopilot/interface.go @@ -1,6 +1,7 @@ package autopilot import ( + "context" "net" "github.com/btcsuite/btcd/btcec/v2" @@ -35,7 +36,8 @@ type Node interface { // iterate through all edges emanating from/to the target node. For // each active channel, this function should be called with the // 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 @@ -83,7 +85,7 @@ type ChannelGraph interface { // ForEachNode is a higher-order function that should be called once // for each connected node within the channel graph. If the passed // 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 @@ -142,7 +144,7 @@ type AttachmentHeuristic interface { // // NOTE: A NodeID not found in the returned map is implicitly given a // score of 0. - NodeScores(g ChannelGraph, chans []LocalChannel, + NodeScores(ctx context.Context, g ChannelGraph, chans []LocalChannel, chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( map[NodeID]*NodeScore, error) } @@ -155,7 +157,7 @@ type NodeMetric interface { Name() string // 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 // map are per node and can be in arbitrary domain. If normalize is diff --git a/autopilot/manager.go b/autopilot/manager.go index 0463f98d9..600a1055b 100644 --- a/autopilot/manager.go +++ b/autopilot/manager.go @@ -1,6 +1,7 @@ package autopilot import ( + "context" "fmt" "sync" @@ -266,8 +267,8 @@ func (m *Manager) StopAgent() error { } // QueryHeuristics queries the available autopilot heuristics for node scores. -func (m *Manager) QueryHeuristics(nodes []NodeID, localState bool) ( - HeuristicScores, error) { +func (m *Manager) QueryHeuristics(ctx context.Context, nodes []NodeID, + localState bool) (HeuristicScores, error) { m.Lock() defer m.Unlock() @@ -278,7 +279,8 @@ func (m *Manager) QueryHeuristics(nodes []NodeID, localState bool) ( } 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 @@ -289,8 +291,8 @@ type HeuristicScores map[string]map[NodeID]float64 // the agent's current active heuristic. // // NOTE: Must be called with the manager's lock. -func (m *Manager) queryHeuristics(nodes map[NodeID]struct{}, localState bool) ( - HeuristicScores, error) { +func (m *Manager) queryHeuristics(ctx context.Context, + nodes map[NodeID]struct{}, localState bool) (HeuristicScores, error) { // 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 @@ -338,7 +340,7 @@ func (m *Manager) queryHeuristics(nodes map[NodeID]struct{}, localState bool) ( } s, err := h.NodeScores( - m.cfg.PilotCfg.Graph, totalChans, chanSize, nodes, + ctx, m.cfg.PilotCfg.Graph, totalChans, chanSize, nodes, ) if err != nil { return nil, fmt.Errorf("unable to get sub score: %w", diff --git a/autopilot/prefattach.go b/autopilot/prefattach.go index 4f4ff635f..76d814a46 100644 --- a/autopilot/prefattach.go +++ b/autopilot/prefattach.go @@ -1,6 +1,7 @@ package autopilot import ( + "context" prand "math/rand" "time" @@ -78,9 +79,9 @@ func (p *PrefAttachment) Name() string { // given to nodes already having high connectivity in the graph. // // NOTE: This is a part of the AttachmentHeuristic interface. -func (p *PrefAttachment) NodeScores(g ChannelGraph, chans []LocalChannel, - chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( - map[NodeID]*NodeScore, error) { +func (p *PrefAttachment) NodeScores(ctx context.Context, g ChannelGraph, + chans []LocalChannel, chanSize btcutil.Amount, + nodes map[NodeID]struct{}) (map[NodeID]*NodeScore, error) { // We first run though the graph once in order to find the median // channel size. @@ -88,8 +89,10 @@ func (p *PrefAttachment) NodeScores(g ChannelGraph, chans []LocalChannel, allChans []btcutil.Amount seenChans = make(map[uint64]struct{}) ) - if err := g.ForEachNode(func(n Node) error { - err := n.ForEachChannel(func(e ChannelEdge) error { + if err := g.ForEachNode(ctx, func(ctx context.Context, n Node) error { + err := n.ForEachChannel(ctx, func(_ context.Context, + e ChannelEdge) error { + if _, ok := seenChans[e.ChanID.ToUint64()]; ok { return nil } @@ -114,15 +117,19 @@ func (p *PrefAttachment) NodeScores(g ChannelGraph, chans []LocalChannel, // the graph. var maxChans 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 - 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 // channels actually worsens our connectivity in the // graph (we will potentially waste time trying to use // these useless channels in path finding), we decrease // the counter for such channels. - if e.Capacity < medianChanSize/minMedianChanSizeFraction { + if e.Capacity < + medianChanSize/minMedianChanSizeFraction { + nodeChans-- return nil } diff --git a/autopilot/prefattach_test.go b/autopilot/prefattach_test.go index f20c3a480..e0baf4971 100644 --- a/autopilot/prefattach_test.go +++ b/autopilot/prefattach_test.go @@ -2,6 +2,7 @@ package autopilot import ( "bytes" + "context" "errors" prand "math/rand" "net" @@ -13,7 +14,6 @@ import ( "github.com/btcsuite/btcd/btcutil" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" - "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" @@ -36,19 +36,7 @@ type testDBGraph struct { } func newDiskChanGraph(t *testing.T) (testGraph, error) { - backend, err := kvdb.GetBoltBackend(&kvdb.BoltBackendConfig{ - 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) - + graphDB := graphdb.MakeTestGraph(t) require.NoError(t, graphDB.Start()) t.Cleanup(func() { require.NoError(t, graphDB.Stop()) @@ -87,6 +75,8 @@ var chanGraphs = []struct { // TestPrefAttachmentSelectEmptyGraph ensures that when passed an // empty graph, the NodeSores function always returns a score of 0. func TestPrefAttachmentSelectEmptyGraph(t *testing.T) { + t.Parallel() + ctx := context.Background() prefAttach := NewPrefAttachment() // 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. const walletFunds = btcutil.SatoshiPerBitcoin scores, err := prefAttach.NodeScores( - graph, nil, walletFunds, nodes, + ctx, graph, nil, walletFunds, nodes, ) require.NoError(t1, err) @@ -126,6 +116,7 @@ func TestPrefAttachmentSelectEmptyGraph(t *testing.T) { // and the funds are appropriately allocated across each peer. func TestPrefAttachmentSelectTwoVertexes(t *testing.T) { t.Parallel() + ctx := context.Background() 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 // this point. nodes := make(map[NodeID]struct{}) - err = graph.ForEachNode(func(n Node) error { - nodes[n.PubKey()] = struct{}{} - return nil - }) + err = graph.ForEachNode(ctx, + func(_ context.Context, n Node) error { + nodes[n.PubKey()] = struct{}{} + return nil + }, + ) require.NoError(t1, err) require.Len(t1, nodes, 3) @@ -168,7 +161,7 @@ func TestPrefAttachmentSelectTwoVertexes(t *testing.T) { // attempt to get our candidates channel score given // the current state of the graph. candidates, err := prefAttach.NodeScores( - graph, nil, maxChanSize, nodes, + ctx, graph, nil, maxChanSize, nodes, ) 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). func TestPrefAttachmentSelectGreedyAllocation(t *testing.T) { t.Parallel() + ctx := context.Background() prand.Seed(time.Now().Unix()) @@ -245,22 +239,25 @@ func TestPrefAttachmentSelectGreedyAllocation(t *testing.T) { numNodes := 0 twoChans := false nodes := make(map[NodeID]struct{}) - err = graph.ForEachNode(func(n Node) error { - numNodes++ - nodes[n.PubKey()] = struct{}{} - numChans := 0 - err := n.ForEachChannel(func(c ChannelEdge) error { - numChans++ + err = graph.ForEachNode( + ctx, func(ctx context.Context, n Node) error { + numNodes++ + nodes[n.PubKey()] = struct{}{} + numChans := 0 + 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 }) - if err != nil { - return err - } - - twoChans = twoChans || (numChans == 2) - - return nil - }) require.NoError(t1, err) require.EqualValues(t1, 3, numNodes) @@ -272,7 +269,7 @@ func TestPrefAttachmentSelectGreedyAllocation(t *testing.T) { // result, the heuristic should try to greedily // allocate funds to channels. scores, err := prefAttach.NodeScores( - graph, nil, maxChanSize, nodes, + ctx, graph, nil, maxChanSize, nodes, ) require.NoError(t1, err) @@ -290,7 +287,7 @@ func TestPrefAttachmentSelectGreedyAllocation(t *testing.T) { // candidates of that size. const remBalance = btcutil.SatoshiPerBitcoin * 0.5 scores, err = prefAttach.NodeScores( - graph, nil, remBalance, nodes, + ctx, graph, nil, remBalance, nodes, ) require.NoError(t1, err) @@ -313,6 +310,7 @@ func TestPrefAttachmentSelectGreedyAllocation(t *testing.T) { // of zero during scoring. func TestPrefAttachmentSelectSkipNodes(t *testing.T) { t.Parallel() + ctx := context.Background() prand.Seed(time.Now().Unix()) @@ -335,10 +333,13 @@ func TestPrefAttachmentSelectSkipNodes(t *testing.T) { require.NoError(t1, err) nodes := make(map[NodeID]struct{}) - err = graph.ForEachNode(func(n Node) error { - nodes[n.PubKey()] = struct{}{} - return nil - }) + err = graph.ForEachNode( + ctx, func(_ context.Context, n Node) error { + nodes[n.PubKey()] = struct{}{} + + return nil + }, + ) require.NoError(t1, err) 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 // all nodes in the graph. scores, err := prefAttach.NodeScores( - graph, nil, maxChanSize, nodes, + ctx, graph, nil, maxChanSize, nodes, ) require.NoError(t1, err) @@ -374,7 +375,7 @@ func TestPrefAttachmentSelectSkipNodes(t *testing.T) { // then all nodes should have a score of zero, since we // already got channels to them. scores, err = prefAttach.NodeScores( - graph, chans, maxChanSize, nodes, + ctx, graph, chans, maxChanSize, nodes, ) require.NoError(t1, err) @@ -583,9 +584,11 @@ func newMemChannelGraph() *memChannelGraph { // error, then execution should be terminated. // // 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 { - if err := cb(node); err != nil { + if err := cb(ctx, node); err != nil { return err } } @@ -720,7 +723,7 @@ func (t *testNodeTx) Node() *models.LightningNode { func (t *testNodeTx) ForEachChannel(f func(*models.ChannelEdgeInfo, *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, policy2 *models.ChannelEdgePolicy) error { diff --git a/autopilot/setup_test.go b/autopilot/setup_test.go new file mode 100644 index 000000000..c9c050a8e --- /dev/null +++ b/autopilot/setup_test.go @@ -0,0 +1,11 @@ +package autopilot + +import ( + "testing" + + "github.com/lightningnetwork/lnd/kvdb" +) + +func TestMain(m *testing.M) { + kvdb.RunTests(m) +} diff --git a/autopilot/simple_graph.go b/autopilot/simple_graph.go index 4d294b3f2..f028db3c7 100644 --- a/autopilot/simple_graph.go +++ b/autopilot/simple_graph.go @@ -1,5 +1,7 @@ package autopilot +import "context" + // diameterCutoff is used to discard nodes in the diameter calculation. // 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 @@ -20,7 +22,7 @@ type SimpleGraph struct { // NewSimpleGraph creates a simplified graph from the current channel graph. // Returns an error if the channel graph iteration fails due to underlying // failure. -func NewSimpleGraph(g ChannelGraph) (*SimpleGraph, error) { +func NewSimpleGraph(ctx context.Context, g ChannelGraph) (*SimpleGraph, error) { nodes := make(map[NodeID]int) adj := make(map[int][]int) nextIndex := 0 @@ -42,17 +44,22 @@ func NewSimpleGraph(g ChannelGraph) (*SimpleGraph, error) { return nodeIndex } - // Iterate over each node and each channel and update the adj and the node - // index. - err := g.ForEachNode(func(node Node) error { + // Iterate over each node and each channel and update the adj and the + // node index. + err := g.ForEachNode(ctx, func(ctx context.Context, node Node) error { u := getNodeIndex(node) - return node.ForEachChannel(func(edge ChannelEdge) error { - v := getNodeIndex(edge.Peer) + return node.ForEachChannel( + ctx, func(_ context.Context, + edge ChannelEdge) error { - adj[u] = append(adj[u], v) - return nil - }) + v := getNodeIndex(edge.Peer) + + adj[u] = append(adj[u], v) + + return nil + }, + ) }) if err != nil { return nil, err diff --git a/autopilot/top_centrality.go b/autopilot/top_centrality.go index 65157c621..e96b34097 100644 --- a/autopilot/top_centrality.go +++ b/autopilot/top_centrality.go @@ -1,6 +1,7 @@ package autopilot import ( + "context" "runtime" "github.com/btcsuite/btcd/btcutil" @@ -50,12 +51,12 @@ func (g *TopCentrality) Name() string { // As our current implementation of betweenness centrality is non-incremental, // NodeScores will recalculate the centrality values on every call, which is // slow for large graphs. -func (g *TopCentrality) NodeScores(graph ChannelGraph, chans []LocalChannel, - chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( - map[NodeID]*NodeScore, error) { +func (g *TopCentrality) NodeScores(ctx context.Context, graph ChannelGraph, + chans []LocalChannel, chanSize btcutil.Amount, + nodes map[NodeID]struct{}) (map[NodeID]*NodeScore, error) { // 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 } diff --git a/autopilot/top_centrality_test.go b/autopilot/top_centrality_test.go index 9e8242761..e8ca3616d 100644 --- a/autopilot/top_centrality_test.go +++ b/autopilot/top_centrality_test.go @@ -1,6 +1,7 @@ package autopilot import ( + "context" "testing" "github.com/btcsuite/btcd/btcec/v2" @@ -58,7 +59,7 @@ func testTopCentrality(t *testing.T, graph testGraph, // Attempt to get centrality scores and expect // that the result equals with the expected set. scores, err := topCentrality.NodeScores( - graph, channels, chanSize, nodes, + context.Background(), graph, channels, chanSize, nodes, ) require.NoError(t, err) diff --git a/batch/batch.go b/batch/batch.go index 9f4842c65..e31701439 100644 --- a/batch/batch.go +++ b/batch/batch.go @@ -1,10 +1,10 @@ package batch import ( + "context" "errors" "sync" - "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/sqldb" ) @@ -14,28 +14,44 @@ var errSolo = errors.New( "batch function returned an error and should be re-run solo", ) -type request struct { - *Request +// txOpts implements the sqldb.TxOptions interface. It is used to indicate that +// 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 } -type batch struct { - db kvdb.Backend +type batch[Q any] struct { + db sqldb.BatchedTx[Q] start sync.Once - reqs []*request - clear func(b *batch) + reqs []*request[Q] + clear func(b *batch[Q]) locker sync.Locker + txOpts txOpts } // trigger is the entry point for the batch and ensures that run is started at // most once. -func (b *batch) trigger() { - b.start.Do(b.run) +func (b *batch[Q]) trigger(ctx context.Context) { + b.start.Do(func() { + b.run(ctx) + }) } // run executes the current batch of requests. If any individual requests fail // 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 // added to this batch. b.clear(b) @@ -52,9 +68,9 @@ func (b *batch) run() { // that fail will be retried individually. for len(b.reqs) > 0 { 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 { - err := req.Update(tx) + err := req.Do(tx) if err != nil { // If we get a serialization error, we // want the underlying SQL retry diff --git a/batch/batch_test.go b/batch/batch_test.go index 7a73b8f4b..dd9a5fb4f 100644 --- a/batch/batch_test.go +++ b/batch/batch_test.go @@ -1,7 +1,11 @@ package batch import ( + "context" + "database/sql" + "encoding/binary" "errors" + "fmt" "path/filepath" "sync" "testing" @@ -9,10 +13,26 @@ import ( "github.com/btcsuite/btcwallet/walletdb" "github.com/lightningnetwork/lnd/kvdb" + "github.com/lightningnetwork/lnd/sqldb" + "github.com/lightningnetwork/lnd/sqldb/sqlc" "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) { + t.Parallel() + ctx := context.Background() + dbDir := t.TempDir() dbName := filepath.Join(dbDir, "weks.db") @@ -30,19 +50,21 @@ func TestRetry(t *testing.T) { mu sync.Mutex 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 // execute it non-lazily. It should still return the error the second // time. - req := &Request{ - Update: func(tx kvdb.RwTx) error { + req := &Request[kvdb.RwTx]{ + Do: func(tx kvdb.RwTx) error { called++ return errors.New("test") }, } - err = sched.Execute(req) + err = sched.Execute(ctx, req) // Check and reset the called counter. mu.Lock() @@ -56,14 +78,14 @@ func TestRetry(t *testing.T) { // a serialization error, which should cause the underlying postgres // transaction to retry. Since we aren't using postgres, this will // cause the transaction to not be retried at all. - req = &Request{ - Update: func(tx kvdb.RwTx) error { + req = &Request[kvdb.RwTx]{ + Do: func(tx kvdb.RwTx) error { called++ return errors.New("could not serialize access") }, } - err = sched.Execute(req) + err = sched.Execute(ctx, req) // Check the called counter. mu.Lock() @@ -72,3 +94,602 @@ func TestRetry(t *testing.T) { 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) + }) + } + } +} diff --git a/batch/interface.go b/batch/interface.go index cd58a148c..adb447ca7 100644 --- a/batch/interface.go +++ b/batch/interface.go @@ -1,10 +1,13 @@ package batch -import "github.com/lightningnetwork/lnd/kvdb" +import "context" // Request defines an operation that can be batched into a single bbolt // 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 // any possible modifications to local state as a result of previous // calls to Update that were not committed due to a concurrent batch @@ -13,10 +16,10 @@ type Request struct { // NOTE: This field is optional. 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. - Update func(tx kvdb.RwTx) error + Do func(tx Q) error // OnCommit is called if the batch or a subset of the batch including // this request all succeeded without failure. The passed error should @@ -25,31 +28,67 @@ type Request struct { // // NOTE: This field is optional. 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, // 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 // request. -type SchedulerOption func(r *Request) +type SchedulerOption func(*SchedulerOptions) // LazyAdd will make the request be executed lazily, added to the next batch to // reduce db contention. func LazyAdd() SchedulerOption { - return func(r *Request) { - r.lazy = true + return func(opts *SchedulerOptions) { + 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 // 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 // batch. This method blocks until the underlying closure has been // run against the database. The resulting error is returned to the // caller. - Execute(req *Request) error + Execute(ctx context.Context, req *Request[Q]) error } diff --git a/batch/kvdb.go b/batch/kvdb.go new file mode 100644 index 000000000..2e3ea10a9 --- /dev/null +++ b/batch/kvdb.go @@ -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) +} diff --git a/batch/scheduler.go b/batch/scheduler.go index 4a01adda7..dbd319f30 100644 --- a/batch/scheduler.go +++ b/batch/scheduler.go @@ -1,10 +1,11 @@ package batch import ( + "context" "sync" "time" - "github.com/lightningnetwork/lnd/kvdb" + "github.com/lightningnetwork/lnd/sqldb" ) // 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 // this time has elapsed, the batch is closed and executed. Subsequent requests // are then added to a new batch which undergoes the same process. -type TimeScheduler struct { - db kvdb.Backend +type TimeScheduler[Q any] struct { + db sqldb.BatchedTx[Q] locker sync.Locker duration time.Duration mu sync.Mutex - b *batch + b *batch[Q] } // 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 // can be maintained, as successful db operations will cause a request's // OnCommit method to be executed while holding this lock. -func NewTimeScheduler(db kvdb.Backend, locker sync.Locker, - duration time.Duration) *TimeScheduler { +func NewTimeScheduler[Q any](db sqldb.BatchedTx[Q], locker sync.Locker, + duration time.Duration) *TimeScheduler[Q] { - return &TimeScheduler{ + return &TimeScheduler[Q]{ db: db, locker: locker, duration: duration, @@ -42,8 +43,12 @@ func NewTimeScheduler(db kvdb.Backend, locker sync.Locker, // underlying operation is returned to the caller. // // NOTE: Part of the Scheduler interface. -func (s *TimeScheduler) Execute(r *Request) error { - req := request{ +func (s *TimeScheduler[Q]) Execute(ctx context.Context, r *Request[Q]) error { + if r.Opts == nil { + r.Opts = NewDefaultSchedulerOpts() + } + + req := request[Q]{ Request: r, errChan: make(chan error, 1), } @@ -52,20 +57,41 @@ func (s *TimeScheduler) Execute(r *Request) error { // or no batch exists, create a new one. s.mu.Lock() if s.b == nil { - s.b = &batch{ + s.b = &batch[Q]{ db: s.db, clear: s.clear, 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) - // If this is a non-lazy request, we'll execute the batch immediately. - if !r.lazy { - go s.b.trigger() + // We only upgrade the batch to read-write if the new request is not + // read-only. If it is already read-write, we don't need to do anything. + 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() // 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. - 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 { 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 // added. -func (s *TimeScheduler) clear(b *batch) { +func (s *TimeScheduler[Q]) clear(b *batch[Q]) { s.mu.Lock() if s.b == b { s.b = nil diff --git a/channeldb/db_test.go b/channeldb/db_test.go index e175ea1fb..9c8c34d58 100644 --- a/channeldb/db_test.go +++ b/channeldb/db_test.go @@ -14,7 +14,6 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/kvdb" @@ -185,21 +184,22 @@ func TestMultiSourceAddrsForNode(t *testing.T) { fullDB, err := MakeTestDB(t) require.NoError(t, err, "unable to make test database") - graph, err := graphdb.MakeTestGraph(t) - require.NoError(t, err) + graph := newMockAddrSource(t) + t.Cleanup(func() { + graph.AssertExpectations(t) + }) - // We'll make a test vertex to insert into the database, as the source - // node, but this node will only have half the number of addresses it - // usually does. + // We'll make a test vertex, but this node will only have half the + // number of addresses it usually does. testNode := createTestVertex(t) - require.NoError(t, err, "unable to create test node") - testNode.Addresses = []net.Addr{testAddr} - require.NoError(t, graph.SetSourceNode(testNode)) + nodePub, err := testNode.PubKey() + require.NoError(t, err) + 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 // additional address. - nodePub, err := testNode.PubKey() - require.NoError(t, err, "unable to recv node pub") linkNode := NewLinkNode( fullDB.channelStateDB.linkNodeDB, wire.MainNet, nodePub, anotherAddr, diff --git a/channeldb/meta_test.go b/channeldb/meta_test.go index 5b6bd29a9..97f6f0489 100644 --- a/channeldb/meta_test.go +++ b/channeldb/meta_test.go @@ -6,7 +6,6 @@ import ( "github.com/btcsuite/btcwallet/walletdb" "github.com/go-errors/errors" - graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/kvdb" "github.com/stretchr/testify/require" ) @@ -22,13 +21,6 @@ func applyMigration(t *testing.T, beforeMigration, afterMigration func(d *DB), } 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 // with test data. beforeMigration(cdb) diff --git a/config_builder.go b/config_builder.go index 2214250e7..708e79d8f 100644 --- a/config_builder.go +++ b/config_builder.go @@ -1046,10 +1046,14 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( ) } - dbs.GraphDB, err = graphdb.NewChannelGraph(&graphdb.Config{ - KVDB: databaseBackends.GraphDB, - KVStoreOpts: graphDBOptions, - }, chanGraphOpts...) + graphStore, err := graphdb.NewKVStore( + databaseBackends.GraphDB, graphDBOptions..., + ) + if err != nil { + return nil, nil, err + } + + dbs.GraphDB, err = graphdb.NewChannelGraph(graphStore, chanGraphOpts...) if err != nil { cleanUp() diff --git a/discovery/bootstrapper.go b/discovery/bootstrapper.go index 0d370663d..1a0f99735 100644 --- a/discovery/bootstrapper.go +++ b/discovery/bootstrapper.go @@ -2,6 +2,7 @@ package discovery import ( "bytes" + "context" "crypto/rand" "crypto/sha256" "errors" @@ -36,8 +37,9 @@ type NetworkPeerBootstrapper interface { // denotes how many valid peer addresses to return. The passed set of // node nodes allows the caller to ignore a set of nodes perhaps // because they already have connections established. - SampleNodeAddrs(numAddrs uint32, - ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, error) + SampleNodeAddrs(ctx context.Context, numAddrs uint32, + ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, + error) // Name returns a human readable string which names the concrete // implementation of the NetworkPeerBootstrapper. @@ -50,7 +52,8 @@ type NetworkPeerBootstrapper interface { // bootstrapper will be queried successively until the target amount is met. If // the ignore map is populated, then the bootstrappers will be instructed to // 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) { // 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. numAddrsLeft := numAddrs - uint32(len(addrs)) log.Tracef("Querying for %v addresses", numAddrsLeft) - netAddrs, err := bootstrapper.SampleNodeAddrs(numAddrsLeft, ignore) + netAddrs, err := bootstrapper.SampleNodeAddrs( + ctx, numAddrsLeft, ignore, + ) if err != nil { // If we encounter an error with a bootstrapper, then // 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. // // 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) { + ctx := context.TODO() + // We'll merge the ignore map with our currently selected map in order // to ensure we don't return any duplicate nodes. for n := range ignore { @@ -177,7 +185,9 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(numAddrs uint32, 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()) if _, ok := c.tried[nID]; ok { return nil @@ -382,7 +392,8 @@ func (d *DNSSeedBootstrapper) fallBackSRVLookup(soaShim string, // 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 // 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) { var netAddrs []*lnwire.NetAddress diff --git a/discovery/gossiper.go b/discovery/gossiper.go index cdeaa2942..4b2f4f220 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -2,6 +2,7 @@ package discovery import ( "bytes" + "context" "errors" "fmt" "strings" @@ -475,9 +476,6 @@ type AuthenticatedGossiper struct { // held. bestHeight uint32 - quit chan struct{} - wg sync.WaitGroup - // cfg is a copy of the configuration struct that the gossiper service // was initialized with. cfg *Config @@ -555,6 +553,10 @@ type AuthenticatedGossiper struct { vb *ValidationBarrier sync.Mutex + + cancel fn.Option[context.CancelFunc] + quit chan struct{} + wg sync.WaitGroup } // New creates a new AuthenticatedGossiper instance, initialized with the @@ -644,13 +646,16 @@ func (d *AuthenticatedGossiper) PropagateChanPolicyUpdate( func (d *AuthenticatedGossiper) Start() error { var err error d.started.Do(func() { + ctx, cancel := context.WithCancel(context.Background()) + d.cancel = fn.Some(cancel) + log.Info("Authenticated Gossiper starting") - err = d.start() + err = d.start(ctx) }) 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. // We do this immediately so we'll later be able to consume any/all // blocks which were discovered. @@ -680,7 +685,7 @@ func (d *AuthenticatedGossiper) start() error { // Start receiving blocks in its dedicated goroutine. d.wg.Add(2) go d.syncBlockHeight() - go d.networkHandler() + go d.networkHandler(ctx) return nil } @@ -835,6 +840,7 @@ func (d *AuthenticatedGossiper) stop() { d.banman.stop() + d.cancel.WhenSome(func(fn context.CancelFunc) { fn() }) close(d.quit) d.wg.Wait() @@ -851,8 +857,8 @@ func (d *AuthenticatedGossiper) stop() { // then added to a queue for batched trickled announcement to all connected // peers. Remote channel announcements should contain the announcement proof // and be fully validated. -func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(msg lnwire.Message, - peer lnpeer.Peer) chan error { +func (d *AuthenticatedGossiper) ProcessRemoteAnnouncement(ctx context.Context, + msg lnwire.Message, peer lnpeer.Peer) chan error { 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 // 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: "+ "%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 // 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(): return nil + case <-ctx.Done(): + return nil case <-d.quit: 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 // generated announcements are always sent before remotely generated // announcements. -func (d *AuthenticatedGossiper) splitAndSendAnnBatch( +func (d *AuthenticatedGossiper) splitAndSendAnnBatch(ctx context.Context, annBatch msgsToBroadcast) { // delayNextBatch is a helper closure that blocks for `SubBatchDelay` @@ -1374,7 +1384,7 @@ func (d *AuthenticatedGossiper) splitAndSendAnnBatch( // Now send the remote announcements. for _, annBatch := range remoteBatches { - d.sendRemoteBatch(annBatch) + d.sendRemoteBatch(ctx, annBatch) delayNextBatch() } }() @@ -1398,14 +1408,16 @@ func (d *AuthenticatedGossiper) sendLocalBatch(annBatch []msgWithSenders) { // sendRemoteBatch broadcasts a list of remotely generated announcements to our // peers. -func (d *AuthenticatedGossiper) sendRemoteBatch(annBatch []msgWithSenders) { +func (d *AuthenticatedGossiper) sendRemoteBatch(ctx context.Context, + annBatch []msgWithSenders) { + syncerPeers := d.syncMgr.GossipSyncers() // We'll first attempt to filter out this new message for all peers // that have active gossip syncers active. for pub, syncer := range syncerPeers { log.Tracef("Sending messages batch to GossipSyncer(%s)", pub) - syncer.FilterGossipMsgs(annBatch...) + syncer.FilterGossipMsgs(ctx, annBatch...) } for _, msgChunk := range annBatch { @@ -1431,7 +1443,7 @@ func (d *AuthenticatedGossiper) sendRemoteBatch(annBatch []msgWithSenders) { // broadcasting our latest topology state to all connected peers. // // NOTE: This MUST be run as a goroutine. -func (d *AuthenticatedGossiper) networkHandler() { +func (d *AuthenticatedGossiper) networkHandler(ctx context.Context) { defer d.wg.Done() // 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 // 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) } @@ -1463,7 +1475,7 @@ func (d *AuthenticatedGossiper) networkHandler() { // the affected channels and also update the underlying // graph with the new state. newChanUpdates, err := d.processChanPolicyUpdate( - policyUpdate.edgesToUpdate, + ctx, policyUpdate.edgesToUpdate, ) policyUpdate.errChan <- err if err != nil { @@ -1488,7 +1500,7 @@ func (d *AuthenticatedGossiper) networkHandler() { // messages that we'll process serially. case *lnwire.AnnounceSignatures1: emittedAnnouncements, _ := d.processNetworkAnnouncement( - announcement, + ctx, announcement, ) log.Debugf("Processed network message %s, "+ "returned len(announcements)=%v", @@ -1528,7 +1540,7 @@ func (d *AuthenticatedGossiper) networkHandler() { d.wg.Add(1) go d.handleNetworkMessages( - announcement, &announcements, annJobID, + ctx, announcement, &announcements, annJobID, ) // 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 // our peer's policies so we ensure they propagate // properly. - d.splitAndSendAnnBatch(announcementBatch) + d.splitAndSendAnnBatch(ctx, announcementBatch) // The retransmission timer has ticked which indicates that we // 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 // network. 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 "+ "announcements: %v", err) } @@ -1578,8 +1590,8 @@ func (d *AuthenticatedGossiper) networkHandler() { // signal its dependants and add the new announcements to the announce batch. // // NOTE: must be run as a goroutine. -func (d *AuthenticatedGossiper) handleNetworkMessages(nMsg *networkMsg, - deDuped *deDupedAnnouncements, jobID JobID) { +func (d *AuthenticatedGossiper) handleNetworkMessages(ctx context.Context, + nMsg *networkMsg, deDuped *deDupedAnnouncements, jobID JobID) { defer d.wg.Done() 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 // new announcement from our PoV or an edges to a prior vertex/edge we // previously proceeded. - newAnns, allow := d.processNetworkAnnouncement(nMsg) + newAnns, allow := d.processNetworkAnnouncement(ctx, nMsg) log.Tracef("Processed network message %s, returned "+ "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 // RebroadcastInterval. We also check if a refreshed node announcement should // 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 // within the prune interval or re-broadcast interval. 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 // ChannelUpdate to broadcast. chanAnn, chanUpdate, err := d.updateChannel( - chanToUpdate.info, chanToUpdate.edge, + ctx, chanToUpdate.info, chanToUpdate.edge, ) if err != nil { 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 // 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 "+ "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 // provided list of edges and updates the backing ChannelGraphSource. -func (d *AuthenticatedGossiper) processChanPolicyUpdate( +func (d *AuthenticatedGossiper) processChanPolicyUpdate(ctx context.Context, edgesToUpdate []EdgeWithInfo) ([]networkMsg, error) { var chanUpdates []networkMsg @@ -1829,7 +1843,7 @@ func (d *AuthenticatedGossiper) processChanPolicyUpdate( // we'll re-sign and update the backing ChannelGraphSource, and // retrieve our ChannelUpdate to broadcast. _, chanUpdate, err := d.updateChannel( - edgeInfo.Info, edgeInfo.Edge, + ctx, edgeInfo.Info, edgeInfo.Edge, ) if err != nil { return nil, err @@ -1875,7 +1889,7 @@ func (d *AuthenticatedGossiper) processChanPolicyUpdate( edgeInfo.Info, chanUpdate.ChannelFlags, ) err := d.reliableSender.sendMessage( - chanUpdate, remotePubKey, + ctx, chanUpdate, remotePubKey, ) if err != nil { 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 // to receive the remote peer's proof, while the remote peer is able to fully // assemble the proof and craft the ChannelAnnouncement. -func (d *AuthenticatedGossiper) processRejectedEdge( +func (d *AuthenticatedGossiper) processRejectedEdge(_ context.Context, chanAnnMsg *lnwire.ChannelAnnouncement1, 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 // graph. -func (d *AuthenticatedGossiper) addNode(msg *lnwire.NodeAnnouncement, - op ...batch.SchedulerOption) error { +func (d *AuthenticatedGossiper) addNode(_ context.Context, + msg *lnwire.NodeAnnouncement, op ...batch.SchedulerOption) error { if err := netann.ValidateNodeAnn(msg); err != nil { 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 // boolean returned indicates whether any dependents of the announcement should // attempt to be processed as well. -func (d *AuthenticatedGossiper) processNetworkAnnouncement( +func (d *AuthenticatedGossiper) processNetworkAnnouncement(ctx context.Context, nMsg *networkMsg) ([]networkMsg, bool) { // 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 // updating previously advertised information. 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 // *creation* of a new channel within the network. This only advertises // the existence of a channel and not yet the routing policies in // either direction of the channel. 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 // that the directional information for an already known channel has // been updated. 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 // willingness of nodes involved in the funding of a channel to // announce this new channel to the rest of the world. case *lnwire.AnnounceSignatures1: - return d.handleAnnSig(nMsg, msg) + return d.handleAnnSig(ctx, nMsg, msg) default: 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 // should be inspected. -func (d *AuthenticatedGossiper) processZombieUpdate( +func (d *AuthenticatedGossiper) processZombieUpdate(_ context.Context, chanInfo *models.ChannelEdgeInfo, scid lnwire.ShortChannelID, msg *lnwire.ChannelUpdate1) error { @@ -2192,7 +2206,7 @@ func (d *AuthenticatedGossiper) processZombieUpdate( // fetchNodeAnn fetches the latest signed node announcement from our point of // 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) { node, err := d.cfg.Graph.FetchLightningNode(pubKey) @@ -2205,7 +2219,9 @@ func (d *AuthenticatedGossiper) fetchNodeAnn( // isMsgStale determines whether a message retrieved from the backing // 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) { case *lnwire.AnnounceSignatures1: 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 // 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, *lnwire.ChannelUpdate1, error) { @@ -2414,8 +2431,8 @@ func (d *AuthenticatedGossiper) latestHeight() uint32 { } // handleNodeAnnouncement processes a new node announcement. -func (d *AuthenticatedGossiper) handleNodeAnnouncement(nMsg *networkMsg, - nodeAnn *lnwire.NodeAnnouncement, +func (d *AuthenticatedGossiper) handleNodeAnnouncement(ctx context.Context, + nMsg *networkMsg, nodeAnn *lnwire.NodeAnnouncement, ops []batch.SchedulerOption) ([]networkMsg, bool) { timestamp := time.Unix(int64(nodeAnn.Timestamp), 0) @@ -2432,7 +2449,7 @@ func (d *AuthenticatedGossiper) handleNodeAnnouncement(nMsg *networkMsg, 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, err) @@ -2487,8 +2504,10 @@ func (d *AuthenticatedGossiper) handleNodeAnnouncement(nMsg *networkMsg, } // 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) { scid := ann.ShortChannelID @@ -2680,7 +2699,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg, // add an alias ChannelAnnouncement from the gossiper. if !(d.cfg.AssumeChannelValid || d.cfg.IsAlias(scid)) { //nolint:nestif op, capacity, script, err := d.validateFundingTransaction( - ann, tapscriptRoot, + ctx, ann, tapscriptRoot, ) if err != nil { defer d.channelMtx.Unlock(scid.ToUint64()) @@ -2802,7 +2821,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg, if graph.IsError(err, graph.ErrIgnored) { // Attempt to process the rejected message to see if we // get any new announcements. - anns, rErr := d.processRejectedEdge(ann, proof) + anns, rErr := d.processRejectedEdge(ctx, ann, proof) if rErr != nil { key := newRejectCacheKey( scid.ToUint64(), @@ -2945,8 +2964,10 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg, } // 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) { log.Debugf("Processing ChannelUpdate: peer=%v, short_chan_id=%v, ", @@ -3052,7 +3073,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg, break case errors.Is(err, graphdb.ErrZombieEdge): - err = d.processZombieUpdate(chanInfo, graphScid, upd) + err = d.processZombieUpdate(ctx, chanInfo, graphScid, upd) if err != nil { log.Debug(err) nMsg.err <- err @@ -3312,7 +3333,7 @@ func (d *AuthenticatedGossiper) handleChanUpdate(nMsg *networkMsg, // Now we'll attempt to send the channel update message // reliably to the remote peer in the background, so that we // 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 { err := fmt.Errorf("unable to reliably send %v for "+ "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. -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 + 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 // method that will attempt to deliver the proof when it comes // online. - err := d.reliableSender.sendMessage(ann, remotePubKey) + err := d.reliableSender.sendMessage(ctx, ann, remotePubKey) if err != nil { err := fmt.Errorf("unable to reliably send %v for "+ "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 // updates and announcement signatures since we send those directly to // 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 { log.Debugf("Unable to fetch node announcement for %x: %v", 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 { log.Debugf("Unable to fetch node announcement for %x: %v", 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 // the channel announcement proof. The transaction's outpoint and value are // 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, tapscriptRoot fn.Option[chainhash.Hash]) (wire.OutPoint, btcutil.Amount, []byte, error) { diff --git a/discovery/gossiper_test.go b/discovery/gossiper_test.go index b48ea8dbd..399010991 100644 --- a/discovery/gossiper_test.go +++ b/discovery/gossiper_test.go @@ -2,6 +2,7 @@ package discovery import ( "bytes" + "context" "encoding/hex" "fmt" prand "math/rand" @@ -1019,9 +1020,10 @@ func createTestCtx(t *testing.T, startHeight uint32, isChanPeer bool) ( // the router subsystem. func TestProcessAnnouncement(t *testing.T) { t.Parallel() + ctx := context.Background() timestamp := testTimestamp - ctx, err := createTestCtx(t, 0, false) + tCtx, err := createTestCtx(t, 0, false) require.NoError(t, err, "can't create context") assertSenderExistence := func(sender *btcec.PublicKey, msg msgWithSenders) { @@ -1037,11 +1039,11 @@ func TestProcessAnnouncement(t *testing.T) { // First, we'll craft a valid remote channel announcement and send it to // the gossiper so that it can be processed. - ca, err := ctx.createRemoteChannelAnnouncement(0) + ca, err := tCtx.createRemoteChannelAnnouncement(0) require.NoError(t, err, "can't create channel announcement") select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement(ca, nodePeer): + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement(ctx, ca, nodePeer): case <-time.After(2 * time.Second): t.Fatal("remote announcement not processed") } @@ -1050,13 +1052,13 @@ func TestProcessAnnouncement(t *testing.T) { // The announcement should be broadcast and included in our local view // of the graph. select { - case msg := <-ctx.broadcastedMessage: + case msg := <-tCtx.broadcastedMessage: assertSenderExistence(nodePeer.IdentityKey(), msg) case <-time.After(2 * trickleDelay): t.Fatal("announcement wasn't proceeded") } - if len(ctx.router.infos) != 1 { + if len(tCtx.router.infos) != 1 { t.Fatalf("edge wasn't added to router: %v", err) } @@ -1067,7 +1069,7 @@ func TestProcessAnnouncement(t *testing.T) { // We send an invalid channel update and expect it to fail. select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement(ua, nodePeer): + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement(ctx, ua, nodePeer): case <-time.After(2 * time.Second): t.Fatal("remote announcement not processed") } @@ -1076,7 +1078,7 @@ func TestProcessAnnouncement(t *testing.T) { // We should not broadcast the channel update. select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("gossiper should not have broadcast channel update") case <-time.After(2 * trickleDelay): } @@ -1087,7 +1089,7 @@ func TestProcessAnnouncement(t *testing.T) { require.NoError(t, err, "can't create update announcement") select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement(ua, nodePeer): + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement(ctx, ua, nodePeer): case <-time.After(2 * time.Second): t.Fatal("remote announcement not processed") } @@ -1095,13 +1097,13 @@ func TestProcessAnnouncement(t *testing.T) { // The channel policy should be broadcast to the rest of the network. select { - case msg := <-ctx.broadcastedMessage: + case msg := <-tCtx.broadcastedMessage: assertSenderExistence(nodePeer.IdentityKey(), msg) case <-time.After(2 * trickleDelay): t.Fatal("announcement wasn't proceeded") } - if len(ctx.router.edges) != 1 { + if len(tCtx.router.edges) != 1 { t.Fatalf("edge update wasn't added to router: %v", err) } @@ -1110,7 +1112,7 @@ func TestProcessAnnouncement(t *testing.T) { require.NoError(t, err, "can't create node announcement") select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement(na, nodePeer): + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement(ctx, na, nodePeer): case <-time.After(2 * time.Second): t.Fatal("remote announcement not processed") } @@ -1119,13 +1121,13 @@ func TestProcessAnnouncement(t *testing.T) { // It should also be broadcast to the network and included in our local // view of the graph. select { - case msg := <-ctx.broadcastedMessage: + case msg := <-tCtx.broadcastedMessage: assertSenderExistence(nodePeer.IdentityKey(), msg) case <-time.After(2 * trickleDelay): t.Fatal("announcement wasn't proceeded") } - if len(ctx.router.nodes) != 1 { + if len(tCtx.router.nodes) != 1 { t.Fatalf("node wasn't added to router: %v", err) } } @@ -1134,10 +1136,11 @@ func TestProcessAnnouncement(t *testing.T) { // propagated to the router subsystem. func TestPrematureAnnouncement(t *testing.T) { t.Parallel() + ctx := context.Background() timestamp := testTimestamp - ctx, err := createTestCtx(t, 0, false) + tCtx, err := createTestCtx(t, 0, false) require.NoError(t, err, "can't create context") _, err = createNodeAnnouncement(remoteKeyPriv1, timestamp) @@ -1149,18 +1152,18 @@ func TestPrematureAnnouncement(t *testing.T) { // remote side, but block height of this announcement is greater than // highest know to us, for that reason it should be ignored and not // added to the router. - ca, err := ctx.createRemoteChannelAnnouncement( + ca, err := tCtx.createRemoteChannelAnnouncement( 1, withFundingTxPrep(fundingTxPrepTypeNone), ) require.NoError(t, err, "can't create channel announcement") select { - case <-ctx.gossiper.ProcessRemoteAnnouncement(ca, nodePeer): + case <-tCtx.gossiper.ProcessRemoteAnnouncement(ctx, ca, nodePeer): case <-time.After(time.Second): t.Fatal("announcement was not processed") } - if len(ctx.router.infos) != 0 { + if len(tCtx.router.infos) != 0 { t.Fatal("edge was added to router") } } @@ -1169,69 +1172,70 @@ func TestPrematureAnnouncement(t *testing.T) { // properly processes partial and fully announcement signatures message. func TestSignatureAnnouncementLocalFirst(t *testing.T) { t.Parallel() + ctx := context.Background() - ctx, err := createTestCtx(t, proofMatureDelta, false) + tCtx, err := createTestCtx(t, proofMatureDelta, false) require.NoError(t, err, "can't create context") // Set up a channel that we can use to inspect the messages sent // directly from the gossiper. sentMsgs := make(chan lnwire.Message, 10) - ctx.gossiper.reliableSender.cfg.NotifyWhenOnline = func(target [33]byte, - peerChan chan<- lnpeer.Peer) { + tCtx.gossiper.reliableSender.cfg.NotifyWhenOnline = func( + target [33]byte, peerChan chan<- lnpeer.Peer) { pk, _ := btcec.ParsePubKey(target[:]) select { case peerChan <- &mockPeer{ - pk, sentMsgs, ctx.gossiper.quit, atomic.Bool{}, + pk, sentMsgs, tCtx.gossiper.quit, atomic.Bool{}, }: - case <-ctx.gossiper.quit: + case <-tCtx.gossiper.quit: } } - batch, err := ctx.createLocalAnnouncements(0) + batch, err := tCtx.createLocalAnnouncements(0) require.NoError(t, err, "can't generate announcements") remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) require.NoError(t, err, "unable to parse pubkey") remotePeer := &mockPeer{ - remoteKey, sentMsgs, ctx.gossiper.quit, atomic.Bool{}, + remoteKey, sentMsgs, tCtx.gossiper.quit, atomic.Bool{}, } // Recreate lightning network topology. Initialize router with channel // between two nodes. select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.chanAnn): + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanAnn): case <-time.After(2 * time.Second): t.Fatal("did not process local announcement") } require.NoError(t, err, "unable to process channel ann") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel announcement was broadcast") case <-time.After(2 * trickleDelay): } select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.chanUpdAnn1): + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanUpdAnn1): case <-time.After(2 * time.Second): t.Fatal("did not process local announcement") } require.NoError(t, err, "unable to process channel update") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel update announcement was broadcast") case <-time.After(2 * trickleDelay): } select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.nodeAnn1): + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.nodeAnn1): case <-time.After(2 * time.Second): t.Fatal("did not process local announcement") } require.NoError(t, err, "unable to process node ann") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("node announcement was broadcast") case <-time.After(2 * trickleDelay): } @@ -1247,29 +1251,29 @@ func TestSignatureAnnouncementLocalFirst(t *testing.T) { } select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.chanUpdAnn2, remotePeer, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanUpdAnn2, remotePeer, ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } require.NoError(t, err, "unable to process channel update") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel update announcement was broadcast") case <-time.After(2 * trickleDelay): } select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.nodeAnn2, remotePeer, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.nodeAnn2, remotePeer, ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } require.NoError(t, err, "unable to process node ann") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("node announcement was broadcast") case <-time.After(2 * trickleDelay): } @@ -1277,20 +1281,22 @@ func TestSignatureAnnouncementLocalFirst(t *testing.T) { // Pretending that we receive local channel announcement from funding // manager, thereby kick off the announcement exchange process. select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.localProofAnn): + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( + batch.localProofAnn, + ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } require.NoError(t, err, "unable to process local proof") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("announcements were broadcast") case <-time.After(2 * trickleDelay): } number := 0 - if err := ctx.gossiper.cfg.WaitingProofStore.ForAll( + if err := tCtx.gossiper.cfg.WaitingProofStore.ForAll( func(*channeldb.WaitingProof) error { number++ return nil @@ -1307,8 +1313,8 @@ func TestSignatureAnnouncementLocalFirst(t *testing.T) { } select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.remoteProofAnn, remotePeer, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.remoteProofAnn, remotePeer, ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") @@ -1317,14 +1323,14 @@ func TestSignatureAnnouncementLocalFirst(t *testing.T) { for i := 0; i < 5; i++ { select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: case <-time.After(time.Second): t.Fatal("announcement wasn't broadcast") } } number = 0 - if err := ctx.gossiper.cfg.WaitingProofStore.ForAll( + if err := tCtx.gossiper.cfg.WaitingProofStore.ForAll( func(*channeldb.WaitingProof) error { number++ return nil @@ -1345,33 +1351,34 @@ func TestSignatureAnnouncementLocalFirst(t *testing.T) { // processes announcement with unknown channel ids. func TestOrphanSignatureAnnouncement(t *testing.T) { t.Parallel() + ctx := context.Background() - ctx, err := createTestCtx(t, proofMatureDelta, false) + tCtx, err := createTestCtx(t, proofMatureDelta, false) require.NoError(t, err, "can't create context") // Set up a channel that we can use to inspect the messages sent // directly from the gossiper. sentMsgs := make(chan lnwire.Message, 10) - ctx.gossiper.reliableSender.cfg.NotifyWhenOnline = func(target [33]byte, - peerChan chan<- lnpeer.Peer) { + tCtx.gossiper.reliableSender.cfg.NotifyWhenOnline = func( + target [33]byte, peerChan chan<- lnpeer.Peer) { pk, _ := btcec.ParsePubKey(target[:]) select { case peerChan <- &mockPeer{ - pk, sentMsgs, ctx.gossiper.quit, atomic.Bool{}, + pk, sentMsgs, tCtx.gossiper.quit, atomic.Bool{}, }: - case <-ctx.gossiper.quit: + case <-tCtx.gossiper.quit: } } - batch, err := ctx.createLocalAnnouncements(0) + batch, err := tCtx.createLocalAnnouncements(0) require.NoError(t, err, "can't generate announcements") remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) require.NoError(t, err, "unable to parse pubkey") remotePeer := &mockPeer{ - remoteKey, sentMsgs, ctx.gossiper.quit, atomic.Bool{}, + remoteKey, sentMsgs, tCtx.gossiper.quit, atomic.Bool{}, } // Pretending that we receive local channel announcement from funding @@ -1379,15 +1386,16 @@ func TestOrphanSignatureAnnouncement(t *testing.T) { // this case the announcement should be added in the orphan batch // because we haven't announce the channel yet. select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement(batch.remoteProofAnn, - remotePeer): + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.remoteProofAnn, remotePeer, + ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } require.NoError(t, err, "unable to proceed announcement") number := 0 - if err := ctx.gossiper.cfg.WaitingProofStore.ForAll( + if err := tCtx.gossiper.cfg.WaitingProofStore.ForAll( func(*channeldb.WaitingProof) error { number++ return nil @@ -1406,7 +1414,7 @@ func TestOrphanSignatureAnnouncement(t *testing.T) { // Recreate lightning network topology. Initialize router with channel // between two nodes. select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.chanAnn): + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanAnn): case <-time.After(2 * time.Second): t.Fatal("did not process local announcement") } @@ -1414,32 +1422,32 @@ func TestOrphanSignatureAnnouncement(t *testing.T) { require.NoError(t, err, "unable to process") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel announcement was broadcast") case <-time.After(2 * trickleDelay): } select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.chanUpdAnn1): + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanUpdAnn1): case <-time.After(2 * time.Second): t.Fatal("did not process local announcement") } require.NoError(t, err, "unable to process") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel update announcement was broadcast") case <-time.After(2 * trickleDelay): } select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.nodeAnn1): + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.nodeAnn1): case <-time.After(2 * time.Second): t.Fatal("did not process local announcement") } require.NoError(t, err, "unable to process node ann") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("node announcement was broadcast") case <-time.After(2 * trickleDelay): } @@ -1455,28 +1463,29 @@ func TestOrphanSignatureAnnouncement(t *testing.T) { } select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement(batch.chanUpdAnn2, - remotePeer): + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanUpdAnn2, remotePeer, + ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } require.NoError(t, err, "unable to process node ann") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel update announcement was broadcast") case <-time.After(2 * trickleDelay): } select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.nodeAnn2, remotePeer, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.nodeAnn2, remotePeer, ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } require.NoError(t, err, "unable to process") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("node announcement announcement was broadcast") case <-time.After(2 * trickleDelay): } @@ -1484,7 +1493,9 @@ func TestOrphanSignatureAnnouncement(t *testing.T) { // After that we process local announcement, and waiting to receive // the channel announcement. select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.localProofAnn): + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( + batch.localProofAnn, + ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } @@ -1502,14 +1513,14 @@ func TestOrphanSignatureAnnouncement(t *testing.T) { // should be broadcasting the final channel announcements. for i := 0; i < 5; i++ { select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: case <-time.After(time.Second): t.Fatal("announcement wasn't broadcast") } } number = 0 - if err := ctx.gossiper.cfg.WaitingProofStore.ForAll( + if err := tCtx.gossiper.cfg.WaitingProofStore.ForAll( func(p *channeldb.WaitingProof) error { number++ return nil @@ -1532,11 +1543,12 @@ func TestOrphanSignatureAnnouncement(t *testing.T) { // assembled. func TestSignatureAnnouncementRetryAtStartup(t *testing.T) { t.Parallel() + ctx := context.Background() - ctx, err := createTestCtx(t, proofMatureDelta, false) + tCtx, err := createTestCtx(t, proofMatureDelta, false) require.NoError(t, err, "can't create context") - batch, err := ctx.createLocalAnnouncements(0) + batch, err := tCtx.createLocalAnnouncements(0) require.NoError(t, err, "can't generate announcements") remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) @@ -1545,7 +1557,7 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) { // Set up a channel to intercept the messages sent to the remote peer. sentToPeer := make(chan lnwire.Message, 1) remotePeer := &mockPeer{ - remoteKey, sentToPeer, ctx.gossiper.quit, atomic.Bool{}, + remoteKey, sentToPeer, tCtx.gossiper.quit, atomic.Bool{}, } // Since the reliable send to the remote peer of the local channel proof @@ -1553,7 +1565,7 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) { // channel through which it gets sent to control exactly when to // dispatch it. notifyPeers := make(chan chan<- lnpeer.Peer, 1) - ctx.gossiper.reliableSender.cfg.NotifyWhenOnline = func(peer [33]byte, + tCtx.gossiper.reliableSender.cfg.NotifyWhenOnline = func(peer [33]byte, connectedChan chan<- lnpeer.Peer) { notifyPeers <- connectedChan } @@ -1561,13 +1573,13 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) { // Recreate lightning network topology. Initialize router with channel // between two nodes. select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.chanAnn): + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanAnn): case <-time.After(2 * time.Second): t.Fatal("did not process local announcement") } require.NoError(t, err, "unable to process channel ann") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel announcement was broadcast") case <-time.After(2 * trickleDelay): } @@ -1575,7 +1587,7 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) { // Pretending that we receive local channel announcement from funding // manager, thereby kick off the announcement exchange process. select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement( + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( batch.localProofAnn, ): case <-time.After(2 * time.Second): @@ -1597,7 +1609,7 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) { // The proof should not be broadcast yet since we're still missing the // remote party's. select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("announcements were broadcast") case <-time.After(2 * trickleDelay): } @@ -1610,7 +1622,7 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) { } number := 0 - if err := ctx.gossiper.cfg.WaitingProofStore.ForAll( + if err := tCtx.gossiper.cfg.WaitingProofStore.ForAll( func(*channeldb.WaitingProof) error { number++ return nil @@ -1629,7 +1641,7 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) { // Restart the gossiper and restore its original NotifyWhenOnline and // NotifyWhenOffline methods. This should trigger a new attempt to send // the message to the peer. - ctx.gossiper.Stop() + require.NoError(t, tCtx.gossiper.Stop()) isAlias := func(lnwire.ShortChannelID) bool { return false @@ -1653,19 +1665,19 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) { //nolint:ll gossiper := New(Config{ - Notifier: ctx.gossiper.cfg.Notifier, - Broadcast: ctx.gossiper.cfg.Broadcast, - NotifyWhenOnline: ctx.gossiper.reliableSender.cfg.NotifyWhenOnline, - NotifyWhenOffline: ctx.gossiper.reliableSender.cfg.NotifyWhenOffline, - FetchSelfAnnouncement: ctx.gossiper.cfg.FetchSelfAnnouncement, - UpdateSelfAnnouncement: ctx.gossiper.cfg.UpdateSelfAnnouncement, - Graph: ctx.gossiper.cfg.Graph, + Notifier: tCtx.gossiper.cfg.Notifier, + Broadcast: tCtx.gossiper.cfg.Broadcast, + NotifyWhenOnline: tCtx.gossiper.reliableSender.cfg.NotifyWhenOnline, + NotifyWhenOffline: tCtx.gossiper.reliableSender.cfg.NotifyWhenOffline, + FetchSelfAnnouncement: tCtx.gossiper.cfg.FetchSelfAnnouncement, + UpdateSelfAnnouncement: tCtx.gossiper.cfg.UpdateSelfAnnouncement, + Graph: tCtx.gossiper.cfg.Graph, TrickleDelay: trickleDelay, RetransmitTicker: ticker.NewForce(retransmitDelay), RebroadcastInterval: rebroadcastInterval, ProofMatureDelta: proofMatureDelta, - WaitingProofStore: ctx.gossiper.cfg.WaitingProofStore, - MessageStore: ctx.gossiper.cfg.MessageStore, + WaitingProofStore: tCtx.gossiper.cfg.WaitingProofStore, + MessageStore: tCtx.gossiper.cfg.MessageStore, RotateTicker: ticker.NewForce(DefaultSyncerRotationInterval), HistoricalSyncTicker: ticker.NewForce(DefaultHistoricalSyncInterval), NumActiveSyncers: 3, @@ -1676,8 +1688,8 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) { FindBaseByAlias: findBaseByAlias, GetAlias: getAlias, }, &keychain.KeyDescriptor{ - PubKey: ctx.gossiper.selfKey, - KeyLocator: ctx.gossiper.selfKeyLoc, + PubKey: tCtx.gossiper.selfKey, + KeyLocator: tCtx.gossiper.selfKeyLoc, }) require.NoError(t, err, "unable to recreate gossiper") if err := gossiper.Start(); err != nil { @@ -1689,8 +1701,8 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) { // broadcast. gossiper.syncMgr.markGraphSynced() - ctx.gossiper = gossiper - remotePeer.quit = ctx.gossiper.quit + tCtx.gossiper = gossiper + remotePeer.quit = tCtx.gossiper.quit // After starting up, the gossiper will see that it has a proof in the // WaitingProofStore, and will retry sending its part to the remote. @@ -1728,8 +1740,8 @@ out: // Now exchanging the remote channel proof, the channel announcement // broadcast should continue as normal. select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.remoteProofAnn, remotePeer, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.remoteProofAnn, remotePeer, ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") @@ -1739,13 +1751,13 @@ out: } select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: case <-time.After(time.Second): t.Fatal("announcement wasn't broadcast") } number = 0 - if err := ctx.gossiper.cfg.WaitingProofStore.ForAll( + if err := tCtx.gossiper.cfg.WaitingProofStore.ForAll( func(*channeldb.WaitingProof) error { number++ return nil @@ -1767,11 +1779,12 @@ out: // the full proof (ChannelAnnouncement) to the remote peer. func TestSignatureAnnouncementFullProofWhenRemoteProof(t *testing.T) { t.Parallel() + ctx := context.Background() - ctx, err := createTestCtx(t, proofMatureDelta, false) + tCtx, err := createTestCtx(t, proofMatureDelta, false) require.NoError(t, err, "can't create context") - batch, err := ctx.createLocalAnnouncements(0) + batch, err := tCtx.createLocalAnnouncements(0) require.NoError(t, err, "can't generate announcements") remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) @@ -1781,12 +1794,12 @@ func TestSignatureAnnouncementFullProofWhenRemoteProof(t *testing.T) { // gossiper to the remote peer. sentToPeer := make(chan lnwire.Message, 1) remotePeer := &mockPeer{ - remoteKey, sentToPeer, ctx.gossiper.quit, atomic.Bool{}, + remoteKey, sentToPeer, tCtx.gossiper.quit, atomic.Bool{}, } // Override NotifyWhenOnline to return the remote peer which we expect // meesages to be sent to. - ctx.gossiper.reliableSender.cfg.NotifyWhenOnline = func(peer [33]byte, + tCtx.gossiper.reliableSender.cfg.NotifyWhenOnline = func(peer [33]byte, peerChan chan<- lnpeer.Peer) { peerChan <- remotePeer @@ -1795,7 +1808,7 @@ func TestSignatureAnnouncementFullProofWhenRemoteProof(t *testing.T) { // Recreate lightning network topology. Initialize router with channel // between two nodes. select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement( + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( batch.chanAnn, ): case <-time.After(2 * time.Second): @@ -1803,13 +1816,13 @@ func TestSignatureAnnouncementFullProofWhenRemoteProof(t *testing.T) { } require.NoError(t, err, "unable to process channel ann") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel announcement was broadcast") case <-time.After(2 * trickleDelay): } select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement( + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( batch.chanUpdAnn1, ): case <-time.After(2 * time.Second): @@ -1817,7 +1830,7 @@ func TestSignatureAnnouncementFullProofWhenRemoteProof(t *testing.T) { } require.NoError(t, err, "unable to process channel update") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel update announcement was broadcast") case <-time.After(2 * trickleDelay): } @@ -1830,7 +1843,7 @@ func TestSignatureAnnouncementFullProofWhenRemoteProof(t *testing.T) { } select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement( + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( batch.nodeAnn1, ): case <-time.After(2 * time.Second): @@ -1840,35 +1853,34 @@ func TestSignatureAnnouncementFullProofWhenRemoteProof(t *testing.T) { t.Fatalf("unable to process node ann:%v", err) } select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("node announcement was broadcast") case <-time.After(2 * trickleDelay): } select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.chanUpdAnn2, remotePeer, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanUpdAnn2, remotePeer, ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } require.NoError(t, err, "unable to process channel update") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel update announcement was broadcast") case <-time.After(2 * trickleDelay): } - select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.nodeAnn2, remotePeer, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.nodeAnn2, remotePeer, ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } require.NoError(t, err, "unable to process node ann") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("node announcement was broadcast") case <-time.After(2 * trickleDelay): } @@ -1876,7 +1888,7 @@ func TestSignatureAnnouncementFullProofWhenRemoteProof(t *testing.T) { // Pretending that we receive local channel announcement from funding // manager, thereby kick off the announcement exchange process. select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement( + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( batch.localProofAnn, ): case <-time.After(2 * time.Second): @@ -1885,8 +1897,8 @@ func TestSignatureAnnouncementFullProofWhenRemoteProof(t *testing.T) { require.NoError(t, err, "unable to process local proof") select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.remoteProofAnn, remotePeer, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.remoteProofAnn, remotePeer, ): case <-time.After(2 * time.Second): t.Fatal("did not process local announcement") @@ -1904,14 +1916,14 @@ func TestSignatureAnnouncementFullProofWhenRemoteProof(t *testing.T) { // All channel and node announcements should be broadcast. for i := 0; i < 5; i++ { select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: case <-time.After(time.Second): t.Fatal("announcement wasn't broadcast") } } number := 0 - if err := ctx.gossiper.cfg.WaitingProofStore.ForAll( + if err := tCtx.gossiper.cfg.WaitingProofStore.ForAll( func(*channeldb.WaitingProof) error { number++ return nil @@ -1930,8 +1942,8 @@ func TestSignatureAnnouncementFullProofWhenRemoteProof(t *testing.T) { // Now give the gossiper the remote proof yet again. This should // trigger a send of the full ChannelAnnouncement. select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.remoteProofAnn, remotePeer, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.remoteProofAnn, remotePeer, ): case <-time.After(2 * time.Second): t.Fatal("did not process local announcement") @@ -2209,25 +2221,26 @@ func TestDeDuplicatedAnnouncements(t *testing.T) { // announcements for nodes who do not intend to publicly advertise themselves. func TestForwardPrivateNodeAnnouncement(t *testing.T) { t.Parallel() + ctx := context.Background() const ( startingHeight = 100 timestamp = 123456 ) - ctx, err := createTestCtx(t, startingHeight, false) + tCtx, err := createTestCtx(t, startingHeight, false) require.NoError(t, err, "can't create context") // We'll start off by processing a channel announcement without a proof // (i.e., an unadvertised channel), followed by a node announcement for // this same channel announcement. - chanAnn := ctx.createAnnouncementWithoutProof( + chanAnn := tCtx.createAnnouncementWithoutProof( startingHeight-2, selfKeyDesc.PubKey, remoteKeyPub1, ) pubKey := remoteKeyPriv1.PubKey() select { - case err := <-ctx.gossiper.ProcessLocalAnnouncement(chanAnn): + case err := <-tCtx.gossiper.ProcessLocalAnnouncement(chanAnn): if err != nil { t.Fatalf("unable to process local announcement: %v", err) } @@ -2238,7 +2251,7 @@ func TestForwardPrivateNodeAnnouncement(t *testing.T) { // The gossiper should not broadcast the announcement due to it not // having its announcement signatures. select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("gossiper should not have broadcast channel announcement") case <-time.After(2 * trickleDelay): } @@ -2247,7 +2260,7 @@ func TestForwardPrivateNodeAnnouncement(t *testing.T) { require.NoError(t, err, "unable to create node announcement") select { - case err := <-ctx.gossiper.ProcessLocalAnnouncement(nodeAnn): + case err := <-tCtx.gossiper.ProcessLocalAnnouncement(nodeAnn): if err != nil { t.Fatalf("unable to process remote announcement: %v", err) } @@ -2258,7 +2271,7 @@ func TestForwardPrivateNodeAnnouncement(t *testing.T) { // The gossiper should also not broadcast the node announcement due to // it not being part of any advertised channels. select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("gossiper should not have broadcast node announcement") case <-time.After(2 * trickleDelay): } @@ -2267,14 +2280,16 @@ func TestForwardPrivateNodeAnnouncement(t *testing.T) { // by opening a public channel on the network. We'll create a // ChannelAnnouncement and hand it off to the gossiper in order to // process it. - remoteChanAnn, err := ctx.createRemoteChannelAnnouncement( + remoteChanAnn, err := tCtx.createRemoteChannelAnnouncement( startingHeight - 1, ) require.NoError(t, err, "unable to create remote channel announcement") peer := &mockPeer{pubKey, nil, nil, atomic.Bool{}} select { - case err := <-ctx.gossiper.ProcessRemoteAnnouncement(remoteChanAnn, peer): + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, remoteChanAnn, peer, + ): if err != nil { t.Fatalf("unable to process remote announcement: %v", err) } @@ -2283,7 +2298,7 @@ func TestForwardPrivateNodeAnnouncement(t *testing.T) { } select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: case <-time.After(2 * trickleDelay): t.Fatal("gossiper should have broadcast the channel announcement") } @@ -2294,7 +2309,9 @@ func TestForwardPrivateNodeAnnouncement(t *testing.T) { require.NoError(t, err, "unable to create node announcement") select { - case err := <-ctx.gossiper.ProcessRemoteAnnouncement(nodeAnn, peer): + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, nodeAnn, peer, + ): if err != nil { t.Fatalf("unable to process remote announcement: %v", err) } @@ -2303,7 +2320,7 @@ func TestForwardPrivateNodeAnnouncement(t *testing.T) { } select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: case <-time.After(2 * trickleDelay): t.Fatal("gossiper should have broadcast the node announcement") } @@ -2313,13 +2330,14 @@ func TestForwardPrivateNodeAnnouncement(t *testing.T) { // zombie edges. func TestRejectZombieEdge(t *testing.T) { t.Parallel() + ctx := context.Background() // We'll start by creating our test context with a batch of // announcements. - ctx, err := createTestCtx(t, 0, false) + tCtx, err := createTestCtx(t, 0, false) require.NoError(t, err, "unable to create test context") - batch, err := ctx.createRemoteAnnouncements(0) + batch, err := tCtx.createRemoteAnnouncements(0) require.NoError(t, err, "unable to create announcements") remotePeer := &mockPeer{pk: remoteKeyPriv2.PubKey()} @@ -2329,8 +2347,8 @@ func TestRejectZombieEdge(t *testing.T) { processAnnouncements := func(isZombie bool) { t.Helper() - errChan := ctx.gossiper.ProcessRemoteAnnouncement( - batch.chanAnn, remotePeer, + errChan := tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanAnn, remotePeer, ) select { case err := <-errChan: @@ -2346,7 +2364,7 @@ func TestRejectZombieEdge(t *testing.T) { t.Fatal("expected to process channel announcement") } select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: if isZombie { t.Fatal("expected to not broadcast zombie " + "channel announcement") @@ -2358,8 +2376,8 @@ func TestRejectZombieEdge(t *testing.T) { } } - errChan = ctx.gossiper.ProcessRemoteAnnouncement( - batch.chanUpdAnn2, remotePeer, + errChan = tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanUpdAnn2, remotePeer, ) select { case err := <-errChan: @@ -2375,7 +2393,7 @@ func TestRejectZombieEdge(t *testing.T) { t.Fatal("expected to process channel update") } select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: if isZombie { t.Fatal("expected to not broadcast zombie " + "channel update") @@ -2392,7 +2410,7 @@ func TestRejectZombieEdge(t *testing.T) { // zombie within the router. This should reject any announcements for // this edge while it remains as a zombie. chanID := batch.chanAnn.ShortChannelID - err = ctx.router.MarkEdgeZombie( + err = tCtx.router.MarkEdgeZombie( chanID, batch.chanAnn.NodeID1, batch.chanAnn.NodeID2, ) if err != nil { @@ -2403,7 +2421,7 @@ func TestRejectZombieEdge(t *testing.T) { // If we then mark the edge as live, the edge's zombie status should be // overridden and the announcements should be processed. - if err := ctx.router.MarkEdgeLive(chanID); err != nil { + if err := tCtx.router.MarkEdgeLive(chanID); err != nil { t.Fatalf("unable mark channel %v as zombie: %v", chanID, err) } @@ -2414,13 +2432,14 @@ func TestRejectZombieEdge(t *testing.T) { // becomes live by receiving a fresh update. func TestProcessZombieEdgeNowLive(t *testing.T) { t.Parallel() + ctx := context.Background() // We'll start by creating our test context with a batch of // announcements. - ctx, err := createTestCtx(t, 0, false) + tCtx, err := createTestCtx(t, 0, false) require.NoError(t, err, "unable to create test context") - batch, err := ctx.createRemoteAnnouncements(0) + batch, err := tCtx.createRemoteAnnouncements(0) require.NoError(t, err, "unable to create announcements") remotePeer := &mockPeer{pk: remoteKeyPriv1.PubKey()} @@ -2434,8 +2453,8 @@ func TestProcessZombieEdgeNowLive(t *testing.T) { processAnnouncement := func(ann lnwire.Message, isZombie, expectsErr bool) { t.Helper() - errChan := ctx.gossiper.ProcessRemoteAnnouncement( - ann, remotePeer, + errChan := tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, ann, remotePeer, ) var err error @@ -2453,7 +2472,7 @@ func TestProcessZombieEdgeNowLive(t *testing.T) { } select { - case msgWithSenders := <-ctx.broadcastedMessage: + case msgWithSenders := <-tCtx.broadcastedMessage: if isZombie { t.Fatal("expected to not broadcast zombie " + "channel message") @@ -2482,7 +2501,7 @@ func TestProcessZombieEdgeNowLive(t *testing.T) { // want to allow a new update from the second node to allow the entire // edge to be resurrected. chanID := batch.chanAnn.ShortChannelID - err = ctx.router.MarkEdgeZombie( + err = tCtx.router.MarkEdgeZombie( chanID, [33]byte{}, batch.chanAnn.NodeID2, ) if err != nil { @@ -2499,7 +2518,7 @@ func TestProcessZombieEdgeNowLive(t *testing.T) { processAnnouncement(batch.chanUpdAnn1, true, true) // At this point, the channel should still be considered a zombie. - _, _, _, err = ctx.router.GetChannelByID(chanID) + _, _, _, err = tCtx.router.GetChannelByID(chanID) require.ErrorIs(t, err, graphdb.ErrZombieEdge) // Attempting to process the current channel update should fail due to @@ -2531,12 +2550,12 @@ func TestProcessZombieEdgeNowLive(t *testing.T) { // until the channel announcement is. Since the channel update indicates // a fresh new update, the gossiper should stash it until it sees the // corresponding channel announcement. - updateErrChan := ctx.gossiper.ProcessRemoteAnnouncement( - batch.chanUpdAnn2, remotePeer, + updateErrChan := tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanUpdAnn2, remotePeer, ) select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("expected to not broadcast live channel update " + "without announcement") case <-time.After(2 * trickleDelay): @@ -2559,7 +2578,7 @@ func TestProcessZombieEdgeNowLive(t *testing.T) { } select { - case msgWithSenders := <-ctx.broadcastedMessage: + case msgWithSenders := <-tCtx.broadcastedMessage: assertMessage(t, batch.chanUpdAnn2, msgWithSenders.msg) case <-time.After(2 * trickleDelay): t.Fatal("expected to broadcast live channel update") @@ -2571,11 +2590,12 @@ func TestProcessZombieEdgeNowLive(t *testing.T) { // be reprocessed later, after our ChannelAnnouncement. func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { t.Parallel() + ctx := context.Background() - ctx, err := createTestCtx(t, proofMatureDelta, false) + tCtx, err := createTestCtx(t, proofMatureDelta, false) require.NoError(t, err, "can't create context") - batch, err := ctx.createLocalAnnouncements(0) + batch, err := tCtx.createLocalAnnouncements(0) require.NoError(t, err, "can't generate announcements") remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) @@ -2585,12 +2605,12 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { // directly from the gossiper. sentMsgs := make(chan lnwire.Message, 10) remotePeer := &mockPeer{ - remoteKey, sentMsgs, ctx.gossiper.quit, atomic.Bool{}, + remoteKey, sentMsgs, tCtx.gossiper.quit, atomic.Bool{}, } // Override NotifyWhenOnline to return the remote peer which we expect // messages to be sent to. - ctx.gossiper.reliableSender.cfg.NotifyWhenOnline = func(peer [33]byte, + tCtx.gossiper.reliableSender.cfg.NotifyWhenOnline = func(peer [33]byte, peerChan chan<- lnpeer.Peer) { peerChan <- remotePeer @@ -2599,19 +2619,21 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { // Recreate the case where the remote node is sending us its ChannelUpdate // before we have been able to process our own ChannelAnnouncement and // ChannelUpdate. - errRemoteAnn := ctx.gossiper.ProcessRemoteAnnouncement( - batch.chanUpdAnn2, remotePeer, + errRemoteAnn := tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanUpdAnn2, remotePeer, ) select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel update announcement was broadcast") case <-time.After(2 * trickleDelay): } - err = <-ctx.gossiper.ProcessRemoteAnnouncement(batch.nodeAnn2, remotePeer) + err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.nodeAnn2, remotePeer, + ) require.NoError(t, err, "unable to process node ann") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("node announcement was broadcast") case <-time.After(2 * trickleDelay): } @@ -2620,7 +2642,9 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { // we did not already know about, it should have been added // to the map of premature ChannelUpdates. Check that nothing // was added to the graph. - chanInfo, e1, e2, err := ctx.router.GetChannelByID(batch.chanUpdAnn1.ShortChannelID) + chanInfo, e1, e2, err := tCtx.router.GetChannelByID( + batch.chanUpdAnn1.ShortChannelID, + ) if !errors.Is(err, graphdb.ErrEdgeNotFound) { t.Fatalf("Expected ErrEdgeNotFound, got: %v", err) } @@ -2636,32 +2660,32 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { // Recreate lightning network topology. Initialize router with channel // between two nodes. - err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.chanAnn) + err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanAnn) if err != nil { t.Fatalf("unable to process :%v", err) } select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel announcement was broadcast") case <-time.After(2 * trickleDelay): } - err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.chanUpdAnn1) + err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanUpdAnn1) if err != nil { t.Fatalf("unable to process :%v", err) } select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel update announcement was broadcast") case <-time.After(2 * trickleDelay): } - err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.nodeAnn1) + err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.nodeAnn1) if err != nil { t.Fatalf("unable to process :%v", err) } select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("node announcement was broadcast") case <-time.After(2 * trickleDelay): } @@ -2688,7 +2712,7 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { } // Check that the ChannelEdgePolicy was added to the graph. - chanInfo, e1, e2, err = ctx.router.GetChannelByID( + chanInfo, e1, e2, err = tCtx.router.GetChannelByID( batch.chanUpdAnn1.ShortChannelID, ) require.NoError(t, err, "unable to get channel from router") @@ -2704,19 +2728,19 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { // Pretending that we receive local channel announcement from funding // manager, thereby kick off the announcement exchange process. - err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.localProofAnn) + err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.localProofAnn) if err != nil { t.Fatalf("unable to process :%v", err) } select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("announcements were broadcast") case <-time.After(2 * trickleDelay): } number := 0 - if err := ctx.gossiper.cfg.WaitingProofStore.ForAll( + if err := tCtx.gossiper.cfg.WaitingProofStore.ForAll( func(*channeldb.WaitingProof) error { number++ return nil @@ -2732,8 +2756,8 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { t.Fatal("wrong number of objects in storage") } - err = <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.remoteProofAnn, remotePeer, + err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.remoteProofAnn, remotePeer, ) if err != nil { t.Fatalf("unable to process :%v", err) @@ -2741,14 +2765,14 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { for i := 0; i < 4; i++ { select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: case <-time.After(time.Second): t.Fatal("announcement wasn't broadcast") } } number = 0 - if err := ctx.gossiper.cfg.WaitingProofStore.ForAll( + if err := tCtx.gossiper.cfg.WaitingProofStore.ForAll( func(*channeldb.WaitingProof) error { number++ return nil @@ -2770,8 +2794,9 @@ func TestReceiveRemoteChannelUpdateFirst(t *testing.T) { // currently know of. func TestExtraDataChannelAnnouncementValidation(t *testing.T) { t.Parallel() + ctx := context.Background() - ctx, err := createTestCtx(t, 0, false) + tCtx, err := createTestCtx(t, 0, false) require.NoError(t, err, "can't create context") remotePeer := &mockPeer{ @@ -2782,7 +2807,7 @@ func TestExtraDataChannelAnnouncementValidation(t *testing.T) { // that we don't know of ourselves, but should still include in the // final signature check. extraBytes := []byte("gotta validate this still!") - ca, err := ctx.createRemoteChannelAnnouncement( + ca, err := tCtx.createRemoteChannelAnnouncement( 0, withExtraBytes(extraBytes), ) require.NoError(t, err, "can't create channel announcement") @@ -2790,7 +2815,9 @@ func TestExtraDataChannelAnnouncementValidation(t *testing.T) { // We'll now send the announcement to the main gossiper. We should be // able to validate this announcement to problem. select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement(ca, remotePeer): + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, ca, remotePeer, + ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } @@ -2804,9 +2831,10 @@ func TestExtraDataChannelAnnouncementValidation(t *testing.T) { // know of. func TestExtraDataChannelUpdateValidation(t *testing.T) { t.Parallel() + ctx := context.Background() timestamp := testTimestamp - ctx, err := createTestCtx(t, 0, false) + tCtx, err := createTestCtx(t, 0, false) require.NoError(t, err, "can't create context") remotePeer := &mockPeer{ @@ -2816,7 +2844,7 @@ func TestExtraDataChannelUpdateValidation(t *testing.T) { // In this scenario, we'll create two announcements, one regular // channel announcement, and another channel update announcement, that // has additional data that we won't be interpreting. - chanAnn, err := ctx.createRemoteChannelAnnouncement(0) + chanAnn, err := tCtx.createRemoteChannelAnnouncement(0) require.NoError(t, err, "unable to create chan ann") chanUpdAnn1, err := createUpdateAnnouncement( 0, 0, remoteKeyPriv1, timestamp, @@ -2832,21 +2860,27 @@ func TestExtraDataChannelUpdateValidation(t *testing.T) { // We should be able to properly validate all three messages without // any issue. select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement(chanAnn, remotePeer): + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, chanAnn, remotePeer, + ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } require.NoError(t, err, "unable to process announcement") select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement(chanUpdAnn1, remotePeer): + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, chanUpdAnn1, remotePeer, + ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } require.NoError(t, err, "unable to process announcement") select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement(chanUpdAnn2, remotePeer): + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, chanUpdAnn2, remotePeer, + ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } @@ -2858,8 +2892,9 @@ func TestExtraDataChannelUpdateValidation(t *testing.T) { // currently know of. func TestExtraDataNodeAnnouncementValidation(t *testing.T) { t.Parallel() + ctx := context.Background() - ctx, err := createTestCtx(t, 0, false) + tCtx, err := createTestCtx(t, 0, false) require.NoError(t, err, "can't create context") remotePeer := &mockPeer{ @@ -2876,7 +2911,9 @@ func TestExtraDataNodeAnnouncementValidation(t *testing.T) { require.NoError(t, err, "can't create node announcement") select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement(nodeAnn, remotePeer): + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, nodeAnn, remotePeer, + ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } @@ -2928,11 +2965,12 @@ func assertProcessAnnouncement(t *testing.T, result chan error) { // the retransmit ticker ticks. func TestRetransmit(t *testing.T) { t.Parallel() + ctx := context.Background() - ctx, err := createTestCtx(t, proofMatureDelta, false) + tCtx, err := createTestCtx(t, proofMatureDelta, false) require.NoError(t, err, "can't create context") - batch, err := ctx.createLocalAnnouncements(0) + batch, err := tCtx.createLocalAnnouncements(0) require.NoError(t, err, "can't generate announcements") remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) @@ -2943,39 +2981,39 @@ func TestRetransmit(t *testing.T) { // announcement. No messages should be broadcasted yet, since no proof // has been exchanged. assertProcessAnnouncement( - t, ctx.gossiper.ProcessLocalAnnouncement(batch.chanAnn), + t, tCtx.gossiper.ProcessLocalAnnouncement(batch.chanAnn), ) - assertBroadcast(t, ctx, 0) + assertBroadcast(t, tCtx, 0) assertProcessAnnouncement( - t, ctx.gossiper.ProcessLocalAnnouncement(batch.chanUpdAnn1), + t, tCtx.gossiper.ProcessLocalAnnouncement(batch.chanUpdAnn1), ) - assertBroadcast(t, ctx, 0) + assertBroadcast(t, tCtx, 0) assertProcessAnnouncement( - t, ctx.gossiper.ProcessLocalAnnouncement(batch.nodeAnn1), + t, tCtx.gossiper.ProcessLocalAnnouncement(batch.nodeAnn1), ) - assertBroadcast(t, ctx, 0) + assertBroadcast(t, tCtx, 0) // Add the remote channel update to the gossiper. Similarly, nothing // should be broadcasted. assertProcessAnnouncement( - t, ctx.gossiper.ProcessRemoteAnnouncement( - batch.chanUpdAnn2, remotePeer, + t, tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanUpdAnn2, remotePeer, ), ) - assertBroadcast(t, ctx, 0) + assertBroadcast(t, tCtx, 0) // Now add the local and remote proof to the gossiper, which should // trigger a broadcast of the announcements. assertProcessAnnouncement( - t, ctx.gossiper.ProcessLocalAnnouncement(batch.localProofAnn), + t, tCtx.gossiper.ProcessLocalAnnouncement(batch.localProofAnn), ) - assertBroadcast(t, ctx, 0) + assertBroadcast(t, tCtx, 0) assertProcessAnnouncement( - t, ctx.gossiper.ProcessRemoteAnnouncement( - batch.remoteProofAnn, remotePeer, + t, tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.remoteProofAnn, remotePeer, ), ) @@ -2987,7 +3025,7 @@ func TestRetransmit(t *testing.T) { t.Helper() num := chanAnns + chanUpds + nodeAnns - anns := assertBroadcast(t, ctx, num) + anns := assertBroadcast(t, tCtx, num) // Count the received announcements. var chanAnn, chanUpd, nodeAnn int @@ -3015,12 +3053,15 @@ func TestRetransmit(t *testing.T) { // update. checkAnnouncements(t, 1, 2, 1) + retransmit, ok := tCtx.gossiper.cfg.RetransmitTicker.(*ticker.Force) + require.True(t, ok) + // Now let the retransmit ticker tick, which should trigger updates to // be rebroadcast. now := time.Unix(int64(testTimestamp), 0) future := now.Add(rebroadcastInterval + 10*time.Second) select { - case ctx.gossiper.cfg.RetransmitTicker.(*ticker.Force).Force <- future: + case retransmit.Force <- future: case <-time.After(2 * time.Second): t.Fatalf("unable to force tick") } @@ -3034,11 +3075,12 @@ func TestRetransmit(t *testing.T) { // no existing channels in the graph do not get forwarded. func TestNodeAnnouncementNoChannels(t *testing.T) { t.Parallel() + ctx := context.Background() - ctx, err := createTestCtx(t, 0, false) + tCtx, err := createTestCtx(t, 0, false) require.NoError(t, err, "can't create context") - batch, err := ctx.createRemoteAnnouncements(0) + batch, err := tCtx.createRemoteAnnouncements(0) require.NoError(t, err, "can't generate announcements") remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) @@ -3047,8 +3089,9 @@ func TestNodeAnnouncementNoChannels(t *testing.T) { // Process the remote node announcement. select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement(batch.nodeAnn2, - remotePeer): + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.nodeAnn2, remotePeer, + ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } @@ -3057,7 +3100,7 @@ func TestNodeAnnouncementNoChannels(t *testing.T) { // Since no channels or node announcements were already in the graph, // the node announcement should be ignored, and not forwarded. select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("node announcement was broadcast") case <-time.After(2 * trickleDelay): } @@ -3065,16 +3108,18 @@ func TestNodeAnnouncementNoChannels(t *testing.T) { // Now add the node's channel to the graph by processing the channel // announcement and channel update. select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement(batch.chanAnn, - remotePeer): + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanAnn, remotePeer, + ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } require.NoError(t, err, "unable to process announcement") select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement(batch.chanUpdAnn2, - remotePeer): + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanUpdAnn2, remotePeer, + ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } @@ -3082,7 +3127,9 @@ func TestNodeAnnouncementNoChannels(t *testing.T) { // Now process the node announcement again. select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement(batch.nodeAnn2, remotePeer): + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.nodeAnn2, remotePeer, + ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } @@ -3092,7 +3139,7 @@ func TestNodeAnnouncementNoChannels(t *testing.T) { // the channel announcement and update be. for i := 0; i < 3; i++ { select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: case <-time.After(time.Second): t.Fatal("announcement wasn't broadcast") } @@ -3101,15 +3148,16 @@ func TestNodeAnnouncementNoChannels(t *testing.T) { // Processing the same node announcement again should be ignored, as it // is stale. select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement(batch.nodeAnn2, - remotePeer): + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.nodeAnn2, remotePeer, + ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } require.NoError(t, err, "unable to process announcement") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("node announcement was broadcast") case <-time.After(2 * trickleDelay): } @@ -3119,11 +3167,12 @@ func TestNodeAnnouncementNoChannels(t *testing.T) { // validate the msg flags and max HTLC field of a ChannelUpdate. func TestOptionalFieldsChannelUpdateValidation(t *testing.T) { t.Parallel() + ctx := context.Background() - ctx, err := createTestCtx(t, 0, false) + tCtx, err := createTestCtx(t, 0, false) require.NoError(t, err, "can't create context") - processRemoteAnnouncement := ctx.gossiper.ProcessRemoteAnnouncement + processRemoteAnnouncement := tCtx.gossiper.ProcessRemoteAnnouncement chanUpdateHeight := uint32(0) timestamp := uint32(123456) @@ -3131,11 +3180,11 @@ func TestOptionalFieldsChannelUpdateValidation(t *testing.T) { // In this scenario, we'll test whether the message flags field in a // channel update is properly handled. - chanAnn, err := ctx.createRemoteChannelAnnouncement(chanUpdateHeight) + chanAnn, err := tCtx.createRemoteChannelAnnouncement(chanUpdateHeight) require.NoError(t, err, "can't create channel announcement") select { - case err = <-processRemoteAnnouncement(chanAnn, nodePeer): + case err = <-processRemoteAnnouncement(ctx, chanAnn, nodePeer): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } @@ -3155,7 +3204,7 @@ func TestOptionalFieldsChannelUpdateValidation(t *testing.T) { } select { - case err = <-processRemoteAnnouncement(chanUpdAnn, nodePeer): + case err = <-processRemoteAnnouncement(ctx, chanUpdAnn, nodePeer): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } @@ -3172,7 +3221,7 @@ func TestOptionalFieldsChannelUpdateValidation(t *testing.T) { } select { - case err = <-processRemoteAnnouncement(chanUpdAnn, nodePeer): + case err = <-processRemoteAnnouncement(ctx, chanUpdAnn, nodePeer): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } @@ -3188,7 +3237,7 @@ func TestOptionalFieldsChannelUpdateValidation(t *testing.T) { } select { - case err = <-processRemoteAnnouncement(chanUpdAnn, nodePeer): + case err = <-processRemoteAnnouncement(ctx, chanUpdAnn, nodePeer): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } @@ -3205,7 +3254,7 @@ func TestOptionalFieldsChannelUpdateValidation(t *testing.T) { } select { - case err = <-processRemoteAnnouncement(chanUpdAnn, nodePeer): + case err = <-processRemoteAnnouncement(ctx, chanUpdAnn, nodePeer): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } @@ -3216,13 +3265,14 @@ func TestOptionalFieldsChannelUpdateValidation(t *testing.T) { // channel is always sent upon the remote party reconnecting. func TestSendChannelUpdateReliably(t *testing.T) { t.Parallel() + ctx := context.Background() // We'll start by creating our test context and a batch of // announcements. - ctx, err := createTestCtx(t, proofMatureDelta, false) + tCtx, err := createTestCtx(t, proofMatureDelta, false) require.NoError(t, err, "unable to create test context") - batch, err := ctx.createLocalAnnouncements(0) + batch, err := tCtx.createLocalAnnouncements(0) require.NoError(t, err, "can't generate announcements") // We'll also create two keys, one for ourselves and another for the @@ -3235,7 +3285,7 @@ func TestSendChannelUpdateReliably(t *testing.T) { // gossiper to the remote peer. sentToPeer := make(chan lnwire.Message, 1) remotePeer := &mockPeer{ - remoteKey, sentToPeer, ctx.gossiper.quit, atomic.Bool{}, + remoteKey, sentToPeer, tCtx.gossiper.quit, atomic.Bool{}, } // Since we first wait to be notified of the peer before attempting to @@ -3243,13 +3293,13 @@ func TestSendChannelUpdateReliably(t *testing.T) { // NotifyWhenOffline to instead give us access to the channel that will // receive the notification. notifyOnline := make(chan chan<- lnpeer.Peer, 1) - ctx.gossiper.reliableSender.cfg.NotifyWhenOnline = func(_ [33]byte, + tCtx.gossiper.reliableSender.cfg.NotifyWhenOnline = func(_ [33]byte, peerChan chan<- lnpeer.Peer) { notifyOnline <- peerChan } notifyOffline := make(chan chan struct{}, 1) - ctx.gossiper.reliableSender.cfg.NotifyWhenOffline = func( + tCtx.gossiper.reliableSender.cfg.NotifyWhenOffline = func( _ [33]byte) <-chan struct{} { c := make(chan struct{}, 1) @@ -3274,7 +3324,7 @@ func TestSendChannelUpdateReliably(t *testing.T) { // Process the channel announcement for which we'll send a channel // update for. select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.chanAnn): + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanAnn): case <-time.After(2 * time.Second): t.Fatal("did not process local channel announcement") } @@ -3282,14 +3332,14 @@ func TestSendChannelUpdateReliably(t *testing.T) { // It should not be broadcast due to not having an announcement proof. select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel announcement was broadcast") case <-time.After(2 * trickleDelay): } // Now, we'll process the channel update. select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.chanUpdAnn1): + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanUpdAnn1): case <-time.After(2 * time.Second): t.Fatal("did not process local channel update") } @@ -3298,7 +3348,7 @@ func TestSendChannelUpdateReliably(t *testing.T) { // It should also not be broadcast due to the announcement not having an // announcement proof. select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel announcement was broadcast") case <-time.After(2 * trickleDelay): } @@ -3347,7 +3397,7 @@ func TestSendChannelUpdateReliably(t *testing.T) { // With the new update created, we'll go ahead and process it. select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement( + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( batch.chanUpdAnn1, ): case <-time.After(2 * time.Second): @@ -3358,7 +3408,7 @@ func TestSendChannelUpdateReliably(t *testing.T) { // It should also not be broadcast due to the announcement not having an // announcement proof. select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel announcement was broadcast") case <-time.After(2 * trickleDelay): } @@ -3386,7 +3436,7 @@ func TestSendChannelUpdateReliably(t *testing.T) { // We'll then exchange proofs with the remote peer in order to announce // the channel. select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement( + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( batch.localProofAnn, ): case <-time.After(2 * time.Second): @@ -3396,7 +3446,7 @@ func TestSendChannelUpdateReliably(t *testing.T) { // No messages should be broadcast as we don't have the full proof yet. select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel announcement was broadcast") case <-time.After(2 * trickleDelay): } @@ -3405,8 +3455,8 @@ func TestSendChannelUpdateReliably(t *testing.T) { assertMsgSent(batch.localProofAnn) select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.remoteProofAnn, remotePeer, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.remoteProofAnn, remotePeer, ): case <-time.After(2 * time.Second): t.Fatal("did not process remote channel proof") @@ -3417,7 +3467,7 @@ func TestSendChannelUpdateReliably(t *testing.T) { // channel has been announced. for i := 0; i < 2; i++ { select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: case <-time.After(2 * trickleDelay): t.Fatal("expected channel to be announced") } @@ -3439,7 +3489,7 @@ func TestSendChannelUpdateReliably(t *testing.T) { // directly since the reliable sender only applies when the channel is // not announced. select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement( + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( newChannelUpdate, ): case <-time.After(2 * time.Second): @@ -3447,7 +3497,7 @@ func TestSendChannelUpdateReliably(t *testing.T) { } require.NoError(t, err, "unable to process local channel update") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: case <-time.After(2 * trickleDelay): t.Fatal("channel update was not broadcast") } @@ -3494,7 +3544,7 @@ func TestSendChannelUpdateReliably(t *testing.T) { // Since the messages above are now deemed as stale, they should be // removed from the message store. err = wait.NoError(func() error { - msgs, err := ctx.gossiper.cfg.MessageStore.Messages() + msgs, err := tCtx.gossiper.cfg.MessageStore.Messages() if err != nil { return fmt.Errorf("unable to retrieve pending "+ "messages: %v", err) @@ -3532,7 +3582,9 @@ func sendRemoteMsg(t *testing.T, ctx *testCtx, msg lnwire.Message, t.Helper() select { - case err := <-ctx.gossiper.ProcessRemoteAnnouncement(msg, remotePeer): + case err := <-ctx.gossiper.ProcessRemoteAnnouncement( + context.Background(), msg, remotePeer, + ): if err != nil { t.Fatalf("unable to process channel msg: %v", err) } @@ -3932,14 +3984,15 @@ func (m *SyncManager) markGraphSyncing() { // initial historical sync has completed. func TestBroadcastAnnsAfterGraphSynced(t *testing.T) { t.Parallel() + ctx := context.Background() - ctx, err := createTestCtx(t, 10, false) + tCtx, err := createTestCtx(t, 10, false) require.NoError(t, err, "can't create context") // We'll mark the graph as not synced. This should prevent us from // broadcasting any messages we've received as part of our initial // historical sync. - ctx.gossiper.syncMgr.markGraphSyncing() + tCtx.gossiper.syncMgr.markGraphSyncing() assertBroadcast := func(msg lnwire.Message, isRemote bool, shouldBroadcast bool) { @@ -3951,11 +4004,11 @@ func TestBroadcastAnnsAfterGraphSynced(t *testing.T) { } var errChan chan error if isRemote { - errChan = ctx.gossiper.ProcessRemoteAnnouncement( - msg, nodePeer, + errChan = tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, msg, nodePeer, ) } else { - errChan = ctx.gossiper.ProcessLocalAnnouncement(msg) + errChan = tCtx.gossiper.ProcessLocalAnnouncement(msg) } select { @@ -3969,7 +4022,7 @@ func TestBroadcastAnnsAfterGraphSynced(t *testing.T) { } select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: if !shouldBroadcast { t.Fatal("gossip message was broadcast") } @@ -3982,7 +4035,7 @@ func TestBroadcastAnnsAfterGraphSynced(t *testing.T) { // A remote channel announcement should not be broadcast since the graph // has not yet been synced. - chanAnn1, err := ctx.createRemoteChannelAnnouncement(0) + chanAnn1, err := tCtx.createRemoteChannelAnnouncement(0) require.NoError(t, err, "unable to create channel announcement") assertBroadcast(chanAnn1, true, false) @@ -3994,9 +4047,9 @@ func TestBroadcastAnnsAfterGraphSynced(t *testing.T) { // Mark the graph as synced, which should allow the channel announcement // should to be broadcast. - ctx.gossiper.syncMgr.markGraphSynced() + tCtx.gossiper.syncMgr.markGraphSynced() - chanAnn2, err := ctx.createRemoteChannelAnnouncement(1) + chanAnn2, err := tCtx.createRemoteChannelAnnouncement(1) require.NoError(t, err, "unable to create channel announcement") assertBroadcast(chanAnn2, true, true) } @@ -4009,15 +4062,16 @@ func TestBroadcastAnnsAfterGraphSynced(t *testing.T) { // is tested by TestRateLimitChannelUpdates. func TestRateLimitDeDup(t *testing.T) { t.Parallel() + ctx := context.Background() // Create our test harness. const blockHeight = 100 - ctx, err := createTestCtx(t, blockHeight, false) + tCtx, err := createTestCtx(t, blockHeight, false) require.NoError(t, err, "can't create context") - ctx.gossiper.cfg.RebroadcastInterval = time.Hour + tCtx.gossiper.cfg.RebroadcastInterval = time.Hour var findBaseByAliasCount atomic.Int32 - ctx.gossiper.cfg.FindBaseByAlias = func(alias lnwire.ShortChannelID) ( + tCtx.gossiper.cfg.FindBaseByAlias = func(alias lnwire.ShortChannelID) ( lnwire.ShortChannelID, error) { findBaseByAliasCount.Add(1) @@ -4026,33 +4080,33 @@ func TestRateLimitDeDup(t *testing.T) { } getUpdateEdgeCount := func() int { - ctx.router.mu.Lock() - defer ctx.router.mu.Unlock() + tCtx.router.mu.Lock() + defer tCtx.router.mu.Unlock() - return ctx.router.updateEdgeCount + return tCtx.router.updateEdgeCount } // We set the burst to 2 here. The very first update should not count // towards this _and_ any duplicates should also not count towards it. - ctx.gossiper.cfg.MaxChannelUpdateBurst = 2 - ctx.gossiper.cfg.ChannelUpdateInterval = time.Minute + tCtx.gossiper.cfg.MaxChannelUpdateBurst = 2 + tCtx.gossiper.cfg.ChannelUpdateInterval = time.Minute // The graph should start empty. - require.Empty(t, ctx.router.infos) - require.Empty(t, ctx.router.edges) + require.Empty(t, tCtx.router.infos) + require.Empty(t, tCtx.router.edges) // We'll create a batch of signed announcements, including updates for // both sides, for a channel and process them. They should all be // forwarded as this is our first time learning about the channel. - batch, err := ctx.createRemoteAnnouncements(blockHeight) + batch, err := tCtx.createRemoteAnnouncements(blockHeight) require.NoError(t, err) nodePeer1 := &mockPeer{ remoteKeyPriv1.PubKey(), nil, nil, atomic.Bool{}, } select { - case err := <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.chanAnn, nodePeer1, + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanAnn, nodePeer1, ): require.NoError(t, err) case <-time.After(time.Second): @@ -4060,8 +4114,8 @@ func TestRateLimitDeDup(t *testing.T) { } select { - case err := <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.chanUpdAnn1, nodePeer1, + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanUpdAnn1, nodePeer1, ): require.NoError(t, err) case <-time.After(time.Second): @@ -4072,8 +4126,8 @@ func TestRateLimitDeDup(t *testing.T) { remoteKeyPriv2.PubKey(), nil, nil, atomic.Bool{}, } select { - case err := <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.chanUpdAnn2, nodePeer2, + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanUpdAnn2, nodePeer2, ): require.NoError(t, err) case <-time.After(time.Second): @@ -4083,21 +4137,21 @@ func TestRateLimitDeDup(t *testing.T) { timeout := time.After(2 * trickleDelay) for i := 0; i < 3; i++ { select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: case <-timeout: t.Fatal("expected announcement to be broadcast") } } shortChanID := batch.chanAnn.ShortChannelID.ToUint64() - require.Contains(t, ctx.router.infos, shortChanID) - require.Contains(t, ctx.router.edges, shortChanID) + require.Contains(t, tCtx.router.infos, shortChanID) + require.Contains(t, tCtx.router.edges, shortChanID) // Before we send anymore updates, we want to let our test harness // hang during GetChannelByID so that we can ensure that two threads are // waiting for the chan. pause := make(chan struct{}) - ctx.router.pauseGetChannelByID <- pause + tCtx.router.pauseGetChannelByID <- pause // Take note of how many times FindBaseByAlias has been called. // It should be 2 since we have processed two channel updates. @@ -4122,10 +4176,14 @@ func TestRateLimitDeDup(t *testing.T) { // succession. We wait for both to have hit the FindBaseByAlias check // before we un-pause the GetChannelByID call. go func() { - ctx.gossiper.ProcessRemoteAnnouncement(&update, nodePeer1) + tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, &update, nodePeer1, + ) }() go func() { - ctx.gossiper.ProcessRemoteAnnouncement(&update, nodePeer1) + tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, &update, nodePeer1, + ) }() // We know that both are being processed once the count for @@ -4163,7 +4221,7 @@ func TestRateLimitDeDup(t *testing.T) { t.Helper() select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: require.True(t, shouldBroadcast) case <-time.After(2 * trickleDelay): require.False(t, shouldBroadcast) @@ -4172,8 +4230,8 @@ func TestRateLimitDeDup(t *testing.T) { processUpdate := func(msg lnwire.Message, peer lnpeer.Peer) { select { - case err := <-ctx.gossiper.ProcessRemoteAnnouncement( - msg, peer, + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, msg, peer, ): require.NoError(t, err) case <-time.After(time.Second): @@ -4201,31 +4259,32 @@ func TestRateLimitDeDup(t *testing.T) { // channel updates. func TestRateLimitChannelUpdates(t *testing.T) { t.Parallel() + ctx := context.Background() // Create our test harness. const blockHeight = 100 - ctx, err := createTestCtx(t, blockHeight, false) + tCtx, err := createTestCtx(t, blockHeight, false) require.NoError(t, err, "can't create context") - ctx.gossiper.cfg.RebroadcastInterval = time.Hour - ctx.gossiper.cfg.MaxChannelUpdateBurst = 5 - ctx.gossiper.cfg.ChannelUpdateInterval = 5 * time.Second + tCtx.gossiper.cfg.RebroadcastInterval = time.Hour + tCtx.gossiper.cfg.MaxChannelUpdateBurst = 5 + tCtx.gossiper.cfg.ChannelUpdateInterval = 5 * time.Second // The graph should start empty. - require.Empty(t, ctx.router.infos) - require.Empty(t, ctx.router.edges) + require.Empty(t, tCtx.router.infos) + require.Empty(t, tCtx.router.edges) // We'll create a batch of signed announcements, including updates for // both sides, for a channel and process them. They should all be // forwarded as this is our first time learning about the channel. - batch, err := ctx.createRemoteAnnouncements(blockHeight) + batch, err := tCtx.createRemoteAnnouncements(blockHeight) require.NoError(t, err) nodePeer1 := &mockPeer{ remoteKeyPriv1.PubKey(), nil, nil, atomic.Bool{}, } select { - case err := <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.chanAnn, nodePeer1, + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanAnn, nodePeer1, ): require.NoError(t, err) case <-time.After(time.Second): @@ -4233,8 +4292,8 @@ func TestRateLimitChannelUpdates(t *testing.T) { } select { - case err := <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.chanUpdAnn1, nodePeer1, + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanUpdAnn1, nodePeer1, ): require.NoError(t, err) case <-time.After(time.Second): @@ -4245,8 +4304,8 @@ func TestRateLimitChannelUpdates(t *testing.T) { remoteKeyPriv2.PubKey(), nil, nil, atomic.Bool{}, } select { - case err := <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.chanUpdAnn2, nodePeer2, + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanUpdAnn2, nodePeer2, ): require.NoError(t, err) case <-time.After(time.Second): @@ -4256,15 +4315,15 @@ func TestRateLimitChannelUpdates(t *testing.T) { timeout := time.After(2 * trickleDelay) for i := 0; i < 3; i++ { select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: case <-timeout: t.Fatal("expected announcement to be broadcast") } } shortChanID := batch.chanAnn.ShortChannelID.ToUint64() - require.Contains(t, ctx.router.infos, shortChanID) - require.Contains(t, ctx.router.edges, shortChanID) + require.Contains(t, tCtx.router.infos, shortChanID) + require.Contains(t, tCtx.router.edges, shortChanID) // We'll define a helper to assert whether updates should be rate // limited or not depending on their contents. @@ -4274,14 +4333,16 @@ func TestRateLimitChannelUpdates(t *testing.T) { t.Helper() select { - case err := <-ctx.gossiper.ProcessRemoteAnnouncement(update, peer): + case err := <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, update, peer, + ): require.NoError(t, err) case <-time.After(time.Second): t.Fatal("remote announcement not processed") } select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: if shouldRateLimit { t.Fatal("unexpected channel update broadcast") } @@ -4304,7 +4365,7 @@ func TestRateLimitChannelUpdates(t *testing.T) { keepAliveUpdate := *batch.chanUpdAnn1 keepAliveUpdate.Timestamp = uint32( time.Unix(int64(batch.chanUpdAnn1.Timestamp), 0). - Add(ctx.gossiper.cfg.RebroadcastInterval).Unix(), + Add(tCtx.gossiper.cfg.RebroadcastInterval).Unix(), ) require.NoError(t, signUpdate(remoteKeyPriv1, &keepAliveUpdate)) assertRateLimit(&keepAliveUpdate, nodePeer1, false) @@ -4315,7 +4376,7 @@ func TestRateLimitChannelUpdates(t *testing.T) { // seconds with a max burst of 5 per direction. We'll process the max // burst of one direction first. None of these should be rate limited. updateSameDirection := keepAliveUpdate - for i := uint32(0); i < uint32(ctx.gossiper.cfg.MaxChannelUpdateBurst); i++ { + for i := uint32(0); i < uint32(tCtx.gossiper.cfg.MaxChannelUpdateBurst); i++ { //nolint:ll updateSameDirection.Timestamp++ updateSameDirection.BaseFee++ require.NoError(t, signUpdate(remoteKeyPriv1, &updateSameDirection)) @@ -4338,8 +4399,8 @@ func TestRateLimitChannelUpdates(t *testing.T) { // Wait for the next interval to tick. Since we've only waited for one, // only one more update is allowed. - <-time.After(ctx.gossiper.cfg.ChannelUpdateInterval) - for i := 0; i < ctx.gossiper.cfg.MaxChannelUpdateBurst; i++ { + <-time.After(tCtx.gossiper.cfg.ChannelUpdateInterval) + for i := 0; i < tCtx.gossiper.cfg.MaxChannelUpdateBurst; i++ { updateSameDirection.Timestamp++ updateSameDirection.BaseFee++ require.NoError(t, signUpdate(remoteKeyPriv1, &updateSameDirection)) @@ -4353,11 +4414,12 @@ func TestRateLimitChannelUpdates(t *testing.T) { // about our own channels when coming from a remote peer. func TestIgnoreOwnAnnouncement(t *testing.T) { t.Parallel() + ctx := context.Background() - ctx, err := createTestCtx(t, proofMatureDelta, false) + tCtx, err := createTestCtx(t, proofMatureDelta, false) require.NoError(t, err, "can't create context") - batch, err := ctx.createLocalAnnouncements(0) + batch, err := tCtx.createLocalAnnouncements(0) require.NoError(t, err, "can't generate announcements") remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) @@ -4366,8 +4428,8 @@ func TestIgnoreOwnAnnouncement(t *testing.T) { // Try to let the remote peer tell us about the channel we are part of. select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.chanAnn, remotePeer, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanAnn, remotePeer, ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") @@ -4382,66 +4444,66 @@ func TestIgnoreOwnAnnouncement(t *testing.T) { // update. No messages should be broadcast yet, since we don't have // the announcement signatures. select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.chanAnn): + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanAnn): case <-time.After(2 * time.Second): t.Fatal("did not process local announcement") } require.NoError(t, err, "unable to process channel ann") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel announcement was broadcast") case <-time.After(2 * trickleDelay): } select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.chanUpdAnn1): + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.chanUpdAnn1): case <-time.After(2 * time.Second): t.Fatal("did not process local announcement") } require.NoError(t, err, "unable to process channel update") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel update announcement was broadcast") case <-time.After(2 * trickleDelay): } select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.nodeAnn1): + case err = <-tCtx.gossiper.ProcessLocalAnnouncement(batch.nodeAnn1): case <-time.After(2 * time.Second): t.Fatal("did not process local announcement") } require.NoError(t, err, "unable to process node ann") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("node announcement was broadcast") case <-time.After(2 * trickleDelay): } // We should accept the remote's channel update and node announcement. select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.chanUpdAnn2, remotePeer, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanUpdAnn2, remotePeer, ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } require.NoError(t, err, "unable to process channel update") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("channel update announcement was broadcast") case <-time.After(2 * trickleDelay): } select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.nodeAnn2, remotePeer, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.nodeAnn2, remotePeer, ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } require.NoError(t, err, "unable to process node ann") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("node announcement was broadcast") case <-time.After(2 * trickleDelay): } @@ -4449,21 +4511,23 @@ func TestIgnoreOwnAnnouncement(t *testing.T) { // Now we exchange the proofs, the messages will be broadcasted to the // network. select { - case err = <-ctx.gossiper.ProcessLocalAnnouncement(batch.localProofAnn): + case err = <-tCtx.gossiper.ProcessLocalAnnouncement( + batch.localProofAnn, + ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") } require.NoError(t, err, "unable to process local proof") select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: t.Fatal("announcements were broadcast") case <-time.After(2 * trickleDelay): } select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.remoteProofAnn, remotePeer, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.remoteProofAnn, remotePeer, ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") @@ -4472,7 +4536,7 @@ func TestIgnoreOwnAnnouncement(t *testing.T) { for i := 0; i < 5; i++ { select { - case <-ctx.broadcastedMessage: + case <-tCtx.broadcastedMessage: case <-time.After(time.Second): t.Fatal("announcement wasn't broadcast") } @@ -4481,8 +4545,8 @@ func TestIgnoreOwnAnnouncement(t *testing.T) { // Finally, we again check that we'll ignore the remote giving us // announcements about our own channel. select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.chanAnn, remotePeer, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanAnn, remotePeer, ): case <-time.After(2 * time.Second): t.Fatal("did not process remote announcement") @@ -4497,13 +4561,14 @@ func TestIgnoreOwnAnnouncement(t *testing.T) { // error. func TestRejectCacheChannelAnn(t *testing.T) { t.Parallel() + ctx := context.Background() - ctx, err := createTestCtx(t, proofMatureDelta, false) + tCtx, err := createTestCtx(t, proofMatureDelta, false) require.NoError(t, err, "can't create context") // First, we create a channel announcement to send over to our test // peer. - batch, err := ctx.createRemoteAnnouncements(0) + batch, err := tCtx.createRemoteAnnouncements(0) require.NoError(t, err, "can't generate announcements") remoteKey, err := btcec.ParsePubKey(batch.nodeAnn2.NodeID[:]) @@ -4513,12 +4578,12 @@ func TestRejectCacheChannelAnn(t *testing.T) { // Before sending over the announcement, we'll modify it such that we // know it will always fail. chanID := batch.chanAnn.ShortChannelID.ToUint64() - ctx.router.queueValidationFail(chanID) + tCtx.router.queueValidationFail(chanID) // If we process the batch the first time we should get an error. select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.chanAnn, remotePeer, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanAnn, remotePeer, ): require.NotNil(t, err) case <-time.After(2 * time.Second): @@ -4528,8 +4593,8 @@ func TestRejectCacheChannelAnn(t *testing.T) { // If we process it a *second* time, then we should get an error saying // we rejected it already. select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - batch.chanAnn, remotePeer, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, batch.chanAnn, remotePeer, ): errStr := err.Error() require.Contains(t, errStr, "recently rejected") @@ -4577,8 +4642,9 @@ func TestFutureMsgCacheEviction(t *testing.T) { // channel announcements are banned properly. func TestChanAnnBanningNonChanPeer(t *testing.T) { t.Parallel() + ctx := context.Background() - ctx, err := createTestCtx(t, 1000, false) + tCtx, err := createTestCtx(t, 1000, false) require.NoError(t, err, "can't create context") nodePeer1 := &mockPeer{ @@ -4593,15 +4659,15 @@ func TestChanAnnBanningNonChanPeer(t *testing.T) { // Craft a valid channel announcement for a channel we don't // have. We will ensure that it fails validation by modifying // the tx script. - ca, err := ctx.createRemoteChannelAnnouncement( + ca, err := tCtx.createRemoteChannelAnnouncement( uint32(i), withFundingTxPrep(fundingTxPrepTypeInvalidOutput), ) require.NoError(t, err, "can't create channel announcement") select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - ca, nodePeer1, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, ca, nodePeer1, ): require.ErrorIs(t, err, ErrInvalidFundingOutput) @@ -4611,20 +4677,23 @@ func TestChanAnnBanningNonChanPeer(t *testing.T) { } // The peer should be banned now. - require.True(t, ctx.gossiper.isBanned(nodePeer1.PubKey())) + require.True(t, tCtx.gossiper.isBanned(nodePeer1.PubKey())) // Assert that nodePeer has been disconnected. require.True(t, nodePeer1.disconnected.Load()) // Mark the UTXO as spent so that we get the ErrChannelSpent error and // can thus tests that the gossiper ignores closed channels. - ca, err := ctx.createRemoteChannelAnnouncement( + ca, err := tCtx.createRemoteChannelAnnouncement( 101, withFundingTxPrep(fundingTxPrepTypeSpent), ) require.NoError(t, err, "can't create channel announcement") select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement(ca, nodePeer2): + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, ca, nodePeer2, + ): + require.ErrorIs(t, err, ErrChannelSpent) case <-time.After(2 * time.Second): @@ -4632,7 +4701,7 @@ func TestChanAnnBanningNonChanPeer(t *testing.T) { } // Check that the announcement's scid is marked as closed. - isClosed, err := ctx.gossiper.cfg.ScidCloser.IsClosedScid( + isClosed, err := tCtx.gossiper.cfg.ScidCloser.IsClosedScid( ca.ShortChannelID, ) require.Nil(t, err) @@ -4644,16 +4713,19 @@ func TestChanAnnBanningNonChanPeer(t *testing.T) { sourceToPub(nodePeer2.IdentityKey()), ) - ctx.gossiper.recentRejects.Delete(key) + tCtx.gossiper.recentRejects.Delete(key) // The validateFundingTransaction method will mark this channel // as a zombie if any error occurs in the chanvalidate.Validate call. // For the sake of the rest of the test, however, we mark it as live // here. - _ = ctx.router.MarkEdgeLive(ca.ShortChannelID) + _ = tCtx.router.MarkEdgeLive(ca.ShortChannelID) select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement(ca, nodePeer2): + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, ca, nodePeer2, + ): + require.ErrorContains(t, err, "ignoring closed channel") case <-time.After(2 * time.Second): @@ -4665,8 +4737,9 @@ func TestChanAnnBanningNonChanPeer(t *testing.T) { // get disconnected. func TestChanAnnBanningChanPeer(t *testing.T) { t.Parallel() + ctx := context.Background() - ctx, err := createTestCtx(t, 1000, true) + tCtx, err := createTestCtx(t, 1000, true) require.NoError(t, err, "can't create context") nodePeer := &mockPeer{remoteKeyPriv1.PubKey(), nil, nil, atomic.Bool{}} @@ -4676,15 +4749,15 @@ func TestChanAnnBanningChanPeer(t *testing.T) { // Craft a valid channel announcement for a channel we don't // have. We will ensure that it fails validation by modifying // the router. - ca, err := ctx.createRemoteChannelAnnouncement( + ca, err := tCtx.createRemoteChannelAnnouncement( uint32(i), withFundingTxPrep(fundingTxPrepTypeInvalidOutput), ) require.NoError(t, err, "can't create channel announcement") select { - case err = <-ctx.gossiper.ProcessRemoteAnnouncement( - ca, nodePeer, + case err = <-tCtx.gossiper.ProcessRemoteAnnouncement( + ctx, ca, nodePeer, ): require.ErrorIs(t, err, ErrInvalidFundingOutput) @@ -4694,7 +4767,7 @@ func TestChanAnnBanningChanPeer(t *testing.T) { } // The peer should be banned now. - require.True(t, ctx.gossiper.isBanned(nodePeer.PubKey())) + require.True(t, tCtx.gossiper.isBanned(nodePeer.PubKey())) // Assert that the peer wasn't disconnected. require.False(t, nodePeer.disconnected.Load()) @@ -4756,7 +4829,9 @@ func assertChanChainRejection(t *testing.T, ctx *testCtx, err: errChan, } - _, added := ctx.gossiper.handleChanAnnouncement(nMsg, edge) + _, added := ctx.gossiper.handleChanAnnouncement( + context.Background(), nMsg, edge, + ) require.False(t, added) select { diff --git a/discovery/reliable_sender.go b/discovery/reliable_sender.go index b4d32e73f..357654a65 100644 --- a/discovery/reliable_sender.go +++ b/discovery/reliable_sender.go @@ -1,8 +1,10 @@ package discovery import ( + "context" "sync" + "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnwire" ) @@ -28,7 +30,7 @@ type reliableSenderCfg struct { // IsMsgStale determines whether a message retrieved from the backing // 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 @@ -59,8 +61,9 @@ type reliableSender struct { activePeers map[[33]byte]peerManager activePeersMtx sync.Mutex - wg sync.WaitGroup - quit chan struct{} + wg sync.WaitGroup + quit chan struct{} + cancel fn.Option[context.CancelFunc] } // newReliableSender returns a new reliableSender backed by the given config. @@ -76,7 +79,10 @@ func newReliableSender(cfg *reliableSenderCfg) *reliableSender { func (s *reliableSender) Start() error { var err error s.start.Do(func() { - err = s.resendPendingMsgs() + ctx, cancel := context.WithCancel(context.Background()) + s.cancel = fn.Some(cancel) + + err = s.resendPendingMsgs(ctx) }) return err } @@ -87,6 +93,7 @@ func (s *reliableSender) Stop() { log.Debugf("reliableSender is stopping") defer log.Debugf("reliableSender stopped") + s.cancel.WhenSome(func(fn context.CancelFunc) { fn() }) close(s.quit) 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 // disk. Once the peer reconnects, this message, along with any others pending, // 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 // resend the message upon restarts and peer reconnections. 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 // pending messages while taking into account its connection lifecycle. 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 // 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 // one already active. The boolean returned signals whether there was already // one active or not. -func (s *reliableSender) spawnPeerHandler( +func (s *reliableSender) spawnPeerHandler(ctx context.Context, peerPubKey [33]byte) (peerManager, bool) { s.activePeersMtx.Lock() @@ -152,7 +161,7 @@ func (s *reliableSender) spawnPeerHandler( // peerHandler. if !ok { s.wg.Add(1) - go s.peerHandler(msgHandler, peerPubKey) + go s.peerHandler(ctx, msgHandler, peerPubKey) } return msgHandler, ok @@ -164,7 +173,9 @@ func (s *reliableSender) spawnPeerHandler( // offline will be queued and sent once the peer reconnects. // // 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() // We'll start by requesting a notification for when the peer @@ -252,7 +263,7 @@ out: // check whether it's stale. This guarantees that // AnnounceSignatures are sent at least once if we happen to // already have signatures for both parties. - if s.cfg.IsMsgStale(msg) { + if s.cfg.IsMsgStale(ctx, msg) { err := s.cfg.MessageStore.DeleteMessage(msg, peerPubKey) if err != nil { log.Errorf("Unable to remove stale %v message "+ @@ -321,7 +332,7 @@ out: // resendPendingMsgs retrieves and sends all of the messages within the message // 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 // spawn a peerMsgHandler for each. Once the peer is seen as online, all // of the pending messages will be sent. @@ -331,7 +342,7 @@ func (s *reliableSender) resendPendingMsgs() error { } for peer := range peers { - s.spawnPeerHandler(peer) + s.spawnPeerHandler(ctx, peer) } return nil diff --git a/discovery/reliable_sender_test.go b/discovery/reliable_sender_test.go index 19fdaa1ca..fc94d57f3 100644 --- a/discovery/reliable_sender_test.go +++ b/discovery/reliable_sender_test.go @@ -1,6 +1,7 @@ package discovery import ( + "context" "fmt" "sync/atomic" "testing" @@ -11,6 +12,7 @@ import ( "github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwire" + "github.com/stretchr/testify/require" ) // newTestReliableSender creates a new reliable sender instance used for @@ -32,7 +34,7 @@ func newTestReliableSender(t *testing.T) *reliableSender { return c }, MessageStore: newMockMessageStore(), - IsMsgStale: func(lnwire.Message) bool { + IsMsgStale: func(context.Context, lnwire.Message) bool { 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. func TestReliableSenderFlow(t *testing.T) { t.Parallel() + ctx := context.Background() reliableSender := newTestReliableSender(t) @@ -98,9 +101,8 @@ func TestReliableSenderFlow(t *testing.T) { msg1 := randChannelUpdate() var peerPubKey [33]byte copy(peerPubKey[:], pubKey.SerializeCompressed()) - if err := reliableSender.sendMessage(msg1, peerPubKey); err != nil { - t.Fatalf("unable to reliably send message: %v", err) - } + err := reliableSender.sendMessage(ctx, msg1, peerPubKey) + require.NoError(t, err) // 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 @@ -114,9 +116,8 @@ func TestReliableSenderFlow(t *testing.T) { // We'll then attempt to send another additional message reliably. msg2 := randAnnounceSignatures() - if err := reliableSender.sendMessage(msg2, peerPubKey); err != nil { - t.Fatalf("unable to reliably send message: %v", err) - } + err = reliableSender.sendMessage(ctx, msg2, peerPubKey) + require.NoError(t, err) // This should not however request another peer online notification as // 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. msg3 := randChannelUpdate() - if err := reliableSender.sendMessage(msg3, peerPubKey); err != nil { - t.Fatalf("unable to reliably send message: %v", err) - } + err = reliableSender.sendMessage(ctx, msg3, peerPubKey) + require.NoError(t, err) // Again, this should not request another peer online notification // request since we are currently waiting for the peer to be offline. @@ -188,6 +188,7 @@ func TestReliableSenderFlow(t *testing.T) { // them as stale. func TestReliableSenderStaleMessages(t *testing.T) { t.Parallel() + ctx := context.Background() 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 // 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 } @@ -215,9 +218,8 @@ func TestReliableSenderStaleMessages(t *testing.T) { msg1 := randAnnounceSignatures() var peerPubKey [33]byte copy(peerPubKey[:], pubKey.SerializeCompressed()) - if err := reliableSender.sendMessage(msg1, peerPubKey); err != nil { - t.Fatalf("unable to reliably send message: %v", err) - } + err := reliableSender.sendMessage(ctx, msg1, peerPubKey) + require.NoError(t, err) // 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 @@ -245,7 +247,7 @@ func TestReliableSenderStaleMessages(t *testing.T) { // 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 // 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( peerPubKey, ) @@ -265,14 +267,15 @@ func TestReliableSenderStaleMessages(t *testing.T) { } // 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 } // We'll request the message to be sent reliably. - if err := reliableSender.sendMessage(msg2, peerPubKey); err != nil { - t.Fatalf("unable to reliably send message: %v", err) - } + err = reliableSender.sendMessage(ctx, msg2, peerPubKey) + require.NoError(t, err) // We should see an online notification request indicating that a new // peerHandler has been spawned since it was previously torn down. diff --git a/discovery/syncer.go b/discovery/syncer.go index da3749050..b27cc8c71 100644 --- a/discovery/syncer.go +++ b/discovery/syncer.go @@ -409,16 +409,18 @@ func (g *GossipSyncer) Start() { g.started.Do(func() { log.Debugf("Starting GossipSyncer(%x)", g.cfg.peerPub[:]) + ctx, _ := g.cg.Create(context.Background()) + // TODO(conner): only spawn channelGraphSyncer if remote // supports gossip queries, and only spawn replyHandler if we // advertise support if !g.cfg.noSyncChannels { g.cg.WgAdd(1) - go g.channelGraphSyncer() + go g.channelGraphSyncer(ctx) } if !g.cfg.noReplyQueries { 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 // in this state, we will send a QueryChannelRange msg to our peer and advance // the syncer's state to waitingQueryRangeReply. -func (g *GossipSyncer) handleSyncingChans() { +func (g *GossipSyncer) handleSyncingChans(ctx context.Context) { // Prepare the query msg. - queryRangeMsg, err := g.genChanRangeQuery(g.genHistoricalChanRangeQuery) + queryRangeMsg, err := g.genChanRangeQuery( + ctx, g.genHistoricalChanRangeQuery, + ) if err != nil { log.Errorf("Unable to gen chan range query: %v", err) return @@ -456,7 +460,6 @@ func (g *GossipSyncer) handleSyncingChans() { // Send the msg to the remote peer, which is non-blocking as // `sendToPeer` only queues the msg in Brontide. - ctx, _ := g.cg.Create(context.Background()) err = g.cfg.sendToPeer(ctx, queryRangeMsg) if err != nil { 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 // properly channel graph state with the remote peer, and also that we only // 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() for { @@ -488,7 +491,7 @@ func (g *GossipSyncer) channelGraphSyncer() { // understand, as we'll as responding to any other queries by // them. case syncingChans: - g.handleSyncingChans() + g.handleSyncingChans(ctx) // In this state, we've sent out our initial channel range // query and are waiting for the final response from the remote @@ -507,7 +510,9 @@ func (g *GossipSyncer) channelGraphSyncer() { // for the new channels. queryReply, ok := msg.(*lnwire.ReplyChannelRange) if ok { - err := g.processChanRangeReply(queryReply) + err := g.processChanRangeReply( + ctx, queryReply, + ) if err != nil { log.Errorf("Unable to "+ "process chan range "+ @@ -522,6 +527,9 @@ func (g *GossipSyncer) channelGraphSyncer() { case <-g.cg.Done(): return + + case <-ctx.Done(): + return } // 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 // synchronization by continuing to send off another // query chunk. - done := g.synchronizeChanIDs() + done := g.synchronizeChanIDs(ctx) // If this wasn't our last query, then we'll need to // transition to our waiting state. @@ -572,6 +580,9 @@ func (g *GossipSyncer) channelGraphSyncer() { case <-g.cg.Done(): return + + case <-ctx.Done(): + return } // This is our final terminal state where we'll only reply to @@ -591,7 +602,7 @@ func (g *GossipSyncer) channelGraphSyncer() { syncType.IsActiveSync() { err := g.sendGossipTimestampRange( - time.Now(), math.MaxUint32, + ctx, time.Now(), math.MaxUint32, ) if err != nil { log.Errorf("Unable to send update "+ @@ -611,13 +622,16 @@ func (g *GossipSyncer) channelGraphSyncer() { case syncerIdle: select { case req := <-g.syncTransitionReqs: - req.errChan <- g.handleSyncTransition(req) + req.errChan <- g.handleSyncTransition(ctx, req) case req := <-g.historicalSyncReqs: g.handleHistoricalSync(req) case <-g.cg.Done(): return + + case <-ctx.Done(): + return } } } @@ -630,13 +644,13 @@ func (g *GossipSyncer) channelGraphSyncer() { // from the state machine maintained on the same node. // // NOTE: This method MUST be run as a goroutine. -func (g *GossipSyncer) replyHandler() { +func (g *GossipSyncer) replyHandler(ctx context.Context) { defer g.cg.WgDone() for { select { case msg := <-g.queryMsgs: - err := g.replyPeerQueries(msg) + err := g.replyPeerQueries(ctx, msg) switch { case err == ErrGossipSyncerExiting: return @@ -651,14 +665,17 @@ func (g *GossipSyncer) replyHandler() { case <-g.cg.Done(): return + + case <-ctx.Done(): + return } } } // sendGossipTimestampRange constructs and sets a GossipTimestampRange for the // syncer and sends it to the remote peer. -func (g *GossipSyncer) sendGossipTimestampRange(firstTimestamp time.Time, - timestampRange uint32) error { +func (g *GossipSyncer) sendGossipTimestampRange(ctx context.Context, + firstTimestamp time.Time, timestampRange uint32) error { endTimestamp := firstTimestamp.Add( time.Duration(timestampRange) * time.Second, @@ -673,7 +690,6 @@ func (g *GossipSyncer) sendGossipTimestampRange(firstTimestamp time.Time, TimestampRange: timestampRange, } - ctx, _ := g.cg.Create(context.Background()) if err := g.cfg.sendToPeer(ctx, localUpdateHorizon); err != nil { 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 // required to ensure they fit into a single message. We may re-renter this // 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 // for, then we'll transition to our final synced state and return true // 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 // false indicating that we're net yet fully synced. - ctx, _ := g.cg.Create(context.Background()) err := g.cfg.sendToPeer(ctx, &lnwire.QueryShortChanIDs{ ChainHash: g.cfg.chainHash, EncodingType: lnwire.EncodingSortedPlain, @@ -759,7 +774,9 @@ func isLegacyReplyChannelRange(query *lnwire.QueryChannelRange, // processChanRangeReply is called each time the GossipSyncer receives a new // reply to the initial range query to discover new channels that it didn't // 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 := func(timestamp time.Time) bool { 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 // connection. The historicalQuery boolean can be used to generate a query from // the genesis block of the chain. -func (g *GossipSyncer) genChanRangeQuery( +func (g *GossipSyncer) genChanRangeQuery(_ context.Context, historicalQuery bool) (*lnwire.QueryChannelRange, error) { // 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. // 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) { // 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. case *lnwire.QueryChannelRange: - return g.replyChanRangeQuery(msg) + return g.replyChanRangeQuery(ctx, msg) // 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. case *lnwire.QueryShortChanIDs: - return g.replyShortChanIDs(msg) + return g.replyShortChanIDs(ctx, msg) default: 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 // ensure that our final fragment carries the "complete" bit to indicate the // 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 // 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 @@ -1038,8 +1059,6 @@ func (g *GossipSyncer) replyChanRangeQuery(query *lnwire.QueryChannelRange) erro "chain=%v, we're on chain=%v", query.ChainHash, g.cfg.chainHash) - ctx, _ := g.cg.Create(context.Background()) - return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyChannelRange{ ChainHash: query.ChainHash, 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{ ChainHash: query.ChainHash, 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 // will be sent in a streaming chunked manner to ensure that we remain below // 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 // 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 @@ -1219,8 +1238,6 @@ func (g *GossipSyncer) replyShortChanIDs(query *lnwire.QueryShortChanIDs) error "chain=%v, we're on chain=%v", query.ChainHash, g.cfg.chainHash) - ctx, _ := g.cg.Create(context.Background()) - return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyShortChanIDsEnd{ ChainHash: query.ChainHash, Complete: 0, @@ -1252,7 +1269,6 @@ func (g *GossipSyncer) replyShortChanIDs(query *lnwire.QueryShortChanIDs) error // each one individually and synchronously to throttle the sends and // perform buffering of responses in the syncer as opposed to the peer. for _, msg := range replyMsgs { - ctx, _ := g.cg.Create(context.Background()) err := g.cfg.sendToPeerSync(ctx, msg) if err != nil { 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 // the sentinel message to signal that the stream has terminated. - ctx, _ := g.cg.Create(context.Background()) - return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyShortChanIDsEnd{ ChainHash: query.ChainHash, 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 // 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. -func (g *GossipSyncer) ApplyGossipFilter(filter *lnwire.GossipTimestampRange) error { +func (g *GossipSyncer) ApplyGossipFilter(ctx context.Context, + filter *lnwire.GossipTimestampRange) error { + g.Lock() g.remoteUpdateHorizon = filter @@ -1294,6 +1310,8 @@ func (g *GossipSyncer) ApplyGossipFilter(filter *lnwire.GossipTimestampRange) er case <-g.syncerSema: case <-g.cg.Done(): return ErrGossipSyncerExiting + case <-ctx.Done(): + return ctx.Err() } // 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() for _, msg := range newUpdatestoSend { - ctx, _ := g.cg.Create(context.Background()) err := g.cfg.sendToPeerSync(ctx, msg) switch { 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 // 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. -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 // it any new update messages. if g.remoteUpdateHorizon == nil { @@ -1365,6 +1384,8 @@ func (g *GossipSyncer) FilterGossipMsgs(msgs ...msgWithSenders) { select { case <-g.cg.Done(): return + case <-ctx.Done(): + return default: } @@ -1472,7 +1493,6 @@ func (g *GossipSyncer) FilterGossipMsgs(msgs ...msgWithSenders) { return } - ctx, _ := g.cg.Create(context.Background()) if err = g.cfg.sendToPeer(ctx, msgsToSend...); err != nil { 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 // 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. syncType := g.SyncType() if syncType == req.newSyncType { @@ -1608,7 +1630,7 @@ func (g *GossipSyncer) handleSyncTransition(req *syncTransitionReq) error { req.newSyncType) } - err := g.sendGossipTimestampRange(firstTimestamp, timestampRange) + err := g.sendGossipTimestampRange(ctx, firstTimestamp, timestampRange) if err != nil { return fmt.Errorf("unable to send local update horizon: %w", err) diff --git a/discovery/syncer_test.go b/discovery/syncer_test.go index a63997909..44e8d6d70 100644 --- a/discovery/syncer_test.go +++ b/discovery/syncer_test.go @@ -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. func TestGossipSyncerFilterGossipMsgsNoHorizon(t *testing.T) { t.Parallel() + ctx := context.Background() // First, we'll create a GossipSyncer instance with a canned sendToPeer // 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 // peer. - syncer.FilterGossipMsgs(msgs...) + syncer.FilterGossipMsgs(ctx, msgs...) // As the remote peer doesn't yet have a gossip timestamp set, we // 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. func TestGossipSyncerFilterGossipMsgsAllInMemory(t *testing.T) { t.Parallel() + ctx := context.Background() // First, we'll create a GossipSyncer instance with a canned sendToPeer // 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. - syncer.FilterGossipMsgs(msgs...) + syncer.FilterGossipMsgs(ctx, msgs...) // Out of all the messages we sent in, we should only get 2 of them // back. @@ -415,6 +417,7 @@ func TestGossipSyncerFilterGossipMsgsAllInMemory(t *testing.T) { // messages which are within their desired time horizon. func TestGossipSyncerApplyNoHistoricalGossipFilter(t *testing.T) { t.Parallel() + ctx := context.Background() // First, we'll create a GossipSyncer instance with a canned sendToPeer // 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. - syncer.ApplyGossipFilter(remoteHorizon) + require.NoError(t, syncer.ApplyGossipFilter(ctx, remoteHorizon)) // Ensure that the syncer's remote horizon was properly updated. if !reflect.DeepEqual(syncer.remoteUpdateHorizon, remoteHorizon) { @@ -475,6 +478,7 @@ func TestGossipSyncerApplyNoHistoricalGossipFilter(t *testing.T) { // within their desired time horizon. func TestGossipSyncerApplyGossipFilter(t *testing.T) { t.Parallel() + ctx := context.Background() // First, we'll create a GossipSyncer instance with a canned sendToPeer // 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. - err := syncer.ApplyGossipFilter(remoteHorizon) + err := syncer.ApplyGossipFilter(ctx, remoteHorizon) require.NoError(t, err, "unable to apply filter") // 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 } }() - err = syncer.ApplyGossipFilter(remoteHorizon) + err = syncer.ApplyGossipFilter(ctx, remoteHorizon) require.NoError(t, err, "unable to apply filter") // We should get back the exact same message. @@ -594,6 +598,7 @@ func TestGossipSyncerApplyGossipFilter(t *testing.T) { // channels and complete=0. func TestGossipSyncerQueryChannelRangeWrongChainHash(t *testing.T) { t.Parallel() + ctx := context.Background() // First, we'll create a GossipSyncer instance with a canned sendToPeer // message to allow us to intercept their potential sends. @@ -609,7 +614,7 @@ func TestGossipSyncerQueryChannelRangeWrongChainHash(t *testing.T) { FirstBlockHeight: 0, NumBlocks: math.MaxUint32, } - err := syncer.replyChanRangeQuery(query) + err := syncer.replyChanRangeQuery(ctx, query) require.NoError(t, err, "unable to process short chan ID's") select { @@ -646,6 +651,7 @@ func TestGossipSyncerQueryChannelRangeWrongChainHash(t *testing.T) { // complete=0. func TestGossipSyncerReplyShortChanIDsWrongChainHash(t *testing.T) { t.Parallel() + ctx := context.Background() // First, we'll create a GossipSyncer instance with a canned sendToPeer // 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 // chain that it isn't aware of. - err := syncer.replyShortChanIDs(&lnwire.QueryShortChanIDs{ + err := syncer.replyShortChanIDs(ctx, &lnwire.QueryShortChanIDs{ ChainHash: *chaincfg.SimNetParams.GenesisHash, }) 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. func TestGossipSyncerReplyShortChanIDs(t *testing.T) { t.Parallel() + ctx := context.Background() // First, we'll create a GossipSyncer instance with a canned sendToPeer // 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 // from the channel syncer for our target chan ID query. - err := syncer.replyShortChanIDs(&lnwire.QueryShortChanIDs{ + err := syncer.replyShortChanIDs(ctx, &lnwire.QueryShortChanIDs{ ShortChanIDs: queryChanIDs, }) require.NoError(t, err, "unable to query for chan IDs") @@ -800,6 +807,7 @@ func TestGossipSyncerReplyShortChanIDs(t *testing.T) { // the remote peer. func TestGossipSyncerReplyChanRangeQuery(t *testing.T) { t.Parallel() + ctx := context.Background() // We'll use a smaller chunk size so we can easily test all the edge // cases. @@ -866,7 +874,7 @@ func TestGossipSyncerReplyChanRangeQuery(t *testing.T) { }() // 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) } @@ -971,6 +979,7 @@ func TestGossipSyncerReplyChanRangeQuery(t *testing.T) { // executed with the correct block range. func TestGossipSyncerReplyChanRangeQueryBlockRange(t *testing.T) { t.Parallel() + ctx := context.Background() // First create our test gossip syncer that will handle and // respond to the test queries @@ -1052,7 +1061,8 @@ func TestGossipSyncerReplyChanRangeQueryBlockRange(t *testing.T) { // will be reached go func() { 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", err) return @@ -1083,6 +1093,7 @@ func TestGossipSyncerReplyChanRangeQueryBlockRange(t *testing.T) { // back a single response that signals completion. func TestGossipSyncerReplyChanRangeQueryNoNewChans(t *testing.T) { t.Parallel() + ctx := context.Background() // We'll now create our test gossip syncer that will shortly respond to // our canned query. @@ -1121,7 +1132,7 @@ func TestGossipSyncerReplyChanRangeQueryNoNewChans(t *testing.T) { }() // 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) } @@ -1162,6 +1173,7 @@ func TestGossipSyncerReplyChanRangeQueryNoNewChans(t *testing.T) { // channel ID, we properly generate an correct initial channel range response. func TestGossipSyncerGenChanRangeQuery(t *testing.T) { t.Parallel() + ctx := context.Background() // First, we'll create a GossipSyncer instance with a canned sendToPeer // 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 // should return a start height that's back chanRangeQueryBuffer // blocks. - rangeQuery, err := syncer.genChanRangeQuery(false) + rangeQuery, err := syncer.genChanRangeQuery(ctx, false) require.NoError(t, err, "unable to resp") firstHeight := uint32(startingHeight - chanRangeQueryBuffer) @@ -1190,7 +1202,7 @@ func TestGossipSyncerGenChanRangeQuery(t *testing.T) { // Generating a historical range query should result in a start height // of 0. - rangeQuery, err = syncer.genChanRangeQuery(true) + rangeQuery, err = syncer.genChanRangeQuery(ctx, true) require.NoError(t, err, "unable to resp") if rangeQuery.FirstBlockHeight != 0 { t.Fatalf("incorrect chan range query: expected %v, %v", 0, @@ -1222,6 +1234,7 @@ func TestGossipSyncerProcessChanRangeReply(t *testing.T) { // each reply instead. func testGossipSyncerProcessChanRangeReply(t *testing.T, legacy bool) { t.Parallel() + ctx := context.Background() // First, we'll create a GossipSyncer instance with a canned sendToPeer // message to allow us to intercept their potential sends. @@ -1234,7 +1247,7 @@ func testGossipSyncerProcessChanRangeReply(t *testing.T, legacy bool) { startingState := syncer.state - query, err := syncer.genChanRangeQuery(true) + query, err := syncer.genChanRangeQuery(ctx, true) require.NoError(t, err, "unable to generate channel range query") 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 // 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) } - 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) } - 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) } @@ -1427,7 +1440,7 @@ func testGossipSyncerProcessChanRangeReply(t *testing.T, legacy bool) { // If we send the final message, then we should transition to // 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) } @@ -1482,7 +1495,7 @@ func TestGossipSyncerSynchronizeChanIDs(t *testing.T) { for i := 0; i < chunkSize*2; i += 2 { // 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 // 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. - done := syncer.synchronizeChanIDs() + done := syncer.synchronizeChanIDs(context.Background()) if done { t.Fatalf("syncer should be finished!") } diff --git a/docs/release-notes/release-notes-0.20.0.md b/docs/release-notes/release-notes-0.20.0.md index 1ee2e05fa..fd8df9579 100644 --- a/docs/release-notes/release-notes-0.20.0.md +++ b/docs/release-notes/release-notes-0.20.0.md @@ -31,6 +31,14 @@ # Improvements ## 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 ## lncli Updates diff --git a/go.mod b/go.mod index 1dd775257..8cdddddb5 100644 --- a/go.mod +++ b/go.mod @@ -199,6 +199,10 @@ require ( 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 replace github.com/ulikunitz/xz => github.com/ulikunitz/xz v0.5.11 diff --git a/go.sum b/go.sum index cc17829d6..1593ff474 100644 --- a/go.sum +++ b/go.sum @@ -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/queue v1.1.1 h1:99ovBlpM9B0FRCGYJo6RSFDlt8/vOkQQZznVb18iNMI= 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/go.mod h1:waPTRAAcwtu7Ji3+3k+u/xH5GHovTsCoSVpho0KDvdA= github.com/lightningnetwork/lnd/tlv v1.3.1 h1:o7CZg06y+rJZfUMAo0WzBLr0pgBWCzrt0f9gpujYUzk= diff --git a/graph/builder.go b/graph/builder.go index f92b523b0..3350eeb33 100644 --- a/graph/builder.go +++ b/graph/builder.go @@ -13,7 +13,6 @@ import ( "github.com/lightningnetwork/lnd/chainntnfs" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" - "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" @@ -1276,8 +1275,7 @@ func (b *Builder) ForAllOutgoingChannels(cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy) error) error { return b.cfg.Graph.ForEachNodeChannel(b.cfg.SelfNode, - func(_ kvdb.RTx, c *models.ChannelEdgeInfo, - e *models.ChannelEdgePolicy, + func(c *models.ChannelEdgeInfo, e *models.ChannelEdgePolicy, _ *models.ChannelEdgePolicy) error { if e == nil { diff --git a/graph/builder_test.go b/graph/builder_test.go index b813c17e2..fa7fc722d 100644 --- a/graph/builder_test.go +++ b/graph/builder_test.go @@ -1352,10 +1352,7 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( testAddrs = append(testAddrs, testAddr) // Next, create a temporary graph database for usage within the test. - graph, graphBackend, err := makeTestGraph(t, useCache) - if err != nil { - return nil, err - } + graph := graphdb.MakeTestGraph(t, graphdb.WithUseGraphCache(useCache)) aliasMap := make(map[string]route.Vertex) privKeyMap := make(map[string]*btcec.PrivateKey) @@ -1562,12 +1559,11 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( } return &testGraphInstance{ - graph: graph, - graphBackend: graphBackend, - aliasMap: aliasMap, - privKeyMap: privKeyMap, - channelIDs: channelIDs, - links: links, + graph: graph, + aliasMap: aliasMap, + privKeyMap: privKeyMap, + channelIDs: channelIDs, + links: links, }, nil } @@ -1730,10 +1726,7 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, testAddrs = append(testAddrs, testAddr) // Next, create a temporary graph database for usage within the test. - graph, graphBackend, err := makeTestGraph(t, useCache) - if err != nil { - return nil, err - } + graph := graphdb.MakeTestGraph(t, graphdb.WithUseGraphCache(useCache)) aliasMap := make(map[string]route.Vertex) privKeyMap := make(map[string]*btcec.PrivateKey) @@ -1947,11 +1940,10 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, } return &testGraphInstance{ - graph: graph, - graphBackend: graphBackend, - aliasMap: aliasMap, - privKeyMap: privKeyMap, - links: links, + graph: graph, + aliasMap: aliasMap, + privKeyMap: privKeyMap, + links: links, }, nil } diff --git a/graph/db/addr_test.go b/graph/db/addr_test.go index 2e6e5439c..b3dbea8cb 100644 --- a/graph/db/addr_test.go +++ b/graph/db/addr_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/lightningnetwork/lnd/tor" + "github.com/stretchr/testify/require" ) type unknownAddrType struct{} @@ -14,37 +15,47 @@ type unknownAddrType struct{} func (t unknownAddrType) Network() 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 ( + 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 { expAddr net.Addr serErr string }{ // Valid addresses. { - expAddr: &net.TCPAddr{ - IP: testIP4, - Port: 12345, - }, + expAddr: testIPV4Addr, }, { - expAddr: &net.TCPAddr{ - IP: testIP6, - Port: 65535, - }, + expAddr: testIPV6Addr, }, { - expAddr: &tor.OnionAddr{ - OnionService: "3g2upl4pq6kufc4m.onion", - Port: 9735, - }, + expAddr: testOnionV2Addr, }, { - expAddr: &tor.OnionAddr{ - OnionService: "vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion", - Port: 80, - }, + expAddr: testOnionV3Addr, }, // Invalid addresses. @@ -141,9 +152,6 @@ func TestAddrSerialization(t *testing.T) { t.Fatalf("unable to deserialize address: %v", err) } - if addr.String() != test.expAddr.String() { - t.Fatalf("expected address %v after serialization, "+ - "got %v", addr, test.expAddr) - } + require.Equal(t, test.expAddr, addr) } } diff --git a/graph/db/graph.go b/graph/db/graph.go index 4327f8951..cf3b3ab70 100644 --- a/graph/db/graph.go +++ b/graph/db/graph.go @@ -5,33 +5,22 @@ import ( "fmt" "sync" "sync/atomic" + "testing" "time" "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/kvdb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" + "github.com/stretchr/testify/require" ) // ErrChanGraphShuttingDown indicates that the ChannelGraph has shutdown or is // busy 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. // // NOTE: currently, this is purely a pass-through layer directly to the backing @@ -43,7 +32,7 @@ type ChannelGraph struct { graphCache *GraphCache - *KVStore + V1Store *topologyManager quit chan struct{} @@ -51,21 +40,16 @@ type ChannelGraph struct { } // NewChannelGraph creates a new ChannelGraph instance with the given backend. -func NewChannelGraph(cfg *Config, options ...ChanGraphOption) (*ChannelGraph, - error) { +func NewChannelGraph(v1Store V1Store, + options ...ChanGraphOption) (*ChannelGraph, error) { opts := defaultChanGraphOptions() for _, o := range options { o(opts) } - store, err := NewKVStore(cfg.KVDB, cfg.KVStoreOpts...) - if err != nil { - return nil, err - } - g := &ChannelGraph{ - KVStore: store, + V1Store: v1Store, topologyManager: newTopologyManager(), quit: make(chan struct{}), } @@ -179,7 +163,7 @@ func (c *ChannelGraph) populateCache() error { log.Info("Populating in-memory channel graph, this might take a " + "while...") - err := c.KVStore.ForEachNodeCacheable(func(node route.Vertex, + err := c.V1Store.ForEachNodeCacheable(func(node route.Vertex, features *lnwire.FeatureVector) error { c.graphCache.AddNodeFeatures(node, features) @@ -190,7 +174,7 @@ func (c *ChannelGraph) populateCache() error { return err } - err = c.KVStore.ForEachChannel(func(info *models.ChannelEdgeInfo, + err = c.V1Store.ForEachChannel(func(info *models.ChannelEdgeInfo, policy1, policy2 *models.ChannelEdgePolicy) error { 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.KVStore.ForEachNodeDirectedChannel(node, cb) + return c.V1Store.ForEachNodeDirectedChannel(node, cb) } // 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.KVStore.FetchNodeFeatures(node) + return c.V1Store.FetchNodeFeatures(node) } // 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 c.KVStore.GraphSession(cb) + return c.V1Store.GraphSession(cb) } // 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.KVStore.ForEachNodeCached(cb) + return c.V1Store.ForEachNodeCached(cb) } // 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, op ...batch.SchedulerOption) error { - err := c.KVStore.AddLightningNode(node, op...) + err := c.V1Store.AddLightningNode(node, op...) if err != nil { return err } @@ -301,7 +285,7 @@ func (c *ChannelGraph) AddLightningNode(node *models.LightningNode, // DeleteLightningNode starts a new database transaction to remove a vertex/node // from the database according to the node's public key. func (c *ChannelGraph) DeleteLightningNode(nodePub route.Vertex) error { - err := c.KVStore.DeleteLightningNode(nodePub) + err := c.V1Store.DeleteLightningNode(nodePub) if err != nil { return err } @@ -322,7 +306,7 @@ func (c *ChannelGraph) DeleteLightningNode(nodePub route.Vertex) error { func (c *ChannelGraph) AddChannelEdge(edge *models.ChannelEdgeInfo, op ...batch.SchedulerOption) error { - err := c.KVStore.AddChannelEdge(edge, op...) + err := c.V1Store.AddChannelEdge(edge, op...) if err != nil { 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 // we still have a record of this channel in the DB. func (c *ChannelGraph) MarkEdgeLive(chanID uint64) error { - err := c.KVStore.MarkEdgeLive(chanID) + err := c.V1Store.MarkEdgeLive(chanID) if err != nil { return err } @@ -352,7 +336,7 @@ func (c *ChannelGraph) MarkEdgeLive(chanID uint64) error { if c.graphCache != nil { // We need to add the channel back into our graph cache, // 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 { return err } @@ -380,7 +364,7 @@ func (c *ChannelGraph) MarkEdgeLive(chanID uint64) error { func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning, markZombie bool, chanIDs ...uint64) error { - infos, err := c.KVStore.DeleteChannelEdges( + infos, err := c.V1Store.DeleteChannelEdges( strictZombiePruning, markZombie, chanIDs..., ) if err != nil { @@ -409,7 +393,7 @@ func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning, markZombie bool, func (c *ChannelGraph) DisconnectBlockAtHeight(height uint32) ( []*models.ChannelEdgeInfo, error) { - edges, err := c.KVStore.DisconnectBlockAtHeight(height) + edges, err := c.V1Store.DisconnectBlockAtHeight(height) if err != nil { return nil, err } @@ -437,7 +421,7 @@ func (c *ChannelGraph) PruneGraph(spentOutputs []*wire.OutPoint, blockHash *chainhash.Hash, blockHeight uint32) ( []*models.ChannelEdgeInfo, error) { - edges, nodes, err := c.KVStore.PruneGraph( + edges, nodes, err := c.V1Store.PruneGraph( spentOutputs, blockHash, blockHeight, ) 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 // node gains more channels, it will be re-added back to the graph. func (c *ChannelGraph) PruneGraphNodes() error { - nodes, err := c.KVStore.PruneGraphNodes() + nodes, err := c.V1Store.PruneGraphNodes() if err != nil { return err } @@ -504,7 +488,7 @@ func (c *ChannelGraph) PruneGraphNodes() error { func (c *ChannelGraph) FilterKnownChanIDs(chansInfo []ChannelUpdateInfo, 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 { 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 // alive, and we let it be added to the set of IDs to query our // peer for. - err := c.KVStore.MarkEdgeLive( + err := c.V1Store.MarkEdgeLive( info.ShortChannelID.ToUint64(), ) // 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, pubKey1, pubKey2 [33]byte) error { - err := c.KVStore.MarkEdgeZombie(chanID, pubKey1, pubKey2) + err := c.V1Store.MarkEdgeZombie(chanID, pubKey1, pubKey2) if err != nil { return err } @@ -576,7 +560,7 @@ func (c *ChannelGraph) MarkEdgeZombie(chanID uint64, func (c *ChannelGraph) UpdateEdgePolicy(edge *models.ChannelEdgePolicy, op ...batch.SchedulerOption) error { - from, to, err := c.KVStore.UpdateEdgePolicy(edge, op...) + from, to, err := c.V1Store.UpdateEdgePolicy(edge, op...) if err != nil { return err } @@ -598,3 +582,30 @@ func (c *ChannelGraph) UpdateEdgePolicy(edge *models.ChannelEdgePolicy, 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 +} diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go index 754da5eef..2f2527a14 100644 --- a/graph/db/graph_test.go +++ b/graph/db/graph_test.go @@ -19,6 +19,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/graph/db/models" @@ -34,7 +35,10 @@ var ( Port: 9000} anotherAddr, _ = net.ResolveTCPAddr("tcp", "[2001:db8:85a3:0:0:8a2e:370:7334]:80") - testAddrs = []net.Addr{testAddr, anotherAddr} + testAddrs = []net.Addr{testAddr, anotherAddr} + testOpaqueAddr = &lnwire.OpaqueAddrs{ + Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, + } testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571319d18" + "e949ddfa2965fb6caa1bf0314f882d7") @@ -66,9 +70,7 @@ var ( } ) -func createLightningNode(_ kvdb.Backend, priv *btcec.PrivateKey) ( - *models.LightningNode, error) { - +func createLightningNode(priv *btcec.PrivateKey) *models.LightningNode { updateTime := prand.Int63() pub := priv.PubKey().SerializeCompressed() @@ -77,46 +79,49 @@ func createLightningNode(_ kvdb.Backend, priv *btcec.PrivateKey) ( AuthSigBytes: testSig.Serialize(), LastUpdate: time.Unix(updateTime, 0), Color: color.RGBA{1, 2, 3, 0}, - Alias: "kek" + string(pub[:]), + Alias: "kek" + hex.EncodeToString(pub), Features: testFeatures, Addresses: testAddrs, + ExtraOpaqueData: make([]byte, 0), } copy(n.PubKeyBytes[:], priv.PubKey().SerializeCompressed()) - return n, nil + return n } -func createTestVertex(db kvdb.Backend) (*models.LightningNode, error) { - priv, err := btcec.NewPrivateKey() - if err != nil { - return nil, err - } +func createTestVertex(t testing.TB) *models.LightningNode { + t.Helper() - return createLightningNode(db, priv) + priv, err := btcec.NewPrivateKey() + require.NoError(t, err) + + return createLightningNode(priv) } func TestNodeInsertionAndDeletion(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) // We'd like to test basic insertion/deletion for vertexes from the // graph, so we'll create a test vertex to start with. - node := &models.LightningNode{ - HaveNodeAnnouncement: true, - AuthSigBytes: testSig.Serialize(), - LastUpdate: time.Unix(1232342, 0), - Color: color.RGBA{1, 2, 3, 0}, - Alias: "kek", - Features: testFeatures, - Addresses: testAddrs, - ExtraOpaqueData: []byte("extra new data"), - PubKeyBytes: testPub, + nodeWithAddrs := func(addrs []net.Addr) *models.LightningNode { + return &models.LightningNode{ + HaveNodeAnnouncement: true, + AuthSigBytes: testSig.Serialize(), + LastUpdate: time.Unix(1232342, 0), + Color: color.RGBA{1, 2, 3, 0}, + Alias: "kek", + Features: testFeatures, + Addresses: addrs, + ExtraOpaqueData: []byte{1, 1, 1, 2, 2, 2, 2}, + PubKeyBytes: testPub, + } } // First, insert the node into the graph DB. This should succeed // without any errors. + node := nodeWithAddrs(testAddrs) if err := graph.AddLightningNode(node); err != nil { t.Fatalf("unable to add node: %v", err) } @@ -135,23 +140,119 @@ func TestNodeInsertionAndDeletion(t *testing.T) { } // The two nodes should match exactly! - if err := compareNodes(node, dbNode); err != nil { - t.Fatalf("nodes don't match: %v", err) - } + compareNodes(t, node, dbNode) + + // Check that the node's features are fetched correctly. This check + // will use the graph cache to fetch the features. + features, err := graph.FetchNodeFeatures(node.PubKeyBytes) + require.NoError(t, err) + require.Equal(t, testFeatures, features) + + // Check that the node's features are fetched correctly. This check + // will check the database directly. + features, err = graph.V1Store.FetchNodeFeatures(node.PubKeyBytes) + require.NoError(t, err) + require.Equal(t, testFeatures, features) // Next, delete the node from the graph, this should purge all data // related to the node. - if err := graph.DeleteLightningNode(testPub); err != nil { - t.Fatalf("unable to delete node; %v", err) - } + require.NoError(t, graph.DeleteLightningNode(testPub)) assertNodeNotInCache(t, graph, testPub) + // Attempting to delete the node again should return an error since + // the node is no longer known. + require.ErrorIs( + t, graph.DeleteLightningNode(testPub), ErrGraphNodeNotFound, + ) + // Finally, attempt to fetch the node again. This should fail as the // node should have been deleted from the database. _, err = graph.FetchLightningNode(testPub) - if err != ErrGraphNodeNotFound { - t.Fatalf("fetch after delete should fail!") + require.ErrorIs(t, err, ErrGraphNodeNotFound) + + // Now, we'll specifically test the updating of addresses of a node + // since the serialisation and persistence of addresses is a bit + // tricky. + + pub, err := node.PubKey() + require.NoError(t, err) + + // Initially, the node is unknown to the graph and there should be no + // addresses for it. + known, addrs, err := graph.AddrsForNode(pub) + require.NoError(t, err) + require.False(t, known) + require.Empty(t, addrs) + + // Add the node without any addresses. + node = nodeWithAddrs(nil) + require.NoError(t, graph.AddLightningNode(node)) + + // Fetch the node and assert the empty addresses. + dbNode, err = graph.FetchLightningNode(testPub) + require.NoError(t, err) + require.Empty(t, dbNode.Addresses) + + known, addrs, err = graph.AddrsForNode(pub) + require.NoError(t, err) + require.True(t, known) + require.Empty(t, addrs) + + // Now, update the node's addresses. + expAddrs := []net.Addr{ + // Add 2 IPV4 addresses. + testAddr, + testIPV4Addr, + // Add 2 IPV6 addresses. + testIPV6Addr, + anotherAddr, + // Add one v2 and one v3 onion address. + testOnionV2Addr, + testOnionV3Addr, + // Make sure to also test the opaque address type. + testOpaqueAddr, } + node = nodeWithAddrs(expAddrs) + require.NoError(t, graph.AddLightningNode(node)) + + // Fetch the node and assert the updated addresses. + dbNode, err = graph.FetchLightningNode(testPub) + require.NoError(t, err) + require.Equal(t, expAddrs, dbNode.Addresses) + + known, addrs, err = graph.AddrsForNode(pub) + require.NoError(t, err) + require.True(t, known) + require.EqualValues(t, expAddrs, addrs) + + // Now, change the address set a bit: change the order of the + // IPV4 addresses, remove one IPV6 address and remove both onion + // addresses. + expAddrs = []net.Addr{ + testIPV4Addr, + testAddr, + testIPV6Addr, + } + node = nodeWithAddrs(expAddrs) + require.NoError(t, graph.AddLightningNode(node)) + + // Fetch the node and assert the updated addresses. + dbNode, err = graph.FetchLightningNode(testPub) + require.NoError(t, err) + require.Equal(t, expAddrs, dbNode.Addresses) + + // Finally, update the set to only contain the Tor addresses. + expAddrs = []net.Addr{ + testOnionV2Addr, + testOnionV3Addr, + } + node = nodeWithAddrs(expAddrs) + require.NoError(t, graph.AddLightningNode(node)) + + // Fetch the node and assert the updated addresses. + dbNode, err = graph.FetchLightningNode(testPub) + require.NoError(t, err) + require.Equal(t, expAddrs, dbNode.Addresses) } // TestPartialNode checks that we can add and retrieve a LightningNode where @@ -159,8 +260,7 @@ func TestNodeInsertionAndDeletion(t *testing.T) { func TestPartialNode(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) // To insert a partial node, we need to add a channel edge that has // node keys for nodes we are not yet aware @@ -194,8 +294,9 @@ func TestPartialNode(t *testing.T) { HaveNodeAnnouncement: false, LastUpdate: time.Unix(0, 0), PubKeyBytes: pubKey1, + Features: lnwire.EmptyFeatureVector(), } - require.NoError(t, compareNodes(dbNode1, expectedNode1)) + compareNodes(t, expectedNode1, dbNode1) _, exists, err = graph.HasLightningNode(dbNode2.PubKeyBytes) require.NoError(t, err) @@ -207,8 +308,9 @@ func TestPartialNode(t *testing.T) { HaveNodeAnnouncement: false, LastUpdate: time.Unix(0, 0), PubKeyBytes: pubKey2, + Features: lnwire.EmptyFeatureVector(), } - require.NoError(t, compareNodes(dbNode2, expectedNode2)) + compareNodes(t, expectedNode2, dbNode2) // Next, delete the node from the graph, this should purge all data // related to the node. @@ -224,13 +326,11 @@ func TestPartialNode(t *testing.T) { func TestAliasLookup(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) // We'd like to test the alias index within the database, so first // create a new test node. - testNode, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + testNode := createTestVertex(t) // Add the node to the graph's database, this should also insert an // entry into the alias index for this node. @@ -250,32 +350,26 @@ func TestAliasLookup(t *testing.T) { } // Ensure that looking up a non-existent alias results in an error. - node, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node := createTestVertex(t) nodePub, err = node.PubKey() require.NoError(t, err, "unable to generate pubkey") _, err = graph.LookupAlias(nodePub) - if err != ErrNodeAliasNotFound { - t.Fatalf("alias lookup should fail for non-existent pubkey") - } + require.ErrorIs(t, err, ErrNodeAliasNotFound) } func TestSourceNode(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) // We'd like to test the setting/getting of the source node, so we // first create a fake node to use within the test. - testNode, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + testNode := createTestVertex(t) // Attempt to fetch the source node, this should return an error as the // source node hasn't yet been set. - if _, err := graph.SourceNode(); err != ErrSourceNodeNotSet { - t.Fatalf("source node shouldn't be set in new graph") - } + _, err := graph.SourceNode() + require.ErrorIs(t, err, ErrSourceNodeNotSet) // Set the source the source node, this should insert the node into the // database in a special way indicating it's the source node. @@ -287,23 +381,19 @@ func TestSourceNode(t *testing.T) { // the one we set above. sourceNode, err := graph.SourceNode() require.NoError(t, err, "unable to fetch source node") - if err := compareNodes(testNode, sourceNode); err != nil { - t.Fatalf("nodes don't match: %v", err) - } + compareNodes(t, testNode, sourceNode) } +// TestEdgeInsertionDeletion tests the basic CRUD operations for channel edges. func TestEdgeInsertionDeletion(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) // We'd like to test the insertion/deletion of edges, so we create two // vertexes to connect. - node1, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") - node2, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node1 := createTestVertex(t) + node2 := createTestVertex(t) // In addition to the fake vertexes we create some fake channel // identifiers. @@ -321,7 +411,7 @@ func TestEdgeInsertionDeletion(t *testing.T) { require.NoError(t, err, "unable to generate node key") edgeInfo := models.ChannelEdgeInfo{ ChannelID: chanID, - ChainHash: key, + ChainHash: *chaincfg.MainNetParams.GenesisHash, AuthProof: &models.ChannelAuthProof{ NodeSig1Bytes: testSig.Serialize(), NodeSig2Bytes: testSig.Serialize(), @@ -336,47 +426,38 @@ func TestEdgeInsertionDeletion(t *testing.T) { copy(edgeInfo.BitcoinKey1Bytes[:], node1Pub.SerializeCompressed()) copy(edgeInfo.BitcoinKey2Bytes[:], node2Pub.SerializeCompressed()) - if err := graph.AddChannelEdge(&edgeInfo); err != nil { - t.Fatalf("unable to create channel edge: %v", err) - } + require.NoError(t, graph.AddChannelEdge(&edgeInfo)) assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo) + // Show that trying to insert the same channel again will return the + // expected error. + err = graph.AddChannelEdge(&edgeInfo) + require.ErrorIs(t, err, ErrEdgeAlreadyExist) + // Ensure that both policies are returned as unknown (nil). _, e1, e2, err := graph.FetchChannelEdgesByID(chanID) - if err != nil { - t.Fatalf("unable to fetch channel edge") - } - if e1 != nil || e2 != nil { - t.Fatalf("channel edges not unknown") - } + require.NoError(t, err) + require.Nil(t, e1) + require.Nil(t, e2) // Next, attempt to delete the edge from the database, again this // should proceed without any issues. - if err := graph.DeleteChannelEdges(false, true, chanID); err != nil { - t.Fatalf("unable to delete edge: %v", err) - } + require.NoError(t, graph.DeleteChannelEdges(false, true, chanID)) assertNoEdge(t, graph, chanID) // Ensure that any query attempts to lookup the delete channel edge are // properly deleted. _, _, _, err = graph.FetchChannelEdgesByOutpoint(&outpoint) - if err == nil { - t.Fatalf("channel edge not deleted") - } - if _, _, _, err := graph.FetchChannelEdgesByID(chanID); err == nil { - t.Fatalf("channel edge not deleted") - } + require.ErrorIs(t, err, ErrEdgeNotFound) + _, _, _, err = graph.FetchChannelEdgesByID(chanID) + require.ErrorIs(t, err, ErrZombieEdge) isZombie, _, _ := graph.IsZombieEdge(chanID) - if !isZombie { - t.Fatal("channel edge not marked as zombie") - } + require.True(t, isZombie) // Finally, attempt to delete a (now) non-existent edge within the // database, this should result in an error. err = graph.DeleteChannelEdges(false, true, chanID) - if err != ErrEdgeNotFound { - t.Fatalf("deleting a non-existent edge should fail!") - } + require.ErrorIs(t, err, ErrEdgeNotFound) } func createEdge(height, txIndex uint32, txPosition uint16, outPointIndex uint32, @@ -393,19 +474,27 @@ func createEdge(height, txIndex uint32, txPosition uint16, outPointIndex uint32, Index: outPointIndex, } + var ( + features = lnwire.NewRawFeatureVector() + featureBuf bytes.Buffer + ) + _ = features.Encode(&featureBuf) + node1Pub, _ := node1.PubKey() node2Pub, _ := node2.PubKey() edgeInfo := models.ChannelEdgeInfo{ ChannelID: shortChanID.ToUint64(), - ChainHash: key, + ChainHash: *chaincfg.MainNetParams.GenesisHash, AuthProof: &models.ChannelAuthProof{ NodeSig1Bytes: testSig.Serialize(), NodeSig2Bytes: testSig.Serialize(), BitcoinSig1Bytes: testSig.Serialize(), BitcoinSig2Bytes: testSig.Serialize(), }, - ChannelPoint: outpoint, - Capacity: 9000, + ChannelPoint: outpoint, + Capacity: 9000, + ExtraOpaqueData: make([]byte, 0), + Features: featureBuf.Bytes(), } copy(edgeInfo.NodeKey1Bytes[:], node1Pub.SerializeCompressed()) @@ -421,21 +510,17 @@ func createEdge(height, txIndex uint32, txPosition uint16, outPointIndex uint32, func TestDisconnectBlockAtHeight(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) - sourceNode, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create source node") + sourceNode := createTestVertex(t) if err := graph.SetSourceNode(sourceNode); err != nil { t.Fatalf("unable to set source node: %v", err) } // We'd like to test the insertion/deletion of edges, so we create two // vertexes to connect. - node1, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") - node2, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node1 := createTestVertex(t) + node2 := createTestVertex(t) // In addition to the fake vertexes we create some fake channel // identifiers. @@ -445,7 +530,7 @@ func TestDisconnectBlockAtHeight(t *testing.T) { // Prune the graph a few times to make sure we have entries in the // prune log. - _, err = graph.PruneGraph(spendOutputs, &blockHash, 155) + _, err := graph.PruneGraph(spendOutputs, &blockHash, 155) require.NoError(t, err, "unable to prune graph") var blockHash2 chainhash.Hash copy(blockHash2[:], bytes.Repeat([]byte{2}, 32)) @@ -607,9 +692,28 @@ func assertEdgeInfoEqual(t *testing.T, e1 *models.ChannelEdgeInfo, } } -func createChannelEdge(db kvdb.Backend, node1, node2 *models.LightningNode) ( - *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, - *models.ChannelEdgePolicy) { +type createEdgeConfig struct { + skipProofs bool +} + +type createEdgeOpt func(*createEdgeConfig) + +// withSkipProofs will let createChannelEdge create an edge without auth +// proofs. In this case, createChannelEdge will then also not create policies. +func withSkipProofs() createEdgeOpt { + return func(cfg *createEdgeConfig) { + cfg.skipProofs = true + } +} + +func createChannelEdge(node1, node2 *models.LightningNode, + options ...createEdgeOpt) (*models.ChannelEdgeInfo, + *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) { + + var opts createEdgeConfig + for _, o := range options { + o(&opts) + } var ( firstNode [33]byte @@ -628,29 +732,45 @@ func createChannelEdge(db kvdb.Backend, node1, node2 *models.LightningNode) ( chanID := uint64(prand.Int63()) outpoint := wire.OutPoint{ Hash: rev, - Index: 9, + Index: prand.Uint32(), } + var ( + features = lnwire.NewRawFeatureVector() + featureBuf bytes.Buffer + ) + _ = features.Encode(&featureBuf) + // Add the new edge to the database, this should proceed without any // errors. edgeInfo := &models.ChannelEdgeInfo{ - ChannelID: chanID, - ChainHash: key, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), + ChannelID: chanID, + ChainHash: *chaincfg.MainNetParams.GenesisHash, + ChannelPoint: outpoint, + Capacity: 1000, + ExtraOpaqueData: []byte{ + 1, 1, 1, + 2, 2, 2, 2, + 3, 3, 3, 3, 3, }, - ChannelPoint: outpoint, - Capacity: 1000, - ExtraOpaqueData: []byte("new unknown feature"), + Features: featureBuf.Bytes(), } copy(edgeInfo.NodeKey1Bytes[:], firstNode[:]) copy(edgeInfo.NodeKey2Bytes[:], secondNode[:]) copy(edgeInfo.BitcoinKey1Bytes[:], firstNode[:]) copy(edgeInfo.BitcoinKey2Bytes[:], secondNode[:]) + if opts.skipProofs { + return edgeInfo, nil, nil + } + + edgeInfo.AuthProof = &models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), + } + edge1 := &models.ChannelEdgePolicy{ SigBytes: testSig.Serialize(), ChannelID: chanID, @@ -686,32 +806,28 @@ func createChannelEdge(db kvdb.Backend, node1, node2 *models.LightningNode) ( func TestEdgeInfoUpdates(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) // We'd like to test the update of edges inserted into the database, so // we create two vertexes to connect. - node1, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node1 := createTestVertex(t) if err := graph.AddLightningNode(node1); err != nil { t.Fatalf("unable to add node: %v", err) } assertNodeInCache(t, graph, node1, testFeatures) - node2, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node2 := createTestVertex(t) if err := graph.AddLightningNode(node2); err != nil { t.Fatalf("unable to add node: %v", err) } assertNodeInCache(t, graph, node2, testFeatures) // Create an edge and add it to the db. - edgeInfo, edge1, edge2 := createChannelEdge(graph.db, node1, node2) + edgeInfo, edge1, edge2 := createChannelEdge(node1, node2) // Make sure inserting the policy at this point, before the edge info // is added, will fail. - if err := graph.UpdateEdgePolicy(edge1); err != ErrEdgeNotFound { - t.Fatalf("expected ErrEdgeNotFound, got: %v", err) - } + err := graph.UpdateEdgePolicy(edge1) + require.ErrorIs(t, err, ErrEdgeNotFound) require.Len(t, graph.graphCache.nodeChannels, 0) // Add the edge info. @@ -966,10 +1082,10 @@ func assertEdgeWithPolicyInCache(t *testing.T, g *ChannelGraph, } } -func randEdgePolicy(chanID uint64, db kvdb.Backend) *models.ChannelEdgePolicy { +func randEdgePolicy(chanID uint64) *models.ChannelEdgePolicy { update := prand.Int63() - return newEdgePolicy(chanID, db, update) + return newEdgePolicy(chanID, update) } func copyEdgePolicy(p *models.ChannelEdgePolicy) *models.ChannelEdgePolicy { @@ -989,9 +1105,7 @@ func copyEdgePolicy(p *models.ChannelEdgePolicy) *models.ChannelEdgePolicy { } } -func newEdgePolicy(chanID uint64, db kvdb.Backend, - updateTime int64) *models.ChannelEdgePolicy { - +func newEdgePolicy(chanID uint64, updateTime int64) *models.ChannelEdgePolicy { return &models.ChannelEdgePolicy{ ChannelID: chanID, LastUpdate: time.Unix(updateTime, 0), @@ -1005,11 +1119,157 @@ func newEdgePolicy(chanID uint64, db kvdb.Backend, } } +// TestAddEdgeProof tests the ability to add an edge proof to an existing edge. +func TestAddEdgeProof(t *testing.T) { + t.Parallel() + + graph := MakeTestGraph(t) + + // Add an edge with no proof. + node1 := createTestVertex(t) + node2 := createTestVertex(t) + edge1, _, _ := createChannelEdge(node1, node2, withSkipProofs()) + require.NoError(t, graph.AddChannelEdge(edge1)) + + // Fetch the edge and assert that the proof is nil and that the rest + // of the edge info is correct. + dbEdge, _, _, err := graph.FetchChannelEdgesByID(edge1.ChannelID) + require.NoError(t, err) + require.Nil(t, dbEdge.AuthProof) + require.Equal(t, edge1, dbEdge) + + // Now, add the edge proof. + proof := &models.ChannelAuthProof{ + NodeSig1Bytes: testSig.Serialize(), + NodeSig2Bytes: testSig.Serialize(), + BitcoinSig1Bytes: testSig.Serialize(), + BitcoinSig2Bytes: testSig.Serialize(), + } + + // First, add the proof to the rest of the channel edge info and try + // to call AddChannelEdge again - this should fail due to the channel + // already existing. + edge1.AuthProof = proof + err = graph.AddChannelEdge(edge1) + require.Error(t, err, ErrEdgeAlreadyExist) + + // Now add just the proof. + scid1 := lnwire.NewShortChanIDFromInt(edge1.ChannelID) + require.NoError(t, graph.AddEdgeProof(scid1, proof)) + + // Fetch the edge again and assert that the proof is now set. + dbEdge, _, _, err = graph.FetchChannelEdgesByID(edge1.ChannelID) + require.NoError(t, err) + require.NotNil(t, dbEdge.AuthProof) + require.Equal(t, edge1, dbEdge) + + // For completeness, also test the case where we insert a new edge with + // an edge proof. Show that the proof is present from the get go. + edge2, _, _ := createChannelEdge(node1, node2) + require.NoError(t, graph.AddChannelEdge(edge2)) + + // Fetch the edge and assert that the proof is nil and that the rest + // of the edge info is correct. + dbEdge2, _, _, err := graph.FetchChannelEdgesByID(edge2.ChannelID) + require.NoError(t, err) + require.NotNil(t, dbEdge2.AuthProof) + require.Equal(t, edge2, dbEdge2) +} + +// TestForEachSourceNodeChannel tests that the ForEachSourceNodeChannel +// correctly iterates through the channels of the set source node. +func TestForEachSourceNodeChannel(t *testing.T) { + t.Parallel() + + graph := MakeTestGraph(t) + + // Create a source node (A) and set it as such in the DB. + nodeA := createTestVertex(t) + require.NoError(t, graph.SetSourceNode(nodeA)) + + // Now, create a few more nodes (B, C, D) along with some channels + // between them. We'll create the following graph: + // + // A -- B -- D + // | + // C + // + // The graph includes a channel (B-D) that does not belong to the source + // node along with 2 channels (A-B and A-C) that do belong to the source + // node. For the A-B channel, we will let the source node set an + // outgoing policy but for the A-C channel, we will set only an incoming + // policy. + + nodeB := createTestVertex(t) + nodeC := createTestVertex(t) + nodeD := createTestVertex(t) + + abEdge, abPolicy1, abPolicy2 := createChannelEdge(nodeA, nodeB) + require.NoError(t, graph.AddChannelEdge(abEdge)) + acEdge, acPolicy1, acPolicy2 := createChannelEdge(nodeA, nodeC) + require.NoError(t, graph.AddChannelEdge(acEdge)) + bdEdge, _, _ := createChannelEdge(nodeB, nodeD) + require.NoError(t, graph.AddChannelEdge(bdEdge)) + + // Figure out which of the policies returned above are node A's so that + // we know which to persist. + // + // First, set the outgoing policy for the A-B channel. + abPolicyAOutgoing := abPolicy1 + if !bytes.Equal(abPolicy1.ToNode[:], nodeB.PubKeyBytes[:]) { + abPolicyAOutgoing = abPolicy2 + } + require.NoError(t, graph.UpdateEdgePolicy(abPolicyAOutgoing)) + + // Now, set the incoming policy for the A-C channel. + acPolicyAIncoming := acPolicy1 + if !bytes.Equal(acPolicy1.ToNode[:], nodeA.PubKeyBytes[:]) { + acPolicyAIncoming = acPolicy2 + } + require.NoError(t, graph.UpdateEdgePolicy(acPolicyAIncoming)) + + type sourceNodeChan struct { + otherNode route.Vertex + havePolicy bool + } + + // Put together our expected source node channels. + expectedSrcChans := map[wire.OutPoint]*sourceNodeChan{ + abEdge.ChannelPoint: { + otherNode: nodeB.PubKeyBytes, + havePolicy: true, + }, + acEdge.ChannelPoint: { + otherNode: nodeC.PubKeyBytes, + havePolicy: false, + }, + } + + // Now, we'll use the ForEachSourceNodeChannel and assert that it + // returns the expected data in the call-back. + err := graph.ForEachSourceNodeChannel(func(chanPoint wire.OutPoint, + havePolicy bool, otherNode *models.LightningNode) error { + + require.Contains(t, expectedSrcChans, chanPoint) + expected := expectedSrcChans[chanPoint] + + require.Equal( + t, expected.otherNode[:], otherNode.PubKeyBytes[:], + ) + require.Equal(t, expected.havePolicy, havePolicy) + + delete(expectedSrcChans, chanPoint) + + return nil + }) + require.NoError(t, err) + require.Empty(t, expectedSrcChans) +} + func TestGraphTraversal(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) // We'd like to test some of the graph traversal capabilities within // the DB, so we'll create a series of fake nodes to insert into the @@ -1028,7 +1288,7 @@ func TestGraphTraversal(t *testing.T) { // set of channels (to force the fall back), we should find all the // channel as well as the nodes included. graph.graphCache = nil - err = graph.ForEachNodeCached(func(node route.Vertex, + err := graph.ForEachNodeCached(func(node route.Vertex, chans map[uint64]*DirectedChannel) error { if _, ok := nodeIndex[node]; !ok { @@ -1064,7 +1324,7 @@ func TestGraphTraversal(t *testing.T) { numNodeChans := 0 firstNode, secondNode := nodeList[0], nodeList[1] err = graph.ForEachNodeChannel(firstNode.PubKeyBytes, - func(_ kvdb.RTx, _ *models.ChannelEdgeInfo, outEdge, + func(_ *models.ChannelEdgeInfo, outEdge, inEdge *models.ChannelEdgePolicy) error { // All channels between first and second node should @@ -1104,8 +1364,7 @@ func TestGraphTraversal(t *testing.T) { func TestGraphTraversalCacheable(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) // We'd like to test some of the graph traversal capabilities within // the DB, so we'll create a series of fake nodes to insert into the @@ -1117,13 +1376,11 @@ func TestGraphTraversalCacheable(t *testing.T) { // Create a map of all nodes with the iteration we know works (because // it is tested in another test). nodeMap := make(map[route.Vertex]struct{}) - err = graph.forEachNode( - func(tx kvdb.RTx, n *models.LightningNode) error { - nodeMap[n.PubKeyBytes] = struct{}{} + err := graph.ForEachNode(func(tx NodeRTx) error { + nodeMap[tx.Node().PubKeyBytes] = struct{}{} - return nil - }, - ) + return nil + }) require.NoError(t, err) require.Len(t, nodeMap, numNodes) @@ -1142,34 +1399,42 @@ func TestGraphTraversalCacheable(t *testing.T) { require.NoError(t, err) require.Len(t, nodeMap, 0) - err = graph.db.View(func(tx kvdb.RTx) error { - for _, node := range nodes { - err := graph.ForEachNodeChannelTx(tx, node, - func(tx kvdb.RTx, info *models.ChannelEdgeInfo, - policy *models.ChannelEdgePolicy, - policy2 *models.ChannelEdgePolicy) error { //nolint:ll + // Duplicate the map before we start deleting from it so that we can + // check that both the cached and db version of + // ForEachNodeDirectedChannel works as expected here. + chanIndex2 := make(map[uint64]struct{}) + for k, v := range chanIndex { + chanIndex2[k] = v + } - delete(chanIndex, info.ChannelID) - return nil - }, - ) - if err != nil { - return err - } - } + for _, node := range nodes { + // Query the ChannelGraph which uses the cache to iterate + // through the channels for each node. + err = graph.ForEachNodeDirectedChannel( + node, func(d *DirectedChannel) error { + delete(chanIndex, d.ChannelID) + return nil + }, + ) + require.NoError(t, err) - return nil - }, func() {}) - - require.NoError(t, err) + // Now skip the cache and query the DB directly. + err = graph.V1Store.ForEachNodeDirectedChannel( + node, func(d *DirectedChannel) error { + delete(chanIndex2, d.ChannelID) + return nil + }, + ) + require.NoError(t, err) + } require.Len(t, chanIndex, 0) + require.Len(t, chanIndex2, 0) } func TestGraphCacheTraversal(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err) + graph := MakeTestGraph(t) // We'd like to test some of the graph traversal capabilities within // the DB, so we'll create a series of fake nodes to insert into the @@ -1185,7 +1450,7 @@ func TestGraphCacheTraversal(t *testing.T) { for _, node := range nodeList { node := node - err = graph.graphCache.ForEachChannel( + err := graph.graphCache.ForEachChannel( node.PubKeyBytes, func(d *DirectedChannel) error { delete(chanIndex, d.ChannelID) @@ -1219,14 +1484,13 @@ func TestGraphCacheTraversal(t *testing.T) { require.Equal(t, numChannels*2*(numNodes-1), numNodeChans) } -func fillTestGraph(t require.TestingT, graph *ChannelGraph, numNodes, +func fillTestGraph(t testing.TB, graph *ChannelGraph, numNodes, numChannels int) (map[uint64]struct{}, []*models.LightningNode) { nodes := make([]*models.LightningNode, numNodes) nodeIndex := map[string]struct{}{} for i := 0; i < numNodes; i++ { - node, err := createTestVertex(graph.db) - require.NoError(t, err) + node := createTestVertex(t) nodes[i] = node nodeIndex[node.Alias] = struct{}{} @@ -1240,12 +1504,10 @@ func fillTestGraph(t require.TestingT, graph *ChannelGraph, numNodes, // Iterate over each node as returned by the graph, if all nodes are // reached, then the map created above should be empty. - err := graph.forEachNode( - func(_ kvdb.RTx, node *models.LightningNode) error { - delete(nodeIndex, node.Alias) - return nil - }, - ) + err := graph.ForEachNode(func(tx NodeRTx) error { + delete(nodeIndex, tx.Node().Alias) + return nil + }) require.NoError(t, err) require.Len(t, nodeIndex, 0) @@ -1271,7 +1533,7 @@ func fillTestGraph(t require.TestingT, graph *ChannelGraph, numNodes, edgeInfo := models.ChannelEdgeInfo{ ChannelID: chanID, - ChainHash: key, + ChainHash: *chaincfg.MainNetParams.GenesisHash, AuthProof: &models.ChannelAuthProof{ NodeSig1Bytes: testSig.Serialize(), NodeSig2Bytes: testSig.Serialize(), @@ -1290,7 +1552,7 @@ func fillTestGraph(t require.TestingT, graph *ChannelGraph, numNodes, // Create and add an edge with random data that points // from node1 -> node2. - edge := randEdgePolicy(chanID, graph.db) + edge := randEdgePolicy(chanID) edge.ChannelFlags = 0 edge.ToNode = node2.PubKeyBytes edge.SigBytes = testSig.Serialize() @@ -1298,7 +1560,7 @@ func fillTestGraph(t require.TestingT, graph *ChannelGraph, numNodes, // Create another random edge that points from // node2 -> node1 this time. - edge = randEdgePolicy(chanID, graph.db) + edge = randEdgePolicy(chanID) edge.ChannelFlags = 1 edge.ToNode = node1.PubKeyBytes edge.SigBytes = testSig.Serialize() @@ -1352,12 +1614,11 @@ func assertNumChans(t *testing.T, graph *ChannelGraph, n int) { func assertNumNodes(t *testing.T, graph *ChannelGraph, n int) { numNodes := 0 - err := graph.forEachNode( - func(_ kvdb.RTx, _ *models.LightningNode) error { - numNodes++ - return nil - }, - ) + err := graph.ForEachNode(func(tx NodeRTx) error { + numNodes++ + + return nil + }) if err != nil { _, _, line, _ := runtime.Caller(1) t.Fatalf("line %v: unable to scan nodes: %v", line, err) @@ -1415,11 +1676,9 @@ func assertChanViewEqualChanPoints(t *testing.T, a []EdgePoint, func TestGraphPruning(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) - sourceNode, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create source node") + sourceNode := createTestVertex(t) if err := graph.SetSourceNode(sourceNode); err != nil { t.Fatalf("unable to set source node: %v", err) } @@ -1430,10 +1689,7 @@ func TestGraphPruning(t *testing.T) { const numNodes = 5 graphNodes := make([]*models.LightningNode, numNodes) for i := 0; i < numNodes; i++ { - node, err := createTestVertex(graph.db) - if err != nil { - t.Fatalf("unable to create node: %v", err) - } + node := createTestVertex(t) if err := graph.AddLightningNode(node); err != nil { t.Fatalf("unable to add node: %v", err) @@ -1458,7 +1714,7 @@ func TestGraphPruning(t *testing.T) { edgeInfo := models.ChannelEdgeInfo{ ChannelID: chanID, - ChainHash: key, + ChainHash: *chaincfg.MainNetParams.GenesisHash, AuthProof: &models.ChannelAuthProof{ NodeSig1Bytes: testSig.Serialize(), NodeSig2Bytes: testSig.Serialize(), @@ -1493,7 +1749,7 @@ func TestGraphPruning(t *testing.T) { // Create and add an edge with random data that points from // node_i -> node_i+1 - edge := randEdgePolicy(chanID, graph.db) + edge := randEdgePolicy(chanID) edge.ChannelFlags = 0 edge.ToNode = graphNodes[i].PubKeyBytes edge.SigBytes = testSig.Serialize() @@ -1503,7 +1759,7 @@ func TestGraphPruning(t *testing.T) { // Create another random edge that points from node_i+1 -> // node_i this time. - edge = randEdgePolicy(chanID, graph.db) + edge = randEdgePolicy(chanID) edge.ChannelFlags = 1 edge.ToNode = graphNodes[i].PubKeyBytes edge.SigBytes = testSig.Serialize() @@ -1609,8 +1865,7 @@ func TestGraphPruning(t *testing.T) { func TestHighestChanID(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) // If we don't yet have any channels in the database, then we should // get a channel ID of zero if we ask for the highest channel ID. @@ -1623,10 +1878,8 @@ func TestHighestChanID(t *testing.T) { // Next, we'll insert two channels into the database, with each channel // connecting the same two nodes. - node1, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") - node2, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node1 := createTestVertex(t) + node2 := createTestVertex(t) // The first channel with be at height 10, while the other will be at // height 100. @@ -1671,8 +1924,7 @@ func TestHighestChanID(t *testing.T) { func TestChanUpdatesInHorizon(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) // If we issue an arbitrary query before any channel updates are // inserted in the database, we should get zero results. @@ -1686,13 +1938,11 @@ func TestChanUpdatesInHorizon(t *testing.T) { } // We'll start by creating two nodes which will seed our test graph. - node1, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node1 := createTestVertex(t) if err := graph.AddLightningNode(node1); err != nil { t.Fatalf("unable to add node: %v", err) } - node2, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node2 := createTestVertex(t) if err := graph.AddLightningNode(node2); err != nil { t.Fatalf("unable to add node: %v", err) } @@ -1717,7 +1967,7 @@ func TestChanUpdatesInHorizon(t *testing.T) { endTime = endTime.Add(time.Second * 10) edge1 := newEdgePolicy( - chanID.ToUint64(), graph.db, edge1UpdateTime.Unix(), + chanID.ToUint64(), edge1UpdateTime.Unix(), ) edge1.ChannelFlags = 0 edge1.ToNode = node2.PubKeyBytes @@ -1727,7 +1977,7 @@ func TestChanUpdatesInHorizon(t *testing.T) { } edge2 := newEdgePolicy( - chanID.ToUint64(), graph.db, edge2UpdateTime.Unix(), + chanID.ToUint64(), edge2UpdateTime.Unix(), ) edge2.ChannelFlags = 1 edge2.ToNode = node1.PubKeyBytes @@ -1832,8 +2082,7 @@ func TestChanUpdatesInHorizon(t *testing.T) { func TestNodeUpdatesInHorizon(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) startTime := time.Unix(1234, 0) endTime := startTime @@ -1854,10 +2103,7 @@ func TestNodeUpdatesInHorizon(t *testing.T) { const numNodes = 10 nodeAnns := make([]models.LightningNode, 0, numNodes) for i := 0; i < numNodes; i++ { - nodeAnn, err := createTestVertex(graph.db) - if err != nil { - t.Fatalf("unable to create test vertex: %v", err) - } + nodeAnn := createTestVertex(t) // The node ann will use the current end time as its last // update them, then we'll add 10 seconds in order to create @@ -1936,10 +2182,7 @@ func TestNodeUpdatesInHorizon(t *testing.T) { } for i := 0; i < len(resp); i++ { - err := compareNodes(&queryCase.resp[i], &resp[i]) - if err != nil { - t.Fatal(err) - } + compareNodes(t, &queryCase.resp[i], &resp[i]) } } } @@ -1954,8 +2197,7 @@ func TestNodeUpdatesInHorizon(t *testing.T) { func TestFilterKnownChanIDsZombieRevival(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err) + graph := MakeTestGraph(t) var ( scid1 = lnwire.ShortChannelID{BlockHeight: 1} @@ -1969,7 +2211,7 @@ func TestFilterKnownChanIDsZombieRevival(t *testing.T) { } // Mark channel 1 and 2 as zombies. - err = graph.MarkEdgeZombie(scid1.ToUint64(), [33]byte{}, [33]byte{}) + err := graph.MarkEdgeZombie(scid1.ToUint64(), [33]byte{}, [33]byte{}) require.NoError(t, err) err = graph.MarkEdgeZombie(scid2.ToUint64(), [33]byte{}, [33]byte{}) require.NoError(t, err) @@ -2020,8 +2262,7 @@ func TestFilterKnownChanIDsZombieRevival(t *testing.T) { func TestFilterKnownChanIDs(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) isZombieUpdate := func(updateTime1 time.Time, updateTime2 time.Time) bool { @@ -2051,13 +2292,11 @@ func TestFilterKnownChanIDs(t *testing.T) { }, filteredIDs) // We'll start by creating two nodes which will seed our test graph. - node1, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node1 := createTestVertex(t) if err := graph.AddLightningNode(node1); err != nil { t.Fatalf("unable to add node: %v", err) } - node2, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node2 := createTestVertex(t) if err := graph.AddLightningNode(node2); err != nil { t.Fatalf("unable to add node: %v", err) } @@ -2200,19 +2439,15 @@ func TestFilterKnownChanIDs(t *testing.T) { func TestStressTestChannelGraphAPI(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err) + graph := MakeTestGraph(t) - node1, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node1 := createTestVertex(t) require.NoError(t, graph.AddLightningNode(node1)) - node2, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node2 := createTestVertex(t) require.NoError(t, graph.AddLightningNode(node2)) - err = graph.SetSourceNode(node1) - require.NoError(t, err) + require.NoError(t, graph.SetSourceNode(node1)) type chanInfo struct { info models.ChannelEdgeInfo @@ -2488,17 +2723,14 @@ func TestStressTestChannelGraphAPI(t *testing.T) { func TestFilterChannelRange(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err) + graph := MakeTestGraph(t) // We'll first populate our graph with two nodes. All channels created // below will be made between these two nodes. - node1, err := createTestVertex(graph.db) - require.NoError(t, err) + node1 := createTestVertex(t) require.NoError(t, graph.AddLightningNode(node1)) - node2, err := createTestVertex(graph.db) - require.NoError(t, err) + node2 := createTestVertex(t) require.NoError(t, graph.AddLightningNode(node2)) // If we try to filter a channel range before we have any channels @@ -2707,18 +2939,15 @@ func TestFilterChannelRange(t *testing.T) { func TestFetchChanInfos(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) // We'll first populate our graph with two nodes. All channels created // below will be made between these two nodes. - node1, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node1 := createTestVertex(t) if err := graph.AddLightningNode(node1); err != nil { t.Fatalf("unable to add node: %v", err) } - node2, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node2 := createTestVertex(t) if err := graph.AddLightningNode(node2); err != nil { t.Fatalf("unable to add node: %v", err) } @@ -2742,9 +2971,7 @@ func TestFetchChanInfos(t *testing.T) { updateTime := endTime endTime = updateTime.Add(time.Second * 10) - edge1 := newEdgePolicy( - chanID.ToUint64(), graph.db, updateTime.Unix(), - ) + edge1 := newEdgePolicy(chanID.ToUint64(), updateTime.Unix()) edge1.ChannelFlags = 0 edge1.ToNode = node2.PubKeyBytes edge1.SigBytes = testSig.Serialize() @@ -2752,9 +2979,7 @@ func TestFetchChanInfos(t *testing.T) { t.Fatalf("unable to update edge: %v", err) } - edge2 := newEdgePolicy( - chanID.ToUint64(), graph.db, updateTime.Unix(), - ) + edge2 := newEdgePolicy(chanID.ToUint64(), updateTime.Unix()) edge2.ChannelFlags = 1 edge2.ToNode = node1.PubKeyBytes edge2.SigBytes = testSig.Serialize() @@ -2783,7 +3008,7 @@ func TestFetchChanInfos(t *testing.T) { if err := graph.AddChannelEdge(&zombieChan); err != nil { t.Fatalf("unable to create channel edge: %v", err) } - err = graph.DeleteChannelEdges(false, true, zombieChan.ChannelID) + err := graph.DeleteChannelEdges(false, true, zombieChan.ChannelID) require.NoError(t, err, "unable to delete and mark edge zombie") edgeQuery = append(edgeQuery, zombieChanID.ToUint64()) @@ -2816,17 +3041,14 @@ func TestFetchChanInfos(t *testing.T) { func TestIncompleteChannelPolicies(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) // Create two nodes. - node1, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node1 := createTestVertex(t) if err := graph.AddLightningNode(node1); err != nil { t.Fatalf("unable to add node: %v", err) } - node2, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node2 := createTestVertex(t) if err := graph.AddLightningNode(node2); err != nil { t.Fatalf("unable to add node: %v", err) } @@ -2845,7 +3067,7 @@ func TestIncompleteChannelPolicies(t *testing.T) { calls := 0 err := graph.ForEachNodeChannel(node.PubKeyBytes, - func(_ kvdb.RTx, _ *models.ChannelEdgeInfo, outEdge, + func(_ *models.ChannelEdgeInfo, outEdge, inEdge *models.ChannelEdgePolicy) error { if !expectedOut && outEdge != nil { @@ -2883,9 +3105,7 @@ func TestIncompleteChannelPolicies(t *testing.T) { // unknown. updateTime := time.Unix(1234, 0) - edgePolicy := newEdgePolicy( - chanID.ToUint64(), graph.db, updateTime.Unix(), - ) + edgePolicy := newEdgePolicy(chanID.ToUint64(), updateTime.Unix()) edgePolicy.ChannelFlags = 0 edgePolicy.ToNode = node2.PubKeyBytes edgePolicy.SigBytes = testSig.Serialize() @@ -2898,9 +3118,7 @@ func TestIncompleteChannelPolicies(t *testing.T) { // Create second policy and assert that both policies are reported // as present. - edgePolicy = newEdgePolicy( - chanID.ToUint64(), graph.db, updateTime.Unix(), - ) + edgePolicy = newEdgePolicy(chanID.ToUint64(), updateTime.Unix()) edgePolicy.ChannelFlags = 1 edgePolicy.ToNode = node1.PubKeyBytes edgePolicy.SigBytes = testSig.Serialize() @@ -2918,24 +3136,26 @@ func TestIncompleteChannelPolicies(t *testing.T) { func TestChannelEdgePruningUpdateIndexDeletion(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) - sourceNode, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create source node") + // The update index only applies to the bbolt graph. + boltStore, ok := graph.V1Store.(*KVStore) + if !ok { + t.Skipf("skipping test that is aimed at a bbolt graph DB") + } + + sourceNode := createTestVertex(t) if err := graph.SetSourceNode(sourceNode); err != nil { t.Fatalf("unable to set source node: %v", err) } // We'll first populate our graph with two nodes. All channels created // below will be made between these two nodes. - node1, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node1 := createTestVertex(t) if err := graph.AddLightningNode(node1); err != nil { t.Fatalf("unable to add node: %v", err) } - node2, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node2 := createTestVertex(t) if err := graph.AddLightningNode(node2); err != nil { t.Fatalf("unable to add node: %v", err) } @@ -2947,7 +3167,7 @@ func TestChannelEdgePruningUpdateIndexDeletion(t *testing.T) { t.Fatalf("unable to add edge: %v", err) } - edge1 := randEdgePolicy(chanID.ToUint64(), graph.db) + edge1 := randEdgePolicy(chanID.ToUint64()) edge1.ChannelFlags = 0 edge1.ToNode = node1.PubKeyBytes edge1.SigBytes = testSig.Serialize() @@ -2956,7 +3176,7 @@ func TestChannelEdgePruningUpdateIndexDeletion(t *testing.T) { } edge1 = copyEdgePolicy(edge1) // Avoid read/write race conditions. - edge2 := randEdgePolicy(chanID.ToUint64(), graph.db) + edge2 := randEdgePolicy(chanID.ToUint64()) edge2.ChannelFlags = 1 edge2.ToNode = node2.PubKeyBytes edge2.SigBytes = testSig.Serialize() @@ -2973,7 +3193,7 @@ func TestChannelEdgePruningUpdateIndexDeletion(t *testing.T) { timestampSet[t] = struct{}{} } - err := kvdb.View(graph.db, func(tx kvdb.RTx) error { + err := kvdb.View(boltStore.db, func(tx kvdb.RTx) error { edges := tx.ReadBucket(edgeBucket) if edges == nil { return ErrGraphNoEdgesFound @@ -3047,7 +3267,7 @@ func TestChannelEdgePruningUpdateIndexDeletion(t *testing.T) { // index entries from the database all together. var blockHash chainhash.Hash copy(blockHash[:], bytes.Repeat([]byte{2}, 32)) - _, err = graph.PruneGraph( + _, err := graph.PruneGraph( []*wire.OutPoint{&edgeInfo.ChannelPoint}, &blockHash, 101, ) require.NoError(t, err, "unable to prune graph") @@ -3063,13 +3283,11 @@ func TestChannelEdgePruningUpdateIndexDeletion(t *testing.T) { func TestPruneGraphNodes(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) // We'll start off by inserting our source node, to ensure that it's // the only node left after we prune the graph. - sourceNode, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create source node") + sourceNode := createTestVertex(t) if err := graph.SetSourceNode(sourceNode); err != nil { t.Fatalf("unable to set source node: %v", err) } @@ -3077,18 +3295,15 @@ func TestPruneGraphNodes(t *testing.T) { // With the source node inserted, we'll now add three nodes to the // channel graph, at the end of the scenario, only two of these nodes // should still be in the graph. - node1, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node1 := createTestVertex(t) if err := graph.AddLightningNode(node1); err != nil { t.Fatalf("unable to add node: %v", err) } - node2, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node2 := createTestVertex(t) if err := graph.AddLightningNode(node2); err != nil { t.Fatalf("unable to add node: %v", err) } - node3, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node3 := createTestVertex(t) if err := graph.AddLightningNode(node3); err != nil { t.Fatalf("unable to add node: %v", err) } @@ -3102,7 +3317,7 @@ func TestPruneGraphNodes(t *testing.T) { // We'll now insert an advertised edge, but it'll only be the edge that // points from the first to the second node. - edge1 := randEdgePolicy(chanID.ToUint64(), graph.db) + edge1 := randEdgePolicy(chanID.ToUint64()) edge1.ChannelFlags = 0 edge1.ToNode = node1.PubKeyBytes edge1.SigBytes = testSig.Serialize() @@ -3123,10 +3338,8 @@ func TestPruneGraphNodes(t *testing.T) { // Finally, we'll ensure that node3, the only fully unconnected node as // properly deleted from the graph and not another node in its place. - _, err = graph.FetchLightningNode(node3.PubKeyBytes) - if err == nil { - t.Fatalf("node 3 should have been deleted!") - } + _, err := graph.FetchLightningNode(node3.PubKeyBytes) + require.NotNil(t, err) } // TestAddChannelEdgeShellNodes tests that when we attempt to add a ChannelEdge @@ -3135,18 +3348,15 @@ func TestPruneGraphNodes(t *testing.T) { func TestAddChannelEdgeShellNodes(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) // To start, we'll create two nodes, and only add one of them to the // channel graph. - node1, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node1 := createTestVertex(t) if err := graph.AddLightningNode(node1); err != nil { t.Fatalf("unable to add node: %v", err) } - node2, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node2 := createTestVertex(t) // We'll now create an edge between the two nodes, as a result, node2 // should be inserted into the database as a shell node. @@ -3157,7 +3367,7 @@ func TestAddChannelEdgeShellNodes(t *testing.T) { // Ensure that node1 was inserted as a full node, while node2 only has // a shell node present. - node1, err = graph.FetchLightningNode(node1.PubKeyBytes) + node1, err := graph.FetchLightningNode(node1.PubKeyBytes) require.NoError(t, err, "unable to fetch node1") if !node1.HaveNodeAnnouncement { t.Fatalf("have shell announcement for node1, shouldn't") @@ -3174,13 +3384,11 @@ func TestAddChannelEdgeShellNodes(t *testing.T) { func TestNodePruningUpdateIndexDeletion(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) // We'll first populate our graph with a single node that will be // removed shortly. - node1, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node1 := createTestVertex(t) if err := graph.AddLightningNode(node1); err != nil { t.Fatalf("unable to add node: %v", err) } @@ -3199,9 +3407,7 @@ func TestNodePruningUpdateIndexDeletion(t *testing.T) { t.Fatalf("should have 1 nodes instead have: %v", len(nodesInHorizon)) } - if err := compareNodes(node1, &nodesInHorizon[0]); err != nil { - t.Fatalf("nodes don't match: %v", err) - } + compareNodes(t, node1, &nodesInHorizon[0]) // We'll now delete the node from the graph, this should result in it // being removed from the update index as well. @@ -3233,26 +3439,20 @@ func TestNodeIsPublic(t *testing.T) { // We'll need to create a separate database and channel graph for each // participant to replicate real-world scenarios (private edges being in // some graphs but not others, etc.). - aliceGraph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") - aliceNode, err := createTestVertex(aliceGraph.db) - require.NoError(t, err, "unable to create test node") + aliceGraph := MakeTestGraph(t) + aliceNode := createTestVertex(t) if err := aliceGraph.SetSourceNode(aliceNode); err != nil { t.Fatalf("unable to set source node: %v", err) } - bobGraph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") - bobNode, err := createTestVertex(bobGraph.db) - require.NoError(t, err, "unable to create test node") + bobGraph := MakeTestGraph(t) + bobNode := createTestVertex(t) if err := bobGraph.SetSourceNode(bobNode); err != nil { t.Fatalf("unable to set source node: %v", err) } - carolGraph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") - carolNode, err := createTestVertex(carolGraph.db) - require.NoError(t, err, "unable to create test node") + carolGraph := MakeTestGraph(t) + carolNode := createTestVertex(t) if err := carolGraph.SetSourceNode(carolNode); err != nil { t.Fatalf("unable to set source node: %v", err) } @@ -3366,25 +3566,22 @@ func TestNodeIsPublic(t *testing.T) { func TestDisabledChannelIDs(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) // Create first node and add it to the graph. - node1, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node1 := createTestVertex(t) if err := graph.AddLightningNode(node1); err != nil { t.Fatalf("unable to add node: %v", err) } // Create second node and add it to the graph. - node2, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node2 := createTestVertex(t) if err := graph.AddLightningNode(node2); err != nil { t.Fatalf("unable to add node: %v", err) } // Adding a new channel edge to the graph. - edgeInfo, edge1, edge2 := createChannelEdge(graph.db, node1, node2) + edgeInfo, edge1, edge2 := createChannelEdge(node1, node2) if err := graph.AddLightningNode(node2); err != nil { t.Fatalf("unable to add node: %v", err) } @@ -3452,20 +3649,23 @@ func TestDisabledChannelIDs(t *testing.T) { func TestEdgePolicyMissingMaxHtcl(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") + graph := MakeTestGraph(t) + + // This test currently directly edits the bytes stored in the bbolt DB. + boltStore, ok := graph.V1Store.(*KVStore) + if !ok { + t.Skipf("skipping test that is aimed at a bbolt graph DB") + } // We'd like to test the update of edges inserted into the database, so // we create two vertexes to connect. - node1, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node1 := createTestVertex(t) if err := graph.AddLightningNode(node1); err != nil { t.Fatalf("unable to add node: %v", err) } - node2, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test node") + node2 := createTestVertex(t) - edgeInfo, edge1, edge2 := createChannelEdge(graph.db, node1, node2) + edgeInfo, edge1, edge2 := createChannelEdge(node1, node2) if err := graph.AddLightningNode(node2); err != nil { t.Fatalf("unable to add node: %v", err) } @@ -3483,20 +3683,14 @@ func TestEdgePolicyMissingMaxHtcl(t *testing.T) { edge1.ExtraOpaqueData = nil var b bytes.Buffer - err = serializeChanEdgePolicy(&b, edge1, to) - if err != nil { - t.Fatalf("unable to serialize policy") - } + require.NoError(t, serializeChanEdgePolicy(&b, edge1, to)) // Set the max_htlc field. The extra bytes added to the serialization // will be the opaque data containing the serialized field. edge1.MessageFlags = lnwire.ChanUpdateRequiredMaxHtlc edge1.MaxHTLC = 13928598 var b2 bytes.Buffer - err = serializeChanEdgePolicy(&b2, edge1, to) - if err != nil { - t.Fatalf("unable to serialize policy") - } + require.NoError(t, serializeChanEdgePolicy(&b2, edge1, to)) withMaxHtlc := b2.Bytes() @@ -3505,25 +3699,11 @@ func TestEdgePolicyMissingMaxHtcl(t *testing.T) { // Attempting to deserialize these bytes should return an error. r := bytes.NewReader(stripped) - err = kvdb.View(graph.db, func(tx kvdb.RTx) error { - nodes := tx.ReadBucket(nodeBucket) - if nodes == nil { - return ErrGraphNotFound - } - - _, err = deserializeChanEdgePolicy(r) - if err != ErrEdgePolicyOptionalFieldNotFound { - t.Fatalf("expected "+ - "ErrEdgePolicyOptionalFieldNotFound, got %v", - err) - } - - return nil - }, func() {}) - require.NoError(t, err, "error reading db") + _, err := deserializeChanEdgePolicy(r) + require.ErrorIs(t, err, ErrEdgePolicyOptionalFieldNotFound) // Put the stripped bytes in the DB. - err = kvdb.Update(graph.db, func(tx kvdb.RwTx) error { + err = kvdb.Update(boltStore.db, func(tx kvdb.RwTx) error { edges := tx.ReadWriteBucket(edgeBucket) if edges == nil { return ErrEdgeNotFound @@ -3615,13 +3795,10 @@ func TestGraphZombieIndex(t *testing.T) { t.Parallel() // We'll start by creating our test graph along with a test edge. - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to create test database") + graph := MakeTestGraph(t) - node1, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test vertex") - node2, err := createTestVertex(graph.db) - require.NoError(t, err, "unable to create test vertex") + node1 := createTestVertex(t) + node2 := createTestVertex(t) // Swap the nodes if the second's pubkey is smaller than the first. // Without this, the comparisons at the end will fail probabilistically. @@ -3629,7 +3806,7 @@ func TestGraphZombieIndex(t *testing.T) { node1, node2 = node2, node1 } - edge, _, _ := createChannelEdge(graph.db, node1, node2) + edge, _, _ := createChannelEdge(node1, node2) require.NoError(t, graph.AddChannelEdge(edge)) // Since the edge is known the graph and it isn't a zombie, IsZombieEdge @@ -3640,7 +3817,7 @@ func TestGraphZombieIndex(t *testing.T) { // If we delete the edge and mark it as a zombie, then we should expect // to see it within the index. - err = graph.DeleteChannelEdges(false, true, edge.ChannelID) + err := graph.DeleteChannelEdges(false, true, edge.ChannelID) require.NoError(t, err, "unable to mark edge as zombie") isZombie, pubKey1, pubKey2 := graph.IsZombieEdge(edge.ChannelID) require.True(t, isZombie) @@ -3675,41 +3852,19 @@ func TestGraphZombieIndex(t *testing.T) { assertNumZombies(t, graph, 1) } -// compareNodes is used to compare two LightningNodes while excluding the -// Features struct, which cannot be compared as the semantics for reserializing -// the featuresMap have not been defined. -func compareNodes(a, b *models.LightningNode) error { - if a.LastUpdate != b.LastUpdate { - return fmt.Errorf("node LastUpdate doesn't match: expected "+ - "%v, got %v", a.LastUpdate, b.LastUpdate) - } - if !reflect.DeepEqual(a.Addresses, b.Addresses) { - return fmt.Errorf("Addresses doesn't match: expected %#v, \n "+ - "got %#v", a.Addresses, b.Addresses) - } - if !reflect.DeepEqual(a.PubKeyBytes, b.PubKeyBytes) { - return fmt.Errorf("PubKey doesn't match: expected %#v, \n "+ - "got %#v", a.PubKeyBytes, b.PubKeyBytes) - } - if !reflect.DeepEqual(a.Color, b.Color) { - return fmt.Errorf("Color doesn't match: expected %#v, \n "+ - "got %#v", a.Color, b.Color) - } - if !reflect.DeepEqual(a.Alias, b.Alias) { - return fmt.Errorf("Alias doesn't match: expected %#v, \n "+ - "got %#v", a.Alias, b.Alias) - } - if !reflect.DeepEqual(a.HaveNodeAnnouncement, b.HaveNodeAnnouncement) { - return fmt.Errorf("HaveNodeAnnouncement doesn't match: "+ - "expected %#v, got %#v", a.HaveNodeAnnouncement, - b.HaveNodeAnnouncement) - } - if !bytes.Equal(a.ExtraOpaqueData, b.ExtraOpaqueData) { - return fmt.Errorf("extra data doesn't match: %v vs %v", - a.ExtraOpaqueData, b.ExtraOpaqueData) - } +// compareNodes is used to compare two LightningNodes. +func compareNodes(t *testing.T, a, b *models.LightningNode) { + t.Helper() - return nil + // Call the PubKey method for each node to ensure that the internal + // `pubKey` field is set for both objects and so require.Equals can + // then be used to compare the structs. + _, err := a.PubKey() + require.NoError(t, err) + _, err = b.PubKey() + require.NoError(t, err) + + require.Equal(t, a, b) } // compareEdgePolicies is used to compare two ChannelEdgePolices using @@ -3787,11 +3942,7 @@ func TestLightningNodeSigVerification(t *testing.T) { } // Create a LightningNode from the same private key. - graph, err := MakeTestGraph(t) - require.NoError(t, err, "unable to make test database") - - node, err := createLightningNode(graph.db, priv) - require.NoError(t, err, "unable to create node") + node := createLightningNode(priv) // And finally check that we can verify the same signature from the // pubkey returned from the lightning node. @@ -3825,20 +3976,15 @@ func TestComputeFee(t *testing.T) { func TestBatchedAddChannelEdge(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.Nil(t, err) + graph := MakeTestGraph(t) - sourceNode, err := createTestVertex(graph.db) - require.Nil(t, err) - err = graph.SetSourceNode(sourceNode) - require.Nil(t, err) + sourceNode := createTestVertex(t) + require.Nil(t, graph.SetSourceNode(sourceNode)) // We'd like to test the insertion/deletion of edges, so we create two // vertexes to connect. - node1, err := createTestVertex(graph.db) - require.Nil(t, err) - node2, err := createTestVertex(graph.db) - require.Nil(t, err) + node1 := createTestVertex(t) + node2 := createTestVertex(t) // In addition to the fake vertexes we create some fake channel // identifiers. @@ -3848,7 +3994,7 @@ func TestBatchedAddChannelEdge(t *testing.T) { // Prune the graph a few times to make sure we have entries in the // prune log. - _, err = graph.PruneGraph(spendOutputs, &blockHash, 155) + _, err := graph.PruneGraph(spendOutputs, &blockHash, 155) require.Nil(t, err) var blockHash2 chainhash.Hash copy(blockHash2[:], bytes.Repeat([]byte{2}, 32)) @@ -3906,31 +4052,24 @@ func TestBatchedAddChannelEdge(t *testing.T) { func TestBatchedUpdateEdgePolicy(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.Nil(t, err) + graph := MakeTestGraph(t) // We'd like to test the update of edges inserted into the database, so // we create two vertexes to connect. - node1, err := createTestVertex(graph.db) - require.Nil(t, err) - err = graph.AddLightningNode(node1) - require.Nil(t, err) - node2, err := createTestVertex(graph.db) - require.Nil(t, err) - err = graph.AddLightningNode(node2) - require.Nil(t, err) + node1 := createTestVertex(t) + require.NoError(t, graph.AddLightningNode(node1)) + node2 := createTestVertex(t) + require.NoError(t, graph.AddLightningNode(node2)) // Create an edge and add it to the db. - edgeInfo, edge1, edge2 := createChannelEdge(graph.db, node1, node2) + edgeInfo, edge1, edge2 := createChannelEdge(node1, node2) // Make sure inserting the policy at this point, before the edge info // is added, will fail. - err = graph.UpdateEdgePolicy(edge1) - require.Error(t, ErrEdgeNotFound, err) + require.Error(t, ErrEdgeNotFound, graph.UpdateEdgePolicy(edge1)) // Add the edge info. - err = graph.AddChannelEdge(edgeInfo) - require.Nil(t, err) + require.NoError(t, graph.AddChannelEdge(edgeInfo)) errTimeout := errors.New("timeout adding batched channel") @@ -3963,8 +4102,7 @@ func TestBatchedUpdateEdgePolicy(t *testing.T) { // BenchmarkForEachChannel is a benchmark test that measures the number of // allocations and the total memory consumed by the full graph traversal. func BenchmarkForEachChannel(b *testing.B) { - graph, err := MakeTestGraph(b) - require.Nil(b, err) + graph := MakeTestGraph(b) const numNodes = 100 const numChannels = 4 @@ -3979,7 +4117,7 @@ func BenchmarkForEachChannel(b *testing.B) { ) var nodes []route.Vertex - err = graph.ForEachNodeCacheable(func(node route.Vertex, + err := graph.ForEachNodeCacheable(func(node route.Vertex, vector *lnwire.FeatureVector) error { nodes = append(nodes, node) @@ -3989,8 +4127,7 @@ func BenchmarkForEachChannel(b *testing.B) { require.NoError(b, err) for _, n := range nodes { - cb := func(tx kvdb.RTx, - info *models.ChannelEdgeInfo, + cb := func(info *models.ChannelEdgeInfo, policy *models.ChannelEdgePolicy, policy2 *models.ChannelEdgePolicy) error { //nolint:ll @@ -4015,24 +4152,21 @@ func BenchmarkForEachChannel(b *testing.B) { // TestGraphCacheForEachNodeChannel tests that the forEachNodeDirectedChannel // method works as expected, and is able to handle nil self edges. func TestGraphCacheForEachNodeChannel(t *testing.T) { - graph, err := MakeTestGraph(t) - require.NoError(t, err) + t.Parallel() + + graph := MakeTestGraph(t) // Unset the channel graph cache to simulate the user running with the // option turned off. graph.graphCache = nil - node1, err := createTestVertex(graph.db) - require.Nil(t, err) - err = graph.AddLightningNode(node1) - require.Nil(t, err) - node2, err := createTestVertex(graph.db) - require.Nil(t, err) - err = graph.AddLightningNode(node2) - require.Nil(t, err) + node1 := createTestVertex(t) + require.NoError(t, graph.AddLightningNode(node1)) + node2 := createTestVertex(t) + require.NoError(t, graph.AddLightningNode(node2)) // Create an edge and add it to the db. - edgeInfo, e1, e2 := createChannelEdge(graph.db, node1, node2) + edgeInfo, e1, e2 := createChannelEdge(node1, node2) // Because of lexigraphical sorting and the usage of random node keys in // this test, we need to determine which edge belongs to node 1 at @@ -4049,7 +4183,7 @@ func TestGraphCacheForEachNodeChannel(t *testing.T) { getSingleChannel := func() *DirectedChannel { var ch *DirectedChannel - err = graph.ForEachNodeDirectedChannel(node1.PubKeyBytes, + err := graph.ForEachNodeDirectedChannel(node1.PubKeyBytes, func(c *DirectedChannel) error { require.Nil(t, ch) ch = c @@ -4093,17 +4227,12 @@ func TestGraphCacheForEachNodeChannel(t *testing.T) { // TestGraphLoading asserts that the cache is properly reconstructed after a // restart. func TestGraphLoading(t *testing.T) { - // First, create a temporary directory to be used for the duration of - // this test. - tempDirName := t.TempDir() + t.Parallel() // Next, create the graph for the first time. - backend, backendCleanup, err := kvdb.GetTestBackend(tempDirName, "cgr") - require.NoError(t, err) - defer backend.Close() - defer backendCleanup() + graphStore := NewTestDB(t) - graph, err := NewChannelGraph(&Config{KVDB: backend}) + graph, err := NewChannelGraph(graphStore) require.NoError(t, err) require.NoError(t, graph.Start()) t.Cleanup(func() { @@ -4117,7 +4246,7 @@ func TestGraphLoading(t *testing.T) { // Recreate the graph. This should cause the graph cache to be // populated. - graphReloaded, err := NewChannelGraph(&Config{KVDB: backend}) + graphReloaded, err := NewChannelGraph(graphStore) require.NoError(t, err) require.NoError(t, graphReloaded.Start()) t.Cleanup(func() { @@ -4141,8 +4270,7 @@ func TestGraphLoading(t *testing.T) { func TestClosedScid(t *testing.T) { t.Parallel() - graph, err := MakeTestGraph(t) - require.Nil(t, err) + graph := MakeTestGraph(t) scid := lnwire.ShortChannelID{} @@ -4178,8 +4306,7 @@ func TestLightningNodePersistence(t *testing.T) { t.Parallel() // Create a new test graph instance. - graph, err := MakeTestGraph(t) - require.NoError(t, err) + graph := MakeTestGraph(t) nodeAnnBytes, err := hex.DecodeString(testNodeAnn) require.NoError(t, err) diff --git a/graph/db/interfaces.go b/graph/db/interfaces.go index f5dce71ca..25d7257be 100644 --- a/graph/db/interfaces.go +++ b/graph/db/interfaces.go @@ -1,6 +1,13 @@ package graphdb 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/lnwire" "github.com/lightningnetwork/lnd/routing/route" @@ -37,3 +44,323 @@ type NodeTraverser interface { // FetchNodeFeatures returns the features of the given node. 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) +} diff --git a/graph/db/kv_store.go b/graph/db/kv_store.go index aa120a39a..3e1e009e2 100644 --- a/graph/db/kv_store.go +++ b/graph/db/kv_store.go @@ -2,6 +2,7 @@ package graphdb import ( "bytes" + "context" "crypto/sha256" "encoding/binary" "errors" @@ -192,10 +193,14 @@ type KVStore struct { rejectCache *rejectCache chanCache *channelCache - chanScheduler batch.Scheduler - nodeScheduler batch.Scheduler + chanScheduler batch.Scheduler[kvdb.RwTx] + 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 // returned instance has its own unique reject cache and channel cache. func NewKVStore(db kvdb.Backend, options ...KVStoreOptionModifier) (*KVStore, @@ -218,10 +223,12 @@ func NewKVStore(db kvdb.Backend, options ...KVStoreOptionModifier) (*KVStore, chanCache: newChannelCache(opts.ChannelCacheSize), } g.chanScheduler = batch.NewTimeScheduler( - db, &g.cacheMu, opts.BatchCommitInterval, + batch.NewBoltBackend[kvdb.RwTx](db), &g.cacheMu, + opts.BatchCommitInterval, ) g.nodeScheduler = batch.NewTimeScheduler( - db, nil, opts.BatchCommitInterval, + batch.NewBoltBackend[kvdb.RwTx](db), nil, + opts.BatchCommitInterval, ) return g, nil @@ -299,29 +306,6 @@ var graphTopLevelBuckets = [][]byte{ 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 // 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 @@ -584,7 +568,7 @@ func (c *KVStore) ForEachNodeCached(cb func(node route.Vertex, channels := make(map[uint64]*DirectedChannel) - err := c.ForEachNodeChannelTx(tx, node.PubKeyBytes, + err := c.forEachNodeChannelTx(tx, node.PubKeyBytes, func(tx kvdb.RTx, e *models.ChannelEdgeInfo, p1 *models.ChannelEdgePolicy, p2 *models.ChannelEdgePolicy) error { @@ -871,19 +855,18 @@ func (c *KVStore) SetSourceNode(node *models.LightningNode) error { // // TODO(roasbeef): also need sig of announcement. func (c *KVStore) AddLightningNode(node *models.LightningNode, - op ...batch.SchedulerOption) error { + opts ...batch.SchedulerOption) error { - r := &batch.Request{ - Update: func(tx kvdb.RwTx) error { + ctx := context.TODO() + + r := &batch.Request[kvdb.RwTx]{ + Opts: batch.NewSchedulerOptions(opts...), + Do: func(tx kvdb.RwTx) error { return addLightningNode(tx, node) }, } - for _, f := range op { - f(r) - } - - return c.nodeScheduler.Execute(r) + return c.nodeScheduler.Execute(ctx, r) } 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 // globally within the database. func (c *KVStore) AddChannelEdge(edge *models.ChannelEdgeInfo, - op ...batch.SchedulerOption) error { + opts ...batch.SchedulerOption) error { + + ctx := context.TODO() var alreadyExists bool - r := &batch.Request{ + r := &batch.Request[kvdb.RwTx]{ + Opts: batch.NewSchedulerOptions(opts...), Reset: func() { alreadyExists = false }, - Update: func(tx kvdb.RwTx) error { + Do: func(tx kvdb.RwTx) error { err := c.addChannelEdge(tx, edge) // Silence ErrEdgeAlreadyExist so that the batch can @@ -1042,15 +1028,7 @@ func (c *KVStore) AddChannelEdge(edge *models.ChannelEdgeInfo, }, } - for _, f := range op { - if f == nil { - return fmt.Errorf("nil scheduler option was used") - } - - f(r) - } - - return c.chanScheduler.Execute(r) + return c.chanScheduler.Execute(ctx, r) } // 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 // nodes on either side of the channel. func (c *KVStore) UpdateEdgePolicy(edge *models.ChannelEdgePolicy, - op ...batch.SchedulerOption) (route.Vertex, route.Vertex, error) { + opts ...batch.SchedulerOption) (route.Vertex, route.Vertex, error) { var ( + ctx = context.TODO() isUpdate1 bool edgeNotFound bool from, to route.Vertex ) - r := &batch.Request{ + r := &batch.Request[kvdb.RwTx]{ + Opts: batch.NewSchedulerOptions(opts...), Reset: func() { isUpdate1 = false edgeNotFound = false }, - Update: func(tx kvdb.RwTx) error { + Do: func(tx kvdb.RwTx) error { var err error from, to, isUpdate1, err = updateEdgePolicy(tx, edge) if err != nil { @@ -2761,11 +2741,7 @@ func (c *KVStore) UpdateEdgePolicy(edge *models.ChannelEdgePolicy, }, } - for _, f := range op { - f(r) - } - - err := c.chanScheduler.Execute(r) + err := c.chanScheduler.Execute(ctx, r) return from, to, err } @@ -2873,7 +2849,7 @@ func (c *KVStore) isPublic(tx kvdb.RTx, nodePub route.Vertex, // used to terminate the check early. nodeIsPublic := false 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, _ *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. 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 { - 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 // 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 @@ -3145,7 +3164,7 @@ func (c *KVStore) ForEachNodeChannel(nodePub route.Vertex, // should be passed as the first argument. Otherwise, the first argument should // be nil and a fresh transaction will be created to execute the graph // traversal. -func (c *KVStore) ForEachNodeChannelTx(tx kvdb.RTx, +func (c *KVStore) forEachNodeChannelTx(tx kvdb.RTx, nodePub route.Vertex, cb func(kvdb.RTx, *models.ChannelEdgeInfo, *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error) error { @@ -3153,11 +3172,11 @@ func (c *KVStore) ForEachNodeChannelTx(tx kvdb.RTx, 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 // one of the nodes, and wishes to obtain the full LightningNode for the other // end of the channel. -func (c *KVStore) FetchOtherNode(tx kvdb.RTx, +func (c *KVStore) fetchOtherNode(tx kvdb.RTx, channel *models.ChannelEdgeInfo, thisNodeKey []byte) ( *models.LightningNode, error) { @@ -4687,7 +4706,7 @@ func (c *chanGraphNodeTx) FetchNode(nodePub route.Vertex) (NodeRTx, error) { func (c *chanGraphNodeTx) ForEachChannel(f func(*models.ChannelEdgeInfo, *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, 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. -func MakeTestGraph(t testing.TB, modifiers ...KVStoreOptionModifier) ( - *ChannelGraph, error) { - - opts := DefaultOptions() - for _, modifier := range modifiers { - modifier(opts) - } +// +// NOTE: this helper currently creates a ChannelGraph that is only ever backed +// by the `KVStore` of the `V1Store` interface. +func MakeTestGraph(t testing.TB, opts ...ChanGraphOption) *ChannelGraph { + t.Helper() // Next, create KVStore for the first time. backend, backendCleanup, err := kvdb.GetTestBackend(t.TempDir(), "cgr") - if err != nil { - backendCleanup() - - return nil, err - } - - graph, err := NewChannelGraph(&Config{ - KVDB: backend, - KVStoreOpts: modifiers, - }) - if err != nil { - backendCleanup() - - return nil, err - } - require.NoError(t, graph.Start()) - + t.Cleanup(backendCleanup) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, backend.Close()) + }) + + graphStore, err := NewKVStore(backend) + require.NoError(t, err) + + graph, err := NewChannelGraph(graphStore, opts...) + require.NoError(t, err) + require.NoError(t, graph.Start()) t.Cleanup(func() { - _ = backend.Close() - backendCleanup() require.NoError(t, graph.Stop()) }) - return graph, nil + return graph } diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go new file mode 100644 index 000000000..fa7a49206 --- /dev/null +++ b/graph/db/sql_store.go @@ -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, + } +} diff --git a/graph/db/test_kvdb.go b/graph/db/test_kvdb.go new file mode 100644 index 000000000..0dd3bd2ff --- /dev/null +++ b/graph/db/test_kvdb.go @@ -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 +} diff --git a/graph/db/test_postgres.go b/graph/db/test_postgres.go new file mode 100644 index 000000000..0c2163083 --- /dev/null +++ b/graph/db/test_postgres.go @@ -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) +} diff --git a/graph/db/test_sqlite.go b/graph/db/test_sqlite.go new file mode 100644 index 000000000..5cfe16812 --- /dev/null +++ b/graph/db/test_sqlite.go @@ -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) +} diff --git a/graph/interfaces.go b/graph/interfaces.go index e351514bb..52897d8f5 100644 --- a/graph/interfaces.go +++ b/graph/interfaces.go @@ -8,7 +8,6 @@ import ( "github.com/lightningnetwork/lnd/batch" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" - "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" ) @@ -254,10 +253,9 @@ type DB interface { // to the caller. // // Unknown policies are passed into the callback as nil values. - ForEachNodeChannel(nodePub route.Vertex, cb func(kvdb.RTx, - *models.ChannelEdgeInfo, - *models.ChannelEdgePolicy, - *models.ChannelEdgePolicy) error) error + ForEachNodeChannel(nodePub route.Vertex, + cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy, + *models.ChannelEdgePolicy) error) error // AddEdgeProof sets the proof of an existing edge in the graph // database. diff --git a/graph/notifications_test.go b/graph/notifications_test.go index 0e2ec7afb..71c6f4a34 100644 --- a/graph/notifications_test.go +++ b/graph/notifications_test.go @@ -22,7 +22,6 @@ import ( "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/input" - "github.com/lightningnetwork/lnd/kvdb" lnmock "github.com/lightningnetwork/lnd/lntest/mock" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" @@ -89,7 +88,7 @@ func createTestNode(t *testing.T) *models.LightningNode { LastUpdate: time.Unix(updateTime, 0), Addresses: testAddrs, Color: color.RGBA{1, 2, 3, 0}, - Alias: "kek" + string(pub[:]), + Alias: "kek" + hex.EncodeToString(pub), AuthSigBytes: testSig.Serialize(), Features: testFeatures, } @@ -1034,9 +1033,7 @@ type testCtx struct { func createTestCtxSingleNode(t *testing.T, startingHeight uint32) *testCtx { - graph, graphBackend, err := makeTestGraph(t, true) - require.NoError(t, err, "failed to make test graph") - + graph := graphdb.MakeTestGraph(t) sourceNode := createTestNode(t) require.NoError(t, @@ -1044,8 +1041,7 @@ func createTestCtxSingleNode(t *testing.T, ) graphInstance := &testGraphInstance{ - graph: graph, - graphBackend: graphBackend, + graph: graph, } return createTestCtxFromGraphInstance( @@ -1084,37 +1080,8 @@ func (c *testCtx) RestartBuilder(t *testing.T) { 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 { - graph *graphdb.ChannelGraph - graphBackend kvdb.Backend + graph *graphdb.ChannelGraph // 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 diff --git a/kvdb/backend.go b/kvdb/backend.go index 269d5d821..3354cc821 100644 --- a/kvdb/backend.go +++ b/kvdb/backend.go @@ -274,7 +274,7 @@ func GetTestBackend(path, name string) (Backend, func(), error) { return nil, empty, err } backend, err := Open( - EtcdBackendName, context.TODO(), etcdConfig, + EtcdBackendName, context.Background(), etcdConfig, ) return backend, cancel, err diff --git a/kvdb/etcd/db_test.go b/kvdb/etcd/db_test.go index 59e29fc94..9ef68d9fe 100644 --- a/kvdb/etcd/db_test.go +++ b/kvdb/etcd/db_test.go @@ -19,7 +19,7 @@ func TestDump(t *testing.T) { f := NewEtcdTestFixture(t) - db, err := newEtcdBackend(context.TODO(), f.BackendConfig()) + db, err := newEtcdBackend(context.Background(), f.BackendConfig()) require.NoError(t, err) err = db.Update(func(tx walletdb.ReadWriteTx) error { diff --git a/kvdb/etcd/fixture.go b/kvdb/etcd/fixture.go index b7a697fad..c19047b39 100644 --- a/kvdb/etcd/fixture.go +++ b/kvdb/etcd/fixture.go @@ -76,7 +76,7 @@ func (f *EtcdTestFixture) NewBackend(singleWriter bool) walletdb.DB { cfg.SingleWriter = true } - db, err := newEtcdBackend(context.TODO(), cfg) + db, err := newEtcdBackend(context.Background(), cfg) require.NoError(f.t, err) 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. func (f *EtcdTestFixture) Put(key, value string) { - ctx, cancel := context.WithTimeout(context.TODO(), testEtcdTimeout) + ctx, cancel := context.WithTimeout( + context.Background(), testEtcdTimeout, + ) defer cancel() _, 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. func (f *EtcdTestFixture) Get(key string) string { - ctx, cancel := context.WithTimeout(context.TODO(), testEtcdTimeout) + ctx, cancel := context.WithTimeout( + context.Background(), testEtcdTimeout, + ) defer cancel() 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. func (f *EtcdTestFixture) Dump() map[string]string { - ctx, cancel := context.WithTimeout(context.TODO(), testEtcdTimeout) + ctx, cancel := context.WithTimeout( + context.Background(), testEtcdTimeout, + ) defer cancel() resp, err := f.cli.Get(ctx, "\x00", clientv3.WithFromKey()) diff --git a/kvdb/etcd/readwrite_tx_test.go b/kvdb/etcd/readwrite_tx_test.go index b5758f7a3..d66b2e251 100644 --- a/kvdb/etcd/readwrite_tx_test.go +++ b/kvdb/etcd/readwrite_tx_test.go @@ -16,7 +16,7 @@ func TestChangeDuringManualTx(t *testing.T) { f := NewEtcdTestFixture(t) - db, err := newEtcdBackend(context.TODO(), f.BackendConfig()) + db, err := newEtcdBackend(context.Background(), f.BackendConfig()) require.NoError(t, err) tx, err := db.BeginReadWriteTx() @@ -44,7 +44,7 @@ func TestChangeDuringUpdate(t *testing.T) { f := NewEtcdTestFixture(t) - db, err := newEtcdBackend(context.TODO(), f.BackendConfig()) + db, err := newEtcdBackend(context.Background(), f.BackendConfig()) require.NoError(t, err) count := 0 diff --git a/kvdb/etcd/walletdb_interface_test.go b/kvdb/etcd/walletdb_interface_test.go index 13c57e337..483becbb2 100644 --- a/kvdb/etcd/walletdb_interface_test.go +++ b/kvdb/etcd/walletdb_interface_test.go @@ -15,5 +15,5 @@ import ( func TestWalletDBInterface(t *testing.T) { f := NewEtcdTestFixture(t) cfg := f.BackendConfig() - walletdbtest.TestInterface(t, dbType, context.TODO(), &cfg) + walletdbtest.TestInterface(t, dbType, context.Background(), &cfg) } diff --git a/lnd.go b/lnd.go index e63ddc196..3afa8c2fb 100644 --- a/lnd.go +++ b/lnd.go @@ -626,7 +626,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, // Set up the core server which will listen for incoming peer // connections. server, err := newServer( - cfg, cfg.Listeners, dbs, activeChainControl, &idKeyDesc, + ctx, cfg, cfg.Listeners, dbs, activeChainControl, &idKeyDesc, activeChainControl.Cfg.WalletUnlockParams.ChansToRestore, multiAcceptor, torController, tlsManager, leaderElector, implCfg, @@ -758,7 +758,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, // case the startup of the subservers do not behave as expected. errChan := make(chan error) go func() { - errChan <- server.Start() + errChan <- server.Start(ctx) }() defer func() { diff --git a/lnrpc/autopilotrpc/autopilot_server.go b/lnrpc/autopilotrpc/autopilot_server.go index 23d0ff5f3..e5952949e 100644 --- a/lnrpc/autopilotrpc/autopilot_server.go +++ b/lnrpc/autopilotrpc/autopilot_server.go @@ -198,7 +198,7 @@ func (s *Server) Status(ctx context.Context, // ModifyStatus activates the current autopilot agent, if active. // // NOTE: Part of the AutopilotServer interface. -func (s *Server) ModifyStatus(ctx context.Context, +func (s *Server) ModifyStatus(_ context.Context, in *ModifyStatusRequest) (*ModifyStatusResponse, error) { log.Debugf("Setting agent enabled=%v", in.Enable) @@ -236,7 +236,7 @@ func (s *Server) QueryScores(ctx context.Context, in *QueryScoresRequest) ( // Query the heuristics. heuristicScores, err := s.manager.QueryHeuristics( - nodes, !in.IgnoreLocalState, + ctx, nodes, !in.IgnoreLocalState, ) if err != nil { return nil, err diff --git a/lnwire/channel_announcement.go b/lnwire/channel_announcement.go index 0a3989abb..05161cca8 100644 --- a/lnwire/channel_announcement.go +++ b/lnwire/channel_announcement.go @@ -70,8 +70,8 @@ var _ SizeableMessage = (*ChannelAnnouncement1)(nil) // io.Reader observing the specified protocol version. // // This is part of the lnwire.Message interface. -func (a *ChannelAnnouncement1) Decode(r io.Reader, pver uint32) error { - return ReadElements(r, +func (a *ChannelAnnouncement1) Decode(r io.Reader, _ uint32) error { + err := ReadElements(r, &a.NodeSig1, &a.NodeSig2, &a.BitcoinSig1, @@ -85,6 +85,11 @@ func (a *ChannelAnnouncement1) Decode(r io.Reader, pver uint32) error { &a.BitcoinKey2, &a.ExtraOpaqueData, ) + if err != nil { + return err + } + + return a.ExtraOpaqueData.ValidateTLV() } // Encode serializes the target ChannelAnnouncement into the passed io.Writer diff --git a/lnwire/channel_announcement_2.go b/lnwire/channel_announcement_2.go index 57b3a24b8..95af69eda 100644 --- a/lnwire/channel_announcement_2.go +++ b/lnwire/channel_announcement_2.go @@ -126,7 +126,7 @@ func (c *ChannelAnnouncement2) DecodeTLVRecords(r io.Reader) error { c.ExtraOpaqueData = tlvRecords } - return nil + return c.ExtraOpaqueData.ValidateTLV() } // Encode serializes the target AnnounceSignatures1 into the passed io.Writer diff --git a/lnwire/channel_update.go b/lnwire/channel_update.go index 88f981671..55d9d3181 100644 --- a/lnwire/channel_update.go +++ b/lnwire/channel_update.go @@ -132,7 +132,7 @@ var _ SizeableMessage = (*ChannelUpdate1)(nil) // io.Reader observing the specified protocol version. // // 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, &a.Signature, 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 diff --git a/lnwire/channel_update_2.go b/lnwire/channel_update_2.go index 56f7edf6b..343af6b1e 100644 --- a/lnwire/channel_update_2.go +++ b/lnwire/channel_update_2.go @@ -154,7 +154,7 @@ func (c *ChannelUpdate2) DecodeTLVRecords(r io.Reader) error { c.ExtraOpaqueData = tlvRecords } - return nil + return c.ExtraOpaqueData.ValidateTLV() } // Encode serializes the target ChannelUpdate2 into the passed io.Writer diff --git a/lnwire/extra_bytes.go b/lnwire/extra_bytes.go index 4681426cb..9530e06e8 100644 --- a/lnwire/extra_bytes.go +++ b/lnwire/extra_bytes.go @@ -63,6 +63,28 @@ func (e *ExtraOpaqueData) Decode(r io.Reader) error { 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 // ExtraOpaqueData instance. The records will be encoded as a raw TLV stream // and stored within the backing slice pointer. diff --git a/lnwire/node_announcement.go b/lnwire/node_announcement.go index 5ba2d7a1d..d3502a7d9 100644 --- a/lnwire/node_announcement.go +++ b/lnwire/node_announcement.go @@ -112,8 +112,8 @@ var _ SizeableMessage = (*NodeAnnouncement)(nil) // io.Reader observing the specified protocol version. // // This is part of the lnwire.Message interface. -func (a *NodeAnnouncement) Decode(r io.Reader, pver uint32) error { - return ReadElements(r, +func (a *NodeAnnouncement) Decode(r io.Reader, _ uint32) error { + err := ReadElements(r, &a.Signature, &a.Features, &a.Timestamp, @@ -123,6 +123,11 @@ func (a *NodeAnnouncement) Decode(r io.Reader, pver uint32) error { &a.Addresses, &a.ExtraOpaqueData, ) + if err != nil { + return err + } + + return a.ExtraOpaqueData.ValidateTLV() } // Encode serializes the target NodeAnnouncement into the passed io.Writer diff --git a/macaroons/service_test.go b/macaroons/service_test.go index b7694ab3e..40d164dd4 100644 --- a/macaroons/service_test.go +++ b/macaroons/service_test.go @@ -55,6 +55,7 @@ func setupTestRootKeyStorage(t *testing.T) kvdb.Backend { // TestNewService tests the creation of the macaroon service. func TestNewService(t *testing.T) { t.Parallel() + ctx := context.Background() // First, initialize a dummy DB file with a store that the service // 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") // 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 { t.Fatalf("Received %v instead of ErrMissingRootKeyID", err) } macaroon, err := service.NewMacaroon( - context.TODO(), macaroons.DefaultRootKeyID, testOperation, + ctx, macaroons.DefaultRootKeyID, testOperation, ) require.NoError(t, err, "Error creating macaroon from service") if macaroon.Namespace().String() != "std:" { @@ -108,6 +109,7 @@ func TestNewService(t *testing.T) { // incoming context. func TestValidateMacaroon(t *testing.T) { t.Parallel() + ctx := context.Background() // First, initialize the service and unlock it. db := setupTestRootKeyStorage(t) @@ -124,7 +126,7 @@ func TestValidateMacaroon(t *testing.T) { // Then, create a new macaroon that we can serialize. macaroon, err := service.NewMacaroon( - context.TODO(), macaroons.DefaultRootKeyID, testOperation, + ctx, macaroons.DefaultRootKeyID, testOperation, testOperationURI, ) require.NoError(t, err, "Error creating macaroon from service") @@ -136,7 +138,7 @@ func TestValidateMacaroon(t *testing.T) { md := metadata.New(map[string]string{ "macaroon": hex.EncodeToString(macaroonBinary), }) - mockContext := metadata.NewIncomingContext(context.Background(), md) + mockContext := metadata.NewIncomingContext(ctx, md) // Finally, validate the macaroon against the required permissions. err = service.ValidateMacaroon( @@ -155,6 +157,7 @@ func TestValidateMacaroon(t *testing.T) { // TestListMacaroonIDs checks that ListMacaroonIDs returns the expected result. func TestListMacaroonIDs(t *testing.T) { t.Parallel() + ctx := context.Background() // First, initialize a dummy DB file with a store that the service // 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. expectedIDs := [][]byte{{1}, {2}, {3}} 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") } // 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") } diff --git a/macaroons/store_test.go b/macaroons/store_test.go index 37dce7110..7a2562140 100644 --- a/macaroons/store_test.go +++ b/macaroons/store_test.go @@ -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, // reading keys and closing it. func TestStore(t *testing.T) { + t.Parallel() + ctx := context.Background() + tempDir, store := newTestStore(t) - _, _, err := store.RootKey(context.TODO()) + _, _, err := store.RootKey(ctx) require.Equal(t, macaroons.ErrStoreLocked, err) - _, err = store.Get(context.TODO(), nil) + _, err = store.Get(ctx, nil) require.Equal(t, macaroons.ErrStoreLocked, err) pw := []byte("weks") @@ -72,18 +75,18 @@ func TestStore(t *testing.T) { // Check ErrContextRootKeyID is returned when no root key ID found in // context. - _, _, err = store.RootKey(context.TODO()) + _, _, err = store.RootKey(ctx) require.Equal(t, macaroons.ErrContextRootKeyID, err) // Check ErrMissingRootKeyID is returned when empty root key ID is used. emptyKeyID := make([]byte, 0) - badCtx := macaroons.ContextWithRootKeyID(context.TODO(), emptyKeyID) + badCtx := macaroons.ContextWithRootKeyID(ctx, emptyKeyID) _, _, err = store.RootKey(badCtx) require.Equal(t, macaroons.ErrMissingRootKeyID, err) // Create a context with illegal root key ID value. encryptedKeyID := []byte("enckey") - badCtx = macaroons.ContextWithRootKeyID(context.TODO(), encryptedKeyID) + badCtx = macaroons.ContextWithRootKeyID(ctx, encryptedKeyID) _, _, err = store.RootKey(badCtx) require.Equal(t, macaroons.ErrKeyValueForbidden, err) diff --git a/peer/brontide.go b/peer/brontide.go index da4aa610a..17e9bc409 100644 --- a/peer/brontide.go +++ b/peer/brontide.go @@ -1989,9 +1989,13 @@ func newChanMsgStream(p *Brontide, cid lnwire.ChannelID) *msgStream { // channel announcements. func newDiscMsgStream(p *Brontide) *msgStream { 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 // and we need to process it. - p.cfg.AuthGossiper.ProcessRemoteAnnouncement(msg, p) + p.cfg.AuthGossiper.ProcessRemoteAnnouncement(ctx, msg, p) } return newMsgStream( diff --git a/peer/setup_test.go b/peer/setup_test.go new file mode 100644 index 000000000..db41fd531 --- /dev/null +++ b/peer/setup_test.go @@ -0,0 +1,11 @@ +package peer + +import ( + "testing" + + "github.com/lightningnetwork/lnd/kvdb" +) + +func TestMain(m *testing.M) { + kvdb.RunTests(m) +} diff --git a/peer/test_utils.go b/peer/test_utils.go index 87a80712f..673eceed8 100644 --- a/peer/test_utils.go +++ b/peer/test_utils.go @@ -23,7 +23,6 @@ import ( "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" - "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntest/channels" "github.com/lightningnetwork/lnd/lntest/mock" "github.com/lightningnetwork/lnd/lntypes" @@ -603,28 +602,13 @@ func createTestPeer(t *testing.T) *peerTestCtx { const chanActiveTimeout = time.Minute - dbPath := t.TempDir() - - 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) + dbAliceGraph := graphdb.MakeTestGraph(t) require.NoError(t, dbAliceGraph.Start()) t.Cleanup(func() { require.NoError(t, dbAliceGraph.Stop()) }) - dbAliceChannel := channeldb.OpenForTesting(t, dbPath) + dbAliceChannel := channeldb.OpenForTesting(t, t.TempDir()) nodeSignerAlice := netann.NewNodeSigner(aliceKeySigner) diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 8a0280686..c497978c9 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -159,26 +159,21 @@ 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 - } + graph := graphdb.MakeTestGraph(t, graphdb.WithUseGraphCache(useCache)) require.NoError(t, graph.Start()) t.Cleanup(func() { 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 @@ -210,7 +205,7 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( testAddrs = append(testAddrs, testAddr) // 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 { return nil, err } @@ -410,12 +405,12 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( } return &testGraphInstance{ - graph: graph, - graphBackend: graphBackend, - aliasMap: aliasMap, - privKeyMap: privKeyMap, - channelIDs: channelIDs, - links: links, + graph: graph, + mcBackend: mcBackend, + aliasMap: aliasMap, + privKeyMap: privKeyMap, + channelIDs: channelIDs, + links: links, }, nil } @@ -479,8 +474,8 @@ type testChannel struct { } type testGraphInstance struct { - graph *graphdb.ChannelGraph - graphBackend kvdb.Backend + graph *graphdb.ChannelGraph + mcBackend kvdb.Backend // aliasMap is a map from a node's alias to its public key. This type is // provided in order to allow easily look up from the human memorable alias @@ -747,11 +742,11 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, } return &testGraphInstance{ - graph: graph, - graphBackend: graphBackend, - aliasMap: aliasMap, - privKeyMap: privKeyMap, - links: links, + graph: graph, + mcBackend: graphBackend, + aliasMap: aliasMap, + privKeyMap: privKeyMap, + links: links, }, nil } diff --git a/routing/router_test.go b/routing/router_test.go index 62a68b213..0f81bea37 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -123,7 +123,7 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T, mcConfig := &MissionControlConfig{Estimator: estimator} mcController, err := NewMissionController( - graphInstance.graphBackend, route.Vertex{}, mcConfig, + graphInstance.mcBackend, route.Vertex{}, mcConfig, ) require.NoError(t, err, "failed to create missioncontrol") diff --git a/rpcserver.go b/rpcserver.go index 6e288604f..b009b2d6b 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -56,7 +56,6 @@ import ( "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/keychain" - "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/labels" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" @@ -6857,7 +6856,7 @@ func (r *rpcServer) GetNodeMetrics(ctx context.Context, if err != nil { return nil, err } - if err := centralityMetric.Refresh(channelGraph); err != nil { + if err := centralityMetric.Refresh(ctx, channelGraph); err != nil { return nil, err } @@ -6959,7 +6958,7 @@ func (r *rpcServer) GetNodeInfo(ctx context.Context, ) err = graph.ForEachNodeChannel(node.PubKeyBytes, - func(_ kvdb.RTx, edge *models.ChannelEdgeInfo, + func(edge *models.ChannelEdgeInfo, c1, c2 *models.ChannelEdgePolicy) error { numChannels++ @@ -7140,7 +7139,7 @@ func (r *rpcServer) GetNetworkInfo(ctx context.Context, // Graph diameter. channelGraph := autopilot.ChannelGraphFromCachedDatabase(graph) - simpleGraph, err := autopilot.NewSimpleGraph(channelGraph) + simpleGraph, err := autopilot.NewSimpleGraph(ctx, channelGraph) if err != nil { return nil, err } @@ -7641,7 +7640,7 @@ func (r *rpcServer) FeeReport(ctx context.Context, var feeReports []*lnrpc.ChannelFeeReport err = channelGraph.ForEachNodeChannel(selfNode.PubKeyBytes, - func(_ kvdb.RTx, chanInfo *models.ChannelEdgeInfo, + func(chanInfo *models.ChannelEdgeInfo, edgePolicy, _ *models.ChannelEdgePolicy) error { // Self node should always have policies for its diff --git a/server.go b/server.go index 3a077a28f..81cffee3e 100644 --- a/server.go +++ b/server.go @@ -51,7 +51,6 @@ import ( "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/keychain" - "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnencrypt" "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 // 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, nodeKeyDesc *keychain.KeyDescriptor, chansToRestore walletunlocker.ChannelsToRecover, @@ -1210,7 +1211,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, *models.ChannelEdgePolicy) error) error { return s.graphDB.ForEachNodeChannel(selfVertex, - func(_ kvdb.RTx, c *models.ChannelEdgeInfo, + func(c *models.ChannelEdgeInfo, e *models.ChannelEdgePolicy, _ *models.ChannelEdgePolicy) error { @@ -2217,7 +2218,7 @@ func (s *server) startLowLevelServices() error { // NOTE: This function is safe for concurrent access. // //nolint:funlen -func (s *server) Start() error { +func (s *server) Start(ctx context.Context) error { // Get the current blockbeat. beat, err := s.getStartingBeat() if err != nil { @@ -2626,7 +2627,9 @@ func (s *server) Start() error { } s.wg.Add(1) - go s.peerBootstrapper(defaultMinPeers, bootstrappers) + go s.peerBootstrapper( + ctx, defaultMinPeers, bootstrappers, + ) } else { 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 // and that nodes newly joining the network receive an up to date network view // as soon as possible. -func (s *server) peerBootstrapper(numTargetPeers uint32, +func (s *server) peerBootstrapper(ctx context.Context, numTargetPeers uint32, bootstrappers []discovery.NetworkPeerBootstrapper) { 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 // 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 // peers. @@ -3161,7 +3164,7 @@ func (s *server) peerBootstrapper(numTargetPeers uint32, ignoreList = s.createBootstrapIgnorePeers() peerAddrs, err := discovery.MultiSourceBootstrap( - ignoreList, numNeeded*2, bootstrappers..., + ctx, ignoreList, numNeeded*2, bootstrappers..., ) if err != nil { srvrLog.Errorf("Unable to retrieve bootstrap "+ @@ -3210,8 +3213,8 @@ const bootstrapBackOffCeiling = time.Minute * 5 // initialPeerBootstrap attempts to continuously connect to peers on startup // until the target number of peers has been reached. This ensures that nodes // receive an up to date network view as soon as possible. -func (s *server) initialPeerBootstrap(ignore map[autopilot.NodeID]struct{}, - numTargetPeers uint32, +func (s *server) initialPeerBootstrap(ctx context.Context, + ignore map[autopilot.NodeID]struct{}, numTargetPeers uint32, bootstrappers []discovery.NetworkPeerBootstrapper) { 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. peersNeeded := numTargetPeers - numActivePeers bootstrapAddrs, err := discovery.MultiSourceBootstrap( - ignore, peersNeeded, bootstrappers..., + ctx, ignore, peersNeeded, bootstrappers..., ) if err != nil { 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, // iterate through the nodes in our channel graph to find addresses // 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 // each of the nodes. - selfPub := s.identityECDH.PubKey().SerializeCompressed() - err = s.graphDB.ForEachNodeChannel(sourceNode.PubKeyBytes, func( - tx kvdb.RTx, - chanInfo *models.ChannelEdgeInfo, - policy, _ *models.ChannelEdgePolicy) error { + err = s.graphDB.ForEachSourceNodeChannel(func(chanPoint wire.OutPoint, + havePolicy bool, channelPeer *models.LightningNode) error { // 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 // 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 "+ - "ChannelPoint(%v): ", chanInfo.ChannelPoint) - } - - // 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) + "ChannelPoint(%v): ", chanPoint) } pubStr := string(channelPeer.PubKeyBytes[:]) @@ -3638,8 +3622,8 @@ func (s *server) establishPersistentConnections() error { return nil }) if err != nil { - srvrLog.Errorf("Failed to iterate channels for node %x", - sourceNode.PubKeyBytes) + srvrLog.Errorf("Failed to iterate over source node channels: "+ + "%v", err) if !errors.Is(err, graphdb.ErrGraphNoEdgesFound) && !errors.Is(err, graphdb.ErrEdgeNotFound) { diff --git a/sqldb/migrations.go b/sqldb/migrations.go index 1eed29828..c14579c46 100644 --- a/sqldb/migrations.go +++ b/sqldb/migrations.go @@ -30,7 +30,7 @@ var ( // // NOTE: The migration function may have runtime dependencies, which // must be injected during runtime. - migrationConfig = []MigrationConfig{ + migrationConfig = append([]MigrationConfig{ { Name: "000001_invoices", Version: 1, @@ -70,7 +70,7 @@ var ( // schema. This is optional and can be disabled by the // user if necessary. }, - } + }, migrationAdditions...) ) // MigrationConfig is a configuration struct that describes SQL migrations. Each diff --git a/sqldb/migrations_dev.go b/sqldb/migrations_dev.go new file mode 100644 index 000000000..61323dbff --- /dev/null +++ b/sqldb/migrations_dev.go @@ -0,0 +1,5 @@ +//go:build test_db_postgres || test_db_sqlite + +package sqldb + +var migrationAdditions = []MigrationConfig{} diff --git a/sqldb/migrations_prod.go b/sqldb/migrations_prod.go new file mode 100644 index 000000000..551cf1fbb --- /dev/null +++ b/sqldb/migrations_prod.go @@ -0,0 +1,5 @@ +//go:build !test_db_postgres && !test_db_sqlite + +package sqldb + +var migrationAdditions []MigrationConfig diff --git a/sqldb/postgres_fixture.go b/sqldb/postgres_fixture.go index d7814df78..91b95d66e 100644 --- a/sqldb/postgres_fixture.go +++ b/sqldb/postgres_fixture.go @@ -39,7 +39,7 @@ type TestPgFixture struct { // NewTestPgFixture constructs a new TestPgFixture starting up a docker // container running Postgres 11. The started container will expire in after // 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) // by specifying an empty endpoint. pool, err := dockertest.NewPool("") @@ -119,13 +119,13 @@ func (f *TestPgFixture) GetConfig(dbName string) *PostgresConfig { } // 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) require.NoError(t, err, "Could not purge resource") } // randomDBName generates a random database name. -func randomDBName(t *testing.T) string { +func randomDBName(t testing.TB) string { randBytes := make([]byte, 8) _, err := rand.Read(randBytes) 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 // testing using the given fixture. -func NewTestPostgresDB(t *testing.T, fixture *TestPgFixture) *PostgresStore { +func NewTestPostgresDB(t testing.TB, fixture *TestPgFixture) *PostgresStore { t.Helper() dbName := randomDBName(t) diff --git a/sqldb/sqlite.go b/sqldb/sqlite.go index a542a9b63..2b2f7be13 100644 --- a/sqldb/sqlite.go +++ b/sqldb/sqlite.go @@ -220,7 +220,7 @@ func (s *SqliteStore) SetSchemaVersion(version int, dirty bool) error { // NewTestSqliteDB is a helper function that creates an SQLite database for // testing. -func NewTestSqliteDB(t *testing.T) *SqliteStore { +func NewTestSqliteDB(t testing.TB) *SqliteStore { t.Helper() t.Logf("Creating new SQLite DB for testing")