Merge pull request #9692 from lightningnetwork/elle-graph
[graph-work-side-branch]: side branch for graph work
This commit is contained in:
75 files changed
+3106
-1492
No files matched your search
@@ -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
|
||||
|
||||
|
||||
+23
-11
@@ -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)
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
+23
-10
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
+15
-8
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package autopilot
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
kvdb.RunTests(m)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
+28
-12
@@ -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
|
||||
|
||||
+628
-7
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
-11
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package batch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/sqldb"
|
||||
)
|
||||
|
||||
// BoltBatcher is a bbolt implementation of the sqldb.BatchedTx interface.
|
||||
type BoltBatcher[Q any] struct {
|
||||
db kvdb.Backend
|
||||
}
|
||||
|
||||
// NewBoltBackend creates a new BoltBackend instance.
|
||||
func NewBoltBackend[Q any](db kvdb.Backend) *BoltBatcher[Q] {
|
||||
return &BoltBatcher[Q]{db: db}
|
||||
}
|
||||
|
||||
// ExecTx will execute the passed txBody, operating upon generic
|
||||
// parameter Q (usually a storage interface) in a single transaction.
|
||||
//
|
||||
// NOTE: This is part of the sqldb.BatchedTx interface.
|
||||
func (t *BoltBatcher[Q]) ExecTx(_ context.Context, opts sqldb.TxOptions,
|
||||
txBody func(Q) error, reset func()) error {
|
||||
|
||||
if opts.ReadOnly() {
|
||||
return kvdb.View(t.db, func(tx kvdb.RTx) error {
|
||||
q, ok := any(tx).(Q)
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to cast tx(%T) "+
|
||||
"into the type expected by the "+
|
||||
"BoltBatcher(%T)", tx, t)
|
||||
}
|
||||
|
||||
return txBody(q)
|
||||
}, reset)
|
||||
}
|
||||
|
||||
return kvdb.Update(t.db, func(tx kvdb.RwTx) error {
|
||||
q, ok := any(tx).(Q)
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to cast tx(%T) into the "+
|
||||
"type expected by the BoltBatcher(%T)", tx, t)
|
||||
}
|
||||
|
||||
return txBody(q)
|
||||
}, reset)
|
||||
}
|
||||
+44
-16
@@ -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
|
||||
|
||||
+11
-11
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
+8
-4
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+82
-58
@@ -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) {
|
||||
|
||||
+423
-348
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
+62
-40
@@ -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)
|
||||
|
||||
+33
-20
@@ -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!")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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=
|
||||
|
||||
+1
-3
@@ -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 {
|
||||
|
||||
+11
-19
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+29
-21
@@ -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)
|
||||
}
|
||||
}
|
||||
+52
-41
@@ -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
|
||||
}
|
||||
+584
-457
File diff suppressed because it is too large.
Load diff
@@ -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)
|
||||
}
|
||||
+102
-92
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//go:build !test_db_posgres && test_db_sqlite
|
||||
|
||||
package graphdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/sqldb"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// NewTestDB is a helper function that creates a SQLStore backed by a sqlite
|
||||
// database for testing. At the moment, it embeds a KVStore but once the
|
||||
// SQLStore fully implements the V1Store interface, the KVStore will be removed.
|
||||
func NewTestDB(t testing.TB) *SQLStore {
|
||||
backend, backendCleanup, err := kvdb.GetTestBackend(t.TempDir(), "cgr")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(backendCleanup)
|
||||
|
||||
graphStore, err := NewKVStore(backend)
|
||||
require.NoError(t, err)
|
||||
|
||||
db := sqldb.NewTestSqliteDB(t).BaseDB
|
||||
|
||||
executor := sqldb.NewTransactionExecutor(
|
||||
db, func(tx *sql.Tx) SQLQueries {
|
||||
return db.WithTx(tx)
|
||||
},
|
||||
)
|
||||
|
||||
return NewSQLStore(executor, graphStore)
|
||||
}
|
||||
+3
-5
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+10
-4
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
+5
-1
@@ -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(
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
kvdb.RunTests(m)
|
||||
}
|
||||
+2
-18
@@ -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)
|
||||
|
||||
|
||||
+24
-29
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
+4
-5
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
//go:build test_db_postgres || test_db_sqlite
|
||||
|
||||
package sqldb
|
||||
|
||||
var migrationAdditions = []MigrationConfig{}
|
||||
@@ -0,0 +1,5 @@
|
||||
//go:build !test_db_postgres && !test_db_sqlite
|
||||
|
||||
package sqldb
|
||||
|
||||
var migrationAdditions []MigrationConfig
|
||||
@@ -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)
|
||||
|
||||
+1
-1
@@ -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")
|
||||
|
||||
Reference in new issue
Block a user